Skip to content

Commit 5ed2c4c

Browse files
committed
Cherry-pick first group of changes from python#18504
1 parent 1ea9373 commit 5ed2c4c

File tree

9 files changed

+148
-20
lines changed

9 files changed

+148
-20
lines changed

mypy/checker.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@
174174
ANY_STRATEGY,
175175
MYPYC_NATIVE_INT_NAMES,
176176
OVERLOAD_NAMES,
177+
PROPERTY_DECORATOR_NAMES,
177178
AnyType,
178179
BoolTypeQuery,
179180
CallableType,
@@ -2318,6 +2319,20 @@ def check_method_override_for_base_with_name(
23182319
)
23192320
return False
23202321

2322+
def get_property_instance(self, method: Decorator) -> Instance | None:
2323+
property_deco_name = next(
2324+
(
2325+
name
2326+
for d in method.original_decorators
2327+
for name in PROPERTY_DECORATOR_NAMES
2328+
if refers_to_fullname(d, name)
2329+
),
2330+
None,
2331+
)
2332+
if property_deco_name is not None:
2333+
return self.named_type(property_deco_name)
2334+
return None
2335+
23212336
def get_op_other_domain(self, tp: FunctionLike) -> Type | None:
23222337
if isinstance(tp, CallableType):
23232338
if tp.arg_kinds and tp.arg_kinds[0] == ARG_POS:

mypy/checkexpr.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -381,12 +381,15 @@ def analyze_ref_expr(self, e: RefExpr, lvalue: bool = False) -> Type:
381381
# Reference to a global function.
382382
result = function_type(node, self.named_type("builtins.function"))
383383
elif isinstance(node, OverloadedFuncDef):
384+
result = node.type
384385
if node.type is None:
385386
if self.chk.in_checked_function() and node.items:
386387
self.chk.handle_cannot_determine_type(node.name, e)
387388
result = AnyType(TypeOfAny.from_error)
388-
else:
389-
result = node.type
389+
elif isinstance(node.items[0], Decorator):
390+
property_type = self.chk.get_property_instance(node.items[0])
391+
if property_type is not None:
392+
result = property_type
390393
elif isinstance(node, TypeInfo):
391394
# Reference to a type object.
392395
if node.typeddict_type:
@@ -412,7 +415,11 @@ def analyze_ref_expr(self, e: RefExpr, lvalue: bool = False) -> Type:
412415
# Reference to a module object.
413416
result = self.module_type(node)
414417
elif isinstance(node, Decorator):
415-
result = self.analyze_var_ref(node.var, e)
418+
property_type = self.chk.get_property_instance(node)
419+
if property_type is not None:
420+
result = property_type
421+
else:
422+
result = self.analyze_var_ref(node.var, e)
416423
elif isinstance(node, TypeAlias):
417424
# Something that refers to a type alias appears in runtime context.
418425
# Note that we suppress bogus errors for alias redefinitions,

