Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 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
15 changes: 15 additions & 0 deletions mypy/checkmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
is_final_node,
)
from mypy.plugin import AttributeContext
from mypy.semanal import refers_to_fullname
from mypy.typeops import (
bind_self,
class_callable,
Expand All @@ -50,6 +51,7 @@
type_object_type_from_function,
)
from mypy.types import (
PROPERTY_DECORATOR_NAMES,
AnyType,
CallableType,
DeletedType,
Expand Down Expand Up @@ -1110,6 +1112,19 @@ def analyze_class_attribute_access(
# C[int].x -> int
t = erase_typevars(expand_type_by_instance(t, isuper), {tv.id for tv in def_vars})

if is_decorated:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe filter before hand?

Suggested change
if is_decorated:
if is_decorated and node.node.func.is_property:

Not much of a difference though, just means there's no need for handling the no property decorator case.

property_deco_name = next(
(
name
for d in cast(Decorator, node.node).original_decorators
for name in PROPERTY_DECORATOR_NAMES
if refers_to_fullname(d, name)
),
None,
)
if property_deco_name is not None:
return mx.named_type(property_deco_name)

is_classmethod = (is_decorated and cast(Decorator, node.node).func.is_class) or (
isinstance(node.node, FuncBase) and node.node.is_class
)
Expand Down
12 changes: 2 additions & 10 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@
NEVER_NAMES,
OVERLOAD_NAMES,
OVERRIDE_DECORATOR_NAMES,
PROPERTY_DECORATOR_NAMES,
PROTOCOL_NAMES,
REVEAL_TYPE_NAMES,
TPDICT_NAMES,
Expand Down Expand Up @@ -1645,16 +1646,7 @@ def visit_decorator(self, dec: Decorator) -> None:
removed.append(i)
dec.func.is_explicit_override = True
self.check_decorated_function_is_method("override", dec)
elif refers_to_fullname(
d,
(
"builtins.property",
"abc.abstractproperty",
"functools.cached_property",
"enum.property",
"types.DynamicClassAttribute",
),
):
elif refers_to_fullname(d, PROPERTY_DECORATOR_NAMES):
removed.append(i)
dec.func.is_property = True
dec.var.is_property = True
Expand Down
9 changes: 9 additions & 0 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,15 @@
# Supported @override decorator names.
OVERRIDE_DECORATOR_NAMES: Final = ("typing.override", "typing_extensions.override")

# Supported property decorators
PROPERTY_DECORATOR_NAMES: Final = (
"builtins.property",
"abc.abstractproperty",
"functools.cached_property",
"enum.property",
"types.DynamicClassAttribute",
)

# A placeholder used for Bogus[...] parameters
_dummy: Final[Any] = object()

Expand Down
30 changes: 30 additions & 0 deletions test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -1665,6 +1665,36 @@ a.f = a.f # E: Property "f" defined in "A" is read-only
a.f.x # E: "int" has no attribute "x"
[builtins fixtures/property.pyi]

[case testPropertyAccessOnClass]
class Foo:
@property
def bar(self) -> bool:
return True

reveal_type(Foo.bar) # N: Revealed type is "builtins.property"
reveal_type(Foo.bar(Foo())) # E: "property" not callable \
# N: Revealed type is "Any"
[builtins fixtures/property.pyi]

[case testPropertyAccessOnClass2]
import functools
from functools import cached_property

class Foo:
@cached_property
def foo(self) -> bool:
return True

@functools.cached_property
def bar(self) -> bool:
return True

reveal_type(Foo.foo) # N: Revealed type is "functools.cached_property[Any]"
reveal_type(Foo.bar) # N: Revealed type is "functools.cached_property[Any]"
Foo.foo(Foo()) # E: "cached_property[Any]" not callable
Foo.bar(Foo()) # E: "cached_property[Any]" not callable
[builtins fixtures/property.pyi]

-- Descriptors
-- -----------

Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-functions.test
Original file line number Diff line number Diff line change
Expand Up @@ -3276,7 +3276,7 @@ class A:
@decorator
def f(self) -> int: ...

reveal_type(A.f) # N: Revealed type is "__main__.something_callable"
reveal_type(A.f) # N: Revealed type is "builtins.property"
reveal_type(A().f) # N: Revealed type is "builtins.str"
[builtins fixtures/property.pyi]

Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/fixtures/property.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class type:

class function: pass

property = object() # Dummy definition
class property: pass # Dummy definition
class classmethod: pass

class list: pass
Expand Down
Loading