From cb1b86dda35ebb5005d0854010ccd53addd11b37 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sun, 12 Dec 2021 13:44:30 +0300 Subject: [PATCH 1/7] Adds first test to `@property`, refs #1362 --- mypy/semanal.py | 6 +-- test-data/unit/check-classes.test | 62 +++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 9a5076beb6f41..9143cbd08288b 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1062,8 +1062,6 @@ def visit_decorator(self, dec: Decorator) -> None: elif refers_to_fullname(d, 'functools.cached_property'): dec.var.is_settable_property = True self.check_decorated_function_is_method('property', dec) - if len(dec.func.arguments) > 1: - self.fail('Too many arguments', dec.func) elif refers_to_fullname(d, 'typing.no_type_check'): dec.var.type = AnyType(TypeOfAny.special_form) no_type_check = True @@ -1086,8 +1084,8 @@ def visit_decorator(self, dec: Decorator) -> None: 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: - self.fail('Decorated property not supported', dec) + # if dec.decorators and dec.var.is_property: + # self.fail('Decorated property not supported', dec) def check_decorated_function_is_method(self, decorator: str, context: Context) -> None: diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index a2bcdade3287c..5292f51612bc1 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -7011,6 +7011,68 @@ class A: reveal_type(A().y) # N: Revealed type is "builtins.int" [builtins fixtures/property.pyi] +[case testDecoratedProperty] +from typing import Callable, TypeVar + +def correct(x: Callable[['X', int], int]) -> Callable[['X'], int]: + def inner(self: 'X') -> int: + return x(self, 1) + return inner + +def changing_type(x: Callable[['X', int], int]) -> Callable[['X'], str]: + def inner(self: 'X') -> str: + return 'a' + return inner + +def wrong_input(x: Callable[['X', int], int]) -> Callable[['X'], int]: + def inner(self: 'X') -> int: + return 1 + return inner + +def wrong_return(x: Callable[['X', int], int]) -> Callable[[], int]: + def inner() -> int: + return 1 + return inner + +C = TypeVar('C', bound=Callable) + +def return_itself(c: C) -> C: + return c + +class X: + @property + @correct + def a(self, arg: int) -> int: + return 1 + + @property + @changing_type + def b(self, arg: int) -> int: + return 2 + + @property # E: Argument 1 to "wrong_input" has incompatible type "Callable[[X, str], str]"; expected "Callable[[X, int], int]" + @wrong_input + def c(self, arg: str) -> str: + return 'a' + + @property + @wrong_return + def d(self, arg: int) -> int: + return 3 + + @property + @return_itself + def e(self) -> int: + return 4 + +reveal_type(X().a) # N: Revealed type is "builtins.int" +reveal_type(X().b) # N: Revealed type is "builtins.str" +reveal_type(X().c) # N: Revealed type is "builtins.int" +reveal_type(X().d) # E: Attribute function "d" with type "Callable[[], int]" does not accept self argument \ + # N: Revealed type is "builtins.int" +reveal_type(X().e) # N: Revealed type is "builtins.int" +[builtins fixtures/property.pyi] + [case testEnclosingScopeLambdaNoCrash] class C: x = lambda x: x.y.g() From 7716f57b9f2b770f7fc1ded7ff95e4b2e7e2f755 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sun, 12 Dec 2021 15:54:37 +0300 Subject: [PATCH 2/7] Improves how `@property` is typechecked --- mypy/checker.py | 49 +++++++++++++++++++----- mypy/nodes.py | 11 +++++- mypy/semanal.py | 5 +-- test-data/unit/check-classes.test | 5 +-- test-data/unit/check-functools.test | 4 +- test-data/unit/fixtures/property.pyi | 26 ++++++++++--- test-data/unit/fixtures/staticmethod.pyi | 10 ++++- test-data/unit/semanal-errors.test | 21 ++-------- 8 files changed, 89 insertions(+), 42 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index b90221a0a5a53..471dc728dddbb 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -3756,20 +3756,37 @@ def visit_decorator(self, e: Decorator) -> None: continue dec = self.expr_checker.accept(d) temp = self.temp_node(sig, context=e) - fullname = None - if isinstance(d, RefExpr): - fullname = d.fullname - # if this is a expression like @b.a where b is an object, get the type of b - # so we can pass it the method hook in the plugins - object_type: Optional[Type] = None - if fullname is None and isinstance(d, MemberExpr) and d.expr in self.type_map: - object_type = self.type_map[d.expr] - fullname = self.expr_checker.method_fullname(object_type, d.name) + fullname, object_type = self.get_decorator_fullname_and_object_type(d) self.check_for_untyped_decorator(e.func, dec, d) sig, t2 = self.expr_checker.check_call(dec, [temp], [nodes.ARG_POS], e, callable_name=fullname, object_type=object_type) + + if e.property_decorator is not None: + # What we do here: we ensure that `@property` / `@cached_property` + # decorators get the correct type, but we don't override the resulting + # signature. Why? Because decorated properties were not supported + # for a long time. And a lot of implementation details assume + # that we won't atually get `builtins.property` type in the result. + # And we also know how to handle properties pretty well. + # So, this is a not-really-dirty-hack + # that allows us to keep this feature simple. + # See: https://github.com/python/mypy/issues/1362 + fullname, object_type = self.get_decorator_fullname_and_object_type( + e.property_decorator, + ) + prop_type = self.expr_checker.accept(e.property_decorator) + temp = self.temp_node(sig, context=e) + self.expr_checker.check_call( + prop_type, + [temp], + [nodes.ARG_POS], + e, + callable_name=fullname, + object_type=object_type, + ) + self.check_untyped_after_decorator(sig, e.func) sig = set_callable_name(sig, e.func) e.var.type = sig @@ -3783,6 +3800,20 @@ def visit_decorator(self, e: Decorator) -> None: if e.type and not isinstance(get_proper_type(e.type), (FunctionLike, AnyType)): self.fail(message_registry.BAD_CONSTRUCTOR_TYPE, e) + def get_decorator_fullname_and_object_type( + self, dec: Expression, + ) -> Tuple[Optional[str], Optional[Type]]: + fullname = None + if isinstance(dec, RefExpr): + fullname = dec.fullname + # if this is a expression like @b.a where b is an object, get the type of b + # so we can pass it the method hook in the plugins + object_type: Optional[Type] = None + if fullname is None and isinstance(dec, MemberExpr) and dec.expr in self.type_map: + object_type = self.type_map[d.expr] + fullname = self.expr_checker.method_fullname(object_type, dec.name) + return fullname, object_type + def check_for_untyped_decorator(self, func: FuncDef, dec_type: Type, diff --git a/mypy/nodes.py b/mypy/nodes.py index 156d756030ae4..7d325ba1fa3ff 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -776,12 +776,20 @@ class Decorator(SymbolNode, Statement): A single Decorator object can include any number of function decorators. """ - __slots__ = ('func', 'decorators', 'original_decorators', 'var', 'is_overload') + __slots__ = ( + 'func', 'decorators', 'original_decorators', + 'property_decorator', + 'var', 'is_overload', + ) func: FuncDef # Decorated function decorators: List[Expression] # Decorators (may be empty) # Some decorators are removed by semanal, keep the original here. original_decorators: List[Expression] + # We use special field, where we store the `@property` / `@cached_property` + # decorator. It is implied that `@property` is the top-most decorator in the chain. + # Other use-cases are so rare, that we don't support them. + property_decorator: Optional[Expression] # TODO: This is mostly used for the type; consider replacing with a 'type' attribute var: "Var" # Represents the decorated function obj is_overload: bool @@ -792,6 +800,7 @@ def __init__(self, func: FuncDef, decorators: List[Expression], self.func = func self.decorators = decorators self.original_decorators = decorators.copy() + self.property_decorator = None self.var = var self.is_overload = False diff --git a/mypy/semanal.py b/mypy/semanal.py index 9143cbd08288b..5ec76e04e80c5 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -950,7 +950,7 @@ def analyze_property_with_multi_part_definition(self, defn: OverloadedFuncDef) - # Get abstractness from the original definition. item.func.is_abstract = first_item.func.is_abstract else: - self.fail("Decorated property not supported", item) + self.fail("Overloaded property is not supported", item) item.func.accept(self) else: self.fail('Unexpected definition for property "{}"'.format(first_item.func.name), @@ -1062,6 +1062,7 @@ def visit_decorator(self, dec: Decorator) -> None: elif refers_to_fullname(d, 'functools.cached_property'): dec.var.is_settable_property = True self.check_decorated_function_is_method('property', dec) + dec.property_decorator = d elif refers_to_fullname(d, 'typing.no_type_check'): dec.var.type = AnyType(TypeOfAny.special_form) no_type_check = True @@ -1084,8 +1085,6 @@ def visit_decorator(self, dec: Decorator) -> None: 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: - # self.fail('Decorated property not supported', dec) def check_decorated_function_is_method(self, decorator: str, context: Context) -> None: diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 5292f51612bc1..8ed01647f1eda 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -7055,7 +7055,7 @@ class X: def c(self, arg: str) -> str: return 'a' - @property + @property # E: Argument 1 to "property" has incompatible type "Callable[[], int]"; expected "Optional[Callable[[Any], Any]]" @wrong_return def d(self, arg: int) -> int: return 3 @@ -7119,11 +7119,10 @@ class B: return A class C: - @property + @property # E: Argument 1 to "property" has incompatible type "Callable[[], Type[A]]"; expected "Optional[Callable[[Any], Any]]" @staticmethod def A() -> Type[A]: return A - [builtins fixtures/staticmethod.pyi] [case testRefMethodWithOverloadDecorator] diff --git a/test-data/unit/check-functools.test b/test-data/unit/check-functools.test index 33653c8d3fbc1..34b73dca29ab7 100644 --- a/test-data/unit/check-functools.test +++ b/test-data/unit/check-functools.test @@ -119,8 +119,8 @@ class Child(Parent): def f(self) -> str: pass @cached_property def g(self) -> int: pass - @cached_property - def h(self, arg) -> int: pass # E: Too many arguments + @cached_property # E: Argument 1 to "cached_property" has incompatible type "Callable[[Child, Any], int]"; expected "Callable[[Any], int]" + def h(self, arg) -> int: pass reveal_type(Parent().f) # N: Revealed type is "builtins.str" reveal_type(Child().f) # N: Revealed type is "builtins.str" reveal_type(Child().g) # N: Revealed type is "builtins.int" diff --git a/test-data/unit/fixtures/property.pyi b/test-data/unit/fixtures/property.pyi index b3f60abaf8a0c..01394ff5d9e8a 100644 --- a/test-data/unit/fixtures/property.pyi +++ b/test-data/unit/fixtures/property.pyi @@ -1,16 +1,32 @@ -import typing +from typing import Any, Callable, Generic, TypeVar -_T = typing.TypeVar('_T') +_T = TypeVar('_T') class object: def __init__(self) -> None: pass class type: - def __init__(self, x: typing.Any) -> None: pass + def __init__(self, x: Any) -> None: pass class function: pass -property = object() # Dummy definition +class property(object): + fget: Callable[[Any], Any] | None + fset: Callable[[Any, Any], None] | None + fdel: Callable[[Any], None] | None + def __init__( + self, + fget: Callable[[Any], Any] | None = ..., + fset: Callable[[Any, Any], None] | None = ..., + fdel: Callable[[Any], None] | None = ..., + doc: str | None = ..., + ) -> None: ... + def getter(self, __fget: Callable[[Any], Any]) -> property: ... + def setter(self, __fset: Callable[[Any, Any], None]) -> property: ... + def deleter(self, __fdel: Callable[[Any], None]) -> property: ... + def __get__(self, __obj: Any, __type: type | None = ...) -> Any: ... + def __set__(self, __obj: Any, __value: Any) -> None: ... + def __delete__(self, __obj: Any) -> None: ... class dict: pass class int: pass @@ -19,4 +35,4 @@ class bytes: pass class bool: pass class ellipsis: pass -class tuple(typing.Generic[_T]): pass +class tuple(Generic[_T]): pass diff --git a/test-data/unit/fixtures/staticmethod.pyi b/test-data/unit/fixtures/staticmethod.pyi index 7d5d98634e480..d36919c29ad99 100644 --- a/test-data/unit/fixtures/staticmethod.pyi +++ b/test-data/unit/fixtures/staticmethod.pyi @@ -1,4 +1,4 @@ -import typing +from typing import Any, Callable class object: def __init__(self) -> None: pass @@ -9,7 +9,13 @@ class type: class function: pass staticmethod = object() # Dummy definition. -property = object() # Dummy definition + +class property(object): + def __init__( + self, + fget: Callable[[Any], Any] | None = ..., + ) -> None: ... + def __get__(self, __obj: Any, __type: type | None = ...) -> Any: ... class int: @staticmethod diff --git a/test-data/unit/semanal-errors.test b/test-data/unit/semanal-errors.test index e5aea483a210e..e9fa0a24f97e5 100644 --- a/test-data/unit/semanal-errors.test +++ b/test-data/unit/semanal-errors.test @@ -1198,7 +1198,7 @@ def f() -> int: pass import typing class A: @property - def f(self, x) -> int: pass # E: Too many arguments + def f(self, x) -> int: pass @property def g() -> int: pass # E: Method must have at least one argument [builtins fixtures/property.pyi] @@ -1207,10 +1207,10 @@ class A: [case testOverloadedProperty] from typing import overload class A: - @overload # E: Decorated property not supported + @overload # E: Overloaded property is not supported @property def f(self) -> int: pass - @property # E: Decorated property not supported + @property # E: Overloaded property is not supported @overload def f(self) -> int: pass [builtins fixtures/property.pyi] @@ -1221,25 +1221,12 @@ 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 + @property # E: Overloaded property is not supported @overload def f(self) -> int: pass [builtins fixtures/property.pyi] [out] -[case testDecoratedProperty] -import typing -def dec(f): pass -class A: - @dec # E: Decorated property not supported - @property - def f(self) -> int: pass - @property # E: Decorated property not supported - @dec - def g(self) -> int: pass -[builtins fixtures/property.pyi] -[out] - [case testImportTwoModulesWithSameNameInFunction] import typing def f() -> None: From 6a01fa89262101509c16bc4d2371e706f6d08be2 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sun, 12 Dec 2021 16:02:43 +0300 Subject: [PATCH 3/7] Typo --- mypy/checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 471dc728dddbb..2cbb1e4fb712e 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -3810,7 +3810,7 @@ def get_decorator_fullname_and_object_type( # so we can pass it the method hook in the plugins object_type: Optional[Type] = None if fullname is None and isinstance(dec, MemberExpr) and dec.expr in self.type_map: - object_type = self.type_map[d.expr] + object_type = self.type_map[dec.expr] fullname = self.expr_checker.method_fullname(object_type, dec.name) return fullname, object_type From ce4dc0d2569a266dd495c8e3d0e05b66513c0f01 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sun, 12 Dec 2021 19:41:29 +0300 Subject: [PATCH 4/7] Fixes tests --- mypy/semanal.py | 5 +++-- test-data/unit/check-inference.test | 1 - test-data/unit/check-protocols.test | 2 +- test-data/unit/fixtures/dataclasses.pyi | 6 +++++- test-data/unit/fixtures/list.pyi | 6 ++++-- test-data/unit/fixtures/property_py2.pyi | 4 +++- test-data/unit/fixtures/staticmethod.pyi | 7 ++----- test-data/unit/lib-stub/abc.pyi | 6 +++++- test-data/unit/semanal-errors.test | 4 ++-- 9 files changed, 25 insertions(+), 16 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 5ec76e04e80c5..fd16a7ad6d670 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -953,8 +953,9 @@ def analyze_property_with_multi_part_definition(self, defn: OverloadedFuncDef) - self.fail("Overloaded property is not supported", item) item.func.accept(self) else: - self.fail('Unexpected definition for property "{}"'.format(first_item.func.name), - item) + self.fail('Unexpected definition for property "{}"'.format( + first_item.func.name, + ), item) deleted_items.append(i + 1) for i in reversed(deleted_items): del items[i] diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index adf4a7b604205..4ef327d80d171 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -1475,7 +1475,6 @@ class A: self.x = [] # E: Need type annotation for "x" (hint: "x: List[] = ...") class B(A): - # TODO?: This error is kind of a false positive, unfortunately @property def x(self) -> List[int]: # E: Signature of "x" incompatible with supertype "A" return [123] diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index 6768263e9832e..b70fbd85a9786 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -88,7 +88,7 @@ main:37: error: Argument 1 to "fun" has incompatible type "B"; expected "P" main:37: note: Following member(s) of "B" have conflicts: main:37: note: x: expected "int", got "str" main:40: note: Revealed type is "builtins.int" -[builtins fixtures/bool.pyi] +[builtins fixtures/property.pyi] [case testSimpleProtocolOneAbstractMethod] from typing import Protocol diff --git a/test-data/unit/fixtures/dataclasses.pyi b/test-data/unit/fixtures/dataclasses.pyi index 206843a88b244..b83d24e9d1b43 100644 --- a/test-data/unit/fixtures/dataclasses.pyi +++ b/test-data/unit/fixtures/dataclasses.pyi @@ -1,4 +1,5 @@ from typing import ( + Any, Callable, Generic, Iterator, Iterable, Mapping, Optional, Sequence, Tuple, TypeVar, Union, overload, ) @@ -40,4 +41,7 @@ class dict(Mapping[KT, VT]): class list(Generic[_T], Sequence[_T]): pass class function: pass class classmethod: pass -property = object() + +class property(object): + def __init__(self, fget: Callable[[Any], Any]) -> None: + pass diff --git a/test-data/unit/fixtures/list.pyi b/test-data/unit/fixtures/list.pyi index 31dc333b3d4f6..f26ba2f20862c 100644 --- a/test-data/unit/fixtures/list.pyi +++ b/test-data/unit/fixtures/list.pyi @@ -1,6 +1,6 @@ # Builtins stub used in list-related test cases. -from typing import TypeVar, Generic, Iterable, Iterator, Sequence, overload +from typing import Any, Callable, TypeVar, Generic, Iterable, Iterator, Sequence, overload T = TypeVar('T') @@ -35,4 +35,6 @@ class str: def __len__(self) -> bool: pass class bool(int): pass -property = object() # Dummy definition. +class property(object): + def __init__(self, fget: Callable[[Any], Any]) -> None: + pass diff --git a/test-data/unit/fixtures/property_py2.pyi b/test-data/unit/fixtures/property_py2.pyi index 3b0ab69cf43f1..2b9861a34a62b 100644 --- a/test-data/unit/fixtures/property_py2.pyi +++ b/test-data/unit/fixtures/property_py2.pyi @@ -10,7 +10,9 @@ class type: class function: pass -property = object() # Dummy definition +class property(object): + def __init__(self, fget: typing.Callable[[typing.Any], typing.Any]) -> None: + pass class int: pass class str: pass diff --git a/test-data/unit/fixtures/staticmethod.pyi b/test-data/unit/fixtures/staticmethod.pyi index d36919c29ad99..bbdfd13fb2eaa 100644 --- a/test-data/unit/fixtures/staticmethod.pyi +++ b/test-data/unit/fixtures/staticmethod.pyi @@ -11,11 +11,8 @@ class function: pass staticmethod = object() # Dummy definition. class property(object): - def __init__( - self, - fget: Callable[[Any], Any] | None = ..., - ) -> None: ... - def __get__(self, __obj: Any, __type: type | None = ...) -> Any: ... + def __init__(self, fget: Callable[[Any], Any]) -> None: + pass class int: @staticmethod diff --git a/test-data/unit/lib-stub/abc.pyi b/test-data/unit/lib-stub/abc.pyi index da90b588fca31..0b20fbfb24f18 100644 --- a/test-data/unit/lib-stub/abc.pyi +++ b/test-data/unit/lib-stub/abc.pyi @@ -5,5 +5,9 @@ T = TypeVar('T', bound=Type[Any]) class ABC(type): pass class ABCMeta(type): def register(cls, tp: T) -> T: pass + abstractmethod = object() -abstractproperty = object() + +class abstractproperty(property): + def __init__(self, fget: Callable[[Any], Any]) -> None: + pass diff --git a/test-data/unit/semanal-errors.test b/test-data/unit/semanal-errors.test index e9fa0a24f97e5..3d3adb0cb8216 100644 --- a/test-data/unit/semanal-errors.test +++ b/test-data/unit/semanal-errors.test @@ -1207,7 +1207,7 @@ class A: [case testOverloadedProperty] from typing import overload class A: - @overload # E: Overloaded property is not supported + @overload @property def f(self) -> int: pass @property # E: Overloaded property is not supported @@ -1221,7 +1221,7 @@ 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: Overloaded property is not supported + @property @overload def f(self) -> int: pass [builtins fixtures/property.pyi] From 9fffcc9f56c3be58e5e8a728e58612416627057f Mon Sep 17 00:00:00 2001 From: sobolevn Date: Mon, 13 Dec 2021 12:14:48 +0300 Subject: [PATCH 5/7] Fixes `check-flags.test` --- test-data/unit/check-classes.test | 2 +- test-data/unit/fixtures/list.pyi | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 8ed01647f1eda..07bc42c9cc6bd 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -7119,7 +7119,7 @@ class B: return A class C: - @property # E: Argument 1 to "property" has incompatible type "Callable[[], Type[A]]"; expected "Optional[Callable[[Any], Any]]" + @property # E: Argument 1 to "property" has incompatible type "Callable[[], Type[A]]"; expected "Callable[[Any], Any]" @staticmethod def A() -> Type[A]: return A diff --git a/test-data/unit/fixtures/list.pyi b/test-data/unit/fixtures/list.pyi index f26ba2f20862c..39d81dd0c47bf 100644 --- a/test-data/unit/fixtures/list.pyi +++ b/test-data/unit/fixtures/list.pyi @@ -36,5 +36,5 @@ class str: class bool(int): pass class property(object): - def __init__(self, fget: Callable[[Any], Any]) -> None: + def __init__(self, fget: Callable[[object], object]) -> None: pass From db3c76b3a84425f5b0648b9099f6b08dba51b6ce Mon Sep 17 00:00:00 2001 From: sobolevn Date: Mon, 13 Dec 2021 15:28:09 +0300 Subject: [PATCH 6/7] wip --- mypy/checkexpr.py | 1 + mypy/semanal.py | 28 +++++++++++++++------------- test-data/unit/semanal-errors.test | 15 +++++++++++++++ 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 1647339ef2172..df988438e8104 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -2105,6 +2105,7 @@ def analyze_ordinary_member_access(self, e: MemberExpr, self.msg, original_type=original_type, chk=self.chk, in_literal_context=self.is_literal_context(), module_symbol_table=module_symbol_table) + print(e.name, original_type) return member_type diff --git a/mypy/semanal.py b/mypy/semanal.py index fd16a7ad6d670..2d16472d94e1d 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -937,24 +937,23 @@ def analyze_property_with_multi_part_definition(self, defn: OverloadedFuncDef) - """ defn.is_property = True items = defn.items - first_item = cast(Decorator, defn.items[0]) + property_def = cast(Decorator, defn.items[0]) deleted_items = [] for i, item in enumerate(items[1:]): if isinstance(item, Decorator): - if len(item.decorators) == 1: - node = item.decorators[0] - 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("Overloaded property is not supported", item) + for node in item.decorators: + if (isinstance(node, MemberExpr) + and isinstance(node.expr, NameExpr) + and node.expr.name == property_def.var.name + and node.name == 'setter'): + # The `property_def` represents the entire property. + property_def.var.is_settable_property = True + # Get abstractness from the original definition. + item.func.is_abstract = property_def.func.is_abstract item.func.accept(self) else: self.fail('Unexpected definition for property "{}"'.format( - first_item.func.name, + property_def.func.name, ), item) deleted_items.append(i + 1) for i in reversed(deleted_items): @@ -1063,7 +1062,10 @@ def visit_decorator(self, dec: Decorator) -> None: elif refers_to_fullname(d, 'functools.cached_property'): dec.var.is_settable_property = True self.check_decorated_function_is_method('property', dec) - dec.property_decorator = d + if i == 0: + dec.property_decorator = d + else: + self.fail('Property must be used as the top-most decorator', d) elif refers_to_fullname(d, 'typing.no_type_check'): dec.var.type = AnyType(TypeOfAny.special_form) no_type_check = True diff --git a/test-data/unit/semanal-errors.test b/test-data/unit/semanal-errors.test index 3d3adb0cb8216..e635ab1c7b947 100644 --- a/test-data/unit/semanal-errors.test +++ b/test-data/unit/semanal-errors.test @@ -1227,6 +1227,21 @@ class A: [builtins fixtures/property.pyi] [out] +[case testPropertyNotAtTop] +from typing import TypeVar + +T = TypeVar('T') + +def identity(i: T) -> T: + return i + +class A: + @identity + @property # E: Property must be used as the top-most decorator + def f(self) -> int: pass +[builtins fixtures/property.pyi] +[out] + [case testImportTwoModulesWithSameNameInFunction] import typing def f() -> None: From 77b72d672b4a5a92ab4a33abe687d4172fe710ea Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sun, 19 Dec 2021 14:25:21 +0300 Subject: [PATCH 7/7] Fixes CI --- mypy/checkexpr.py | 1 - test-data/unit/check-inference.test | 2 +- test-data/unit/check-protocols.test | 13 +++++++------ test-data/unit/fixtures/property.pyi | 4 +++- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index df988438e8104..1647339ef2172 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -2105,7 +2105,6 @@ def analyze_ordinary_member_access(self, e: MemberExpr, self.msg, original_type=original_type, chk=self.chk, in_literal_context=self.is_literal_context(), module_symbol_table=module_symbol_table) - print(e.name, original_type) return member_type diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 4ef327d80d171..67c129a9fa979 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -1478,7 +1478,7 @@ class B(A): @property def x(self) -> List[int]: # E: Signature of "x" incompatible with supertype "A" return [123] -[builtins fixtures/list.pyi] +[builtins fixtures/property.pyi] [case testInferSetInitializedToEmpty] a = set() diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index b70fbd85a9786..730386d7f270a 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -894,7 +894,7 @@ t: Traversable t = D[int]() # OK if int(): t = C() # E: Incompatible types in assignment (expression has type "C", variable has type "Traversable") -[builtins fixtures/list.pyi] +[builtins fixtures/property.pyi] [typing fixtures/typing-medium.pyi] [case testRecursiveProtocols2] @@ -951,7 +951,7 @@ t = A() # OK if int(): t = B() # E: Incompatible types in assignment (expression has type "B", variable has type "P1") t = C() # E: Incompatible types in assignment (expression has type "C", variable has type "P1") -[builtins fixtures/list.pyi] +[builtins fixtures/property.pyi] [typing fixtures/typing-medium.pyi] [case testMutuallyRecursiveProtocolsTypesWithSubteMismatch] @@ -974,7 +974,8 @@ t: P1 t = A() # E: Incompatible types in assignment (expression has type "A", variable has type "P1") \ # N: Following member(s) of "A" have conflicts: \ # N: attr1: expected "Sequence[P2]", got "List[B]" -[builtins fixtures/list.pyi] +[builtins fixtures/property.pyi] +[typing fixtures/typing-medium.pyi] [case testMutuallyRecursiveProtocolsTypesWithSubteMismatchWriteable] from typing import Protocol @@ -1831,7 +1832,7 @@ fun(N2(1)) # E: Argument 1 to "fun" has incompatible type "N2"; expected "P[int, reveal_type(fun3(z)) # N: Revealed type is "builtins.object*" reveal_type(fun3(z3)) # N: Revealed type is "builtins.int*" -[builtins fixtures/list.pyi] +[builtins fixtures/property.pyi] [case testBasicCallableStructuralSubtyping] from typing import Callable, Generic, TypeVar @@ -2017,7 +2018,7 @@ f(C()) # E: Argument 1 to "f" has incompatible type "C"; expected "P" \ # N: attr1: expected "int", got "str" \ # N: attr2: expected "str", got "int" \ # N: Protocol member P.attr2 expected settable variable, got read-only attribute -[builtins fixtures/list.pyi] +[builtins fixtures/property.pyi] [case testIterableProtocolOnClass] from typing import TypeVar, Iterator @@ -2083,7 +2084,7 @@ fun_p(D()) # E: Argument 1 to "fun_p" has incompatible type "D"; expected "PP" # N: Following member(s) of "D" have conflicts: \ # N: attr: expected "int", got "str" fun_p(C()) # OK -[builtins fixtures/list.pyi] +[builtins fixtures/property.pyi] [case testImplicitTypesInProtocols] from typing import Protocol diff --git a/test-data/unit/fixtures/property.pyi b/test-data/unit/fixtures/property.pyi index 01394ff5d9e8a..7187181a9bcee 100644 --- a/test-data/unit/fixtures/property.pyi +++ b/test-data/unit/fixtures/property.pyi @@ -1,4 +1,4 @@ -from typing import Any, Callable, Generic, TypeVar +from typing import Any, Callable, Generic, TypeVar, Sequence _T = TypeVar('_T') @@ -31,8 +31,10 @@ class property(object): class dict: pass class int: pass class str: pass +class float: pass class bytes: pass class bool: pass class ellipsis: pass +class list(Sequence[_T]): pass class tuple(Generic[_T]): pass