mypy/checkmember.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,6 +1194,16 @@ def analyze_class_attribute_access(
11941194
# C[int].x -> int
11951195
t = erase_typevars(expand_type_by_instance(t, isuper), {tv.id for tv in def_vars})
11961196

1197+
if isinstance(node.node, Decorator) and node.node.func.is_property:
1198+
property_type = mx.chk.get_property_instance(node.node)
1199+
if property_type is not None:
1200+
return property_type
1201+
if isinstance(node.node, OverloadedFuncDef) and node.node.is_property:
1202+
assert isinstance(node.node.items[0], Decorator)
1203+
property_type = mx.chk.get_property_instance(node.node.items[0])
1204+
if property_type is not None:
1205+
return property_type
1206+
11971207
is_classmethod = (is_decorated and cast(Decorator, node.node).func.is_class) or (
11981208
isinstance(node.node, SYMBOL_FUNCBASE_TYPES) and node.node.is_class
11991209
)

mypy/semanal.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@
262262
NEVER_NAMES,
263263
OVERLOAD_NAMES,
264264
OVERRIDE_DECORATOR_NAMES,
265+
PROPERTY_DECORATOR_NAMES,
265266
PROTOCOL_NAMES,
266267
REVEAL_TYPE_NAMES,
267268
TPDICT_NAMES,
@@ -1687,16 +1688,7 @@ def visit_decorator(self, dec: Decorator) -> None:
16871688
removed.append(i)
16881689
dec.func.is_explicit_override = True
16891690
self.check_decorated_function_is_method("override", dec)
1690-
elif refers_to_fullname(
1691-
d,
1692-
(
1693-
"builtins.property",
1694-
"abc.abstractproperty",
1695-
"functools.cached_property",
1696-
"enum.property",
1697-
"types.DynamicClassAttribute",
1698-
),
1699-
):
1691+
elif refers_to_fullname(d, PROPERTY_DECORATOR_NAMES):
17001692
removed.append(i)
17011693
dec.func.is_property = True
17021694
dec.var.is_property = True

mypy/types.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,15 @@
178178
# Supported @override decorator names.
179179
OVERRIDE_DECORATOR_NAMES: Final = ("typing.override", "typing_extensions.override")
180180

181+
# Supported property decorators
182+
PROPERTY_DECORATOR_NAMES: Final = (
183+
"builtins.property",
184+
"abc.abstractproperty",
185+
"functools.cached_property",
186+
"enum.property",
187+
"types.DynamicClassAttribute",
188+
)
189+
181190
# A placeholder used for Bogus[...] parameters
182191
_dummy: Final[Any] = object()
183192

test-data/unit/check-classes.test

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1621,8 +1621,7 @@ class A:
16211621
self.x = 1
16221622
self.x = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int")
16231623
return ''
1624-
[builtins fixtures/property.pyi]
1625-
[out]
1624+
[builtins fixtures/property-full.pyi]
16261625

16271626
[case testDynamicallyTypedProperty]
16281627
import typing
@@ -1632,7 +1631,7 @@ class A:
16321631
a = A()
16331632
a.f.xx
16341633
a.f = '' # E: Property "f" defined in "A" is read-only
1635-
[builtins fixtures/property.pyi]
1634+
[builtins fixtures/property-full.pyi]
16361635

16371636
[case testPropertyWithSetter]
16381637
import typing
@@ -1649,7 +1648,7 @@ a.f.x # E: "int" has no attribute "x"
16491648
a.f = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int")
16501649
a.f = 1
16511650
reveal_type(a.f) # N: Revealed type is "builtins.int"
1652-
[builtins fixtures/property.pyi]
1651+
[builtins fixtures/property-full.pyi]
16531652

16541653
[case testPropertyWithDeleterButNoSetter]
16551654
import typing
@@ -1663,7 +1662,60 @@ class A:
16631662
a = A()
16641663
a.f = a.f # E: Property "f" defined in "A" is read-only
16651664
a.f.x # E: "int" has no attribute "x"
1666-
[builtins fixtures/property.pyi]
1665+
[builtins fixtures/property-full.pyi]
1666+
1667+
[case testPropertyAccessOnClass]
1668+
class Foo:
1669+
@property
1670+
def bar(self) -> bool:
1671+
return True
1672+
1673+
reveal_type(bar) # N: Revealed type is "builtins.property"
1674+
1675+
reveal_type(Foo.bar) # N: Revealed type is "builtins.property"
1676+
reveal_type(Foo.bar(Foo())) # E: "property" not callable \
1677+
# N: Revealed type is "Any"
1678+
reveal_type(Foo.bar.fget(Foo())) # E: "None" not callable \
1679+
# N: Revealed type is "Any"
1680+
1681+
class Bar:
1682+
@property
1683+
def bar(self) -> bool:
1684+
return True
1685+
@bar.setter
1686+
def bar(self, bar: bool) -> None:
1687+
pass
1688+
1689+
reveal_type(bar) # N: Revealed type is "builtins.property"
1690+
1691+
reveal_type(Bar.bar) # N: Revealed type is "builtins.property"
1692+
reveal_type(Bar.bar(Bar())) # E: "property" not callable \
1693+
# N: Revealed type is "Any"
1694+
reveal_type(Bar.bar.fget(Bar())) # E: "None" not callable \
1695+
# N: Revealed type is "Any"
1696+
[builtins fixtures/property-full.pyi]
1697+
1698+
[case testPropertyAccessOnClass2]
1699+
import functools
1700+
from functools import cached_property
1701+
1702+
class Foo:
1703+
@cached_property
1704+
def foo(self) -> bool:
1705+
return True
1706+
1707+
@functools.cached_property
1708+
def bar(self) -> bool:
1709+
return True
1710+
1711+
reveal_type(foo) # N: Revealed type is "functools.cached_property[Any]"
1712+
reveal_type(bar) # N: Revealed type is "functools.cached_property[Any]"
1713+
1714+
reveal_type(Foo.foo) # N: Revealed type is "functools.cached_property[Any]"
1715+
reveal_type(Foo.bar) # N: Revealed type is "functools.cached_property[Any]"
1716+
Foo.foo(Foo()) # E: "cached_property[Any]" not callable
1717+
Foo.bar(Foo()) # E: "cached_property[Any]" not callable
1718+
[builtins fixtures/property-full.pyi]
16671719

16681720
-- Descriptors
16691721
-- -----------

test-data/unit/check-functions.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3298,7 +3298,7 @@ class A:
32983298
@decorator
32993299
def f(self) -> int: ...
33003300

3301-
reveal_type(A.f) # N: Revealed type is "__main__.something_callable"
3301+
reveal_type(A.f) # N: Revealed type is "builtins.property"
33023302
reveal_type(A().f) # N: Revealed type is "builtins.str"
33033303
[builtins fixtures/property.pyi]
33043304

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from typing import Any, Callable, Generic, TypeVar
2+
3+
_T = TypeVar('_T')
4+
5+
class object:
6+
def __init__(self) -> None: pass
7+
8+
class type:
9+
def __init__(self, x: Any) -> None: pass
10+
11+
class function: pass
12+
13+
class property:
14+
fget: Callable[[Any], Any] | None
15+
fset: Callable[[Any, Any], None] | None
16+
fdel: Callable[[Any], None] | None
17+
__isabstractmethod__: bool
18+
19+
def __init__(
20+
self,
21+
fget: Callable[[Any], Any] | None = ...,
22+
fset: Callable[[Any, Any], None] | None = ...,
23+
fdel: Callable[[Any], None] | None = ...,
24+
doc: str | None = ...,
25+
) -> None: ...
26+
def getter(self, fget: Callable[[Any], Any], /) -> property: ...
27+
def setter(self, fset: Callable[[Any, Any], None], /) -> property: ...
28+
def deleter(self, fdel: Callable[[Any], None], /) -> property: ...
29+
def __get__(self, instance: Any, owner: type | None = None, /) -> Any: ...
30+
def __set__(self, instance: Any, value: Any, /) -> None: ...
31+
def __delete__(self, instance: Any, /) -> None: ...
32+
class classmethod: pass
33+
34+
class list: pass
35+
class dict: pass
36+
class int: pass
37+
class float: pass
38+
class str: pass
39+
class bytes: pass
40+
class bool: pass
41+
class ellipsis: pass
42+
43+
class tuple(Generic[_T]): pass

test-data/unit/fixtures/property.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class type:
1010

1111
class function: pass
1212

13-
property = object() # Dummy definition
13+
class property: pass # Dummy definition
1414
class classmethod: pass
1515

1616
class list(typing.Generic[_T]): pass

0 commit comments

Comments
 (0)