Skip to content

support propertied decorators #13008

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

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
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
13 changes: 9 additions & 4 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -846,6 +846,8 @@ def analyze_overload_sigs_and_impl(
# that.
non_overload_indexes.append(i)
else:
if item.var.is_property:
self.fail("Decorated property not supported", item)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this puts back the "Decorated property not supported" warning on overloads:

from typing import overload
class A:
    @overload  # E: An overloaded function outside a stub file must have an implementation
    def f(self) -> int: pass
    @property  # E: Decorated property not supported
    @overload
    def f(self) -> int: pass

item.func.is_overload = True
types.append(callable)
elif isinstance(item, FuncDef):
Expand Down Expand Up @@ -957,16 +959,16 @@ def analyze_property_with_multi_part_definition(self, defn: OverloadedFuncDef) -
deleted_items = []
for i, item in enumerate(items[1:]):
if isinstance(item, Decorator):
if len(item.decorators) == 1:
if len(item.decorators) >= 1:
node = item.decorators[0]
Comment on lines +962 to 963
Copy link
Contributor Author

Choose a reason for hiding this comment

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

the first item must be the setter - any other decorator is after the setter:

good:

@foo.setter
@dec
def foo(self, ...):
    ...

bad:

@dec
@foo.setter
def foo(self, ...):
    ...

Copy link
Contributor Author

@graingert graingert Jun 22, 2022

Choose a reason for hiding this comment

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

"@overload" support is still broken, however:

from typing import TypeVar, Generic, overload
from typing_extensions import Self

T_str_or_bytes = TypeVar("T_str_or_bytes", bound=str | bytes)


class Foo(Generic[T_str_or_bytes]):
    def __init__(self, v: T_str_or_bytes) -> None:
        self._v = v

    @property
    @overload
    def ham(self: Foo[str]) -> str:
        return self._v

    @property
    @overload  # E: Decorated property not supported
    def ham(self: Foo[bytes]) -> bytes:
        return self._v

if isinstance(node, MemberExpr):
if node.name == 'setter':
# The first item represents the entire property.
first_item.var.is_settable_property = True
# Get abstractness from the original definition.
item.func.is_abstract = first_item.func.is_abstract
else:
self.fail("Decorated property not supported", item)
else:
self.fail("Decorated property not supported", item)
item.func.accept(self)
else:
self.fail(f'Unexpected definition for property "{first_item.func.name}"',
Expand Down Expand Up @@ -1047,6 +1049,7 @@ def visit_decorator(self, dec: Decorator) -> None:
d.accept(self)
removed: List[int] = []
no_type_check = False
could_be_decorated_property = False
for i, d in enumerate(dec.decorators):
# A bunch of decorators are special cased here.
if refers_to_fullname(d, 'abc.abstractmethod'):
Expand Down Expand Up @@ -1094,14 +1097,16 @@ def visit_decorator(self, dec: Decorator) -> None:
removed.append(i)
else:
self.fail("@final cannot be used with non-method functions", d)
elif not dec.var.is_property:
Copy link
Contributor Author

@graingert graingert Jun 22, 2022

Choose a reason for hiding this comment

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

if a decorator shows up before "is_property" gets set then it's

@decorator
@property
def foo(...)

else we are in the propertied decorator scenario:

@property
@decorator
def foo(...)

could_be_decorated_property = True
for i in reversed(removed):
del dec.decorators[i]
if (not dec.is_overload or dec.var.is_property) and self.type:
dec.var.info = self.type
dec.var.is_initialized_in_class = True
if not no_type_check and self.recurse_into_functions:
dec.func.accept(self)
if dec.decorators and dec.var.is_property:
if could_be_decorated_property and dec.decorators and dec.var.is_property:
self.fail('Decorated property not supported', dec)
if dec.func.is_abstract and dec.func.is_final:
self.fail(f"Method {dec.func.name} is both abstract and final", dec)
Expand Down
18 changes: 17 additions & 1 deletion test-data/unit/semanal-errors.test
Original file line number Diff line number Diff line change
Expand Up @@ -1244,12 +1244,28 @@ class A:
@dec # E: Decorated property not supported
@property
def f(self) -> int: pass
@property # E: Decorated property not supported
@property
@dec
def g(self) -> int: pass
[builtins fixtures/property.pyi]
[out]

[case testDecoratedPropertySetter]
import typing
def dec(f): pass
class A:
@property
@dec
def f(self) -> int: pass
@f.setter
@dec
def f(self, v: int) -> None: pass
@dec # E: Decorated property not supported
@f.setter
def f(self, v: int) -> None: pass
[builtins fixtures/property.pyi]
[out]

[case testImportTwoModulesWithSameNameInFunction]
import typing
def f() -> None:
Expand Down