8000 Check property decorators stricter by sterliakov · Pull Request #19313 · python/mypy · GitHub
[go: up one dir, main page]

Skip to content

Check property decorators stricter #19313

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Document and test .getter rejection
  • Loading branch information
sterliakov committed Jun 21, 2025
commit 25ea88911c79acfe441150ee5851a5ac3b7c41c1
6 changes: 6 additions & 0 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -1575,6 +1575,12 @@ def _is_valid_property_decorator(
if not isinstance(deco.expr, NameExpr) or deco.expr.name != property_name:
return False
if deco.name not in {"setter", "deleter"}:
Copy link
Member
@brianschubert brianschubert Jun 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if deco.name not in {"setter", "deleter"}:
if deco.name not in {"getter", "setter", "deleter"}:

@x.getter is valid at runtime (albeit not common). We probably don't want to emit an "Only supported top decorators are ..." error for it.

I guess we don't have any tests for it 🤷

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. This definitely needs a test, but I think rejecting @prop.getter is a reasonable thing to do: it should override the existing getter, supporting it properly won't be trivial, so explicitly saying "we don't support redefined getters" is not that bad (given that such usage should be really rare, 0 primer hits)

Copy link
Collaborator Author
@sterliakov sterliakov Jun 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By following this github search, I identified two common patterns with @prop.getter (excluding partial subclass overrides as they don't trigger this code and aren't supported anyway):

  1. Mistake with getter instead of setter (followed by signature def (self, value))

  2. Obscure way of defining properties:

     class A:
         @property
         def foo(self): pass
         
         @foo.getter
         def foo(self):
             ...  # Some logic here
         
         @foo.setter
         def foo(self, new_foo):
             ...

    Honestly this makes my eyes bleed. I don't know how popular this is as regex code search is limited to 5 pages, and the file count estimate is never close to reality, they don't actually regex-match whole github. I propose to accept the risk and wait - if we get a couple of tickets about this in the next release, we can whitelist getter as well.

As a secondary reference, pyright handles this in the most confusing manner possible, we don't want to replicate that behaviour. It silently accepts any @prop.getter even with incompatible return type and ignores it completely - see my ticket microsoft/pyright#10633

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The relevant pyright ticket was closed wontfix, but I still think allowing getter and ignoring it isn't optimal:/

# This intentionally excludes getter. While `@prop.getter` is valid at
# runtime, that would mean replacing the already processed getter type.
# Such usage is almost definitely a mistake (except for overrides in
# subclasses but we don't support them anyway) and might be a typo
# (only one letter away from `setter`), it's likely almost never used,
# so supporting it properly won't pay off.
return False
return True

Expand Down
10 changes: 8 additions & 2 deletions test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -1624,7 +1624,6 @@ class A:
[builtins fixtures/property.pyi]

[case testPropertyNameIsChecked]
import typing
class A:
@property
def f(self) -> str: ...
Expand All @@ -1651,14 +1650,21 @@ class C:
[builtins fixtures/property.pyi]

[case testPropertyAttributeIsChecked]
import typing
class A:
@property
def f(self) -> str: ...
@f.unknown # E: Only supported top decorators are @f.setter and @f.deleter
def f(self, val: str) -> None: ...
[builtins fixtures/property.pyi]

[case testPropertyGetterDecoratorIsRejected]
class A:
@property
def f(self) -> str: ...
@f.getter # E: Only supported top decorators are @f.setter and @f.deleter
def f(self, val: str) -> None: ...
[builtins fixtures/property.pyi]

[case testDynamicallyTypedProperty]
import typing
class A:
Expand Down
0