-
Notifications
You must be signed in to change notification settings - Fork 288
Labels
needs-discussionAn issue where it's not clear whether there is a bug or we are behaving as expected.An issue where it's not clear whether there is a bug or we are behaving as expected.typecheckingusabilityUsability & readiness issues identified with running Pyrefly on top OSS projectsUsability & readiness issues identified with running Pyrefly on top OSS projectsv1-verifiedIn both V1 milestone and top-ranked (verified by ranking pipeline)In both V1 milestone and top-ranked (verified by ranking pipeline)
Milestone
Description
Describe the Bug
Pyrefly reports "Decorator @x can only be used on methods" when @property, @classmethod, or @staticmethod are applied to functions outside a class body. This is valid Python -- these descriptors work correctly when the resulting objects are later assigned to class attributes.
Real-world: Django uses this pattern extensively in ModelAdmin and FormSet classes. Libraries like attrs and SQLAlchemy also create properties outside class bodies via factory functions.
# Pattern 1: @property factory function
#
# A helper creates a property descriptor, which is later assigned to a class.
# This is common when multiple classes share the same computed attributes.
def make_property(attr_name: str):
@property
def prop(self):
return getattr(self, "_" + attr_name)
return prop
class Point:
def __init__(self, x: int, y: int):
self._x = x
self._y = y
x = make_property("x")
y = make_property("y")
p = Point(1, 2)
assert p.x == 1
assert p.y == 2
# Pattern 2: @staticmethod outside class body
#
# A utility function decorated with @staticmethod at module level,
# then assigned as a class attribute.
@staticmethod
def utility(x: int) -> int:
return x * 2
class Helper:
compute = utility
result = Helper.compute(5)
# Pattern 3: @classmethod outside class body
#
# A classmethod created at module level, then assigned to a class.
@classmethod
def from_value(cls, val: int) -> "Container":
obj = cls()
obj.value = val
return obj
class Container:
value: int = 0
from_value = from_value # type: ignore[assignment]
c = Container.from_value(42)The easiest way to address this issue might be to lower the default severity of the problem?
Sandbox Link
No response
(Only applicable for extension issues) IDE Information
No response
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
needs-discussionAn issue where it's not clear whether there is a bug or we are behaving as expected.An issue where it's not clear whether there is a bug or we are behaving as expected.typecheckingusabilityUsability & readiness issues identified with running Pyrefly on top OSS projectsUsability & readiness issues identified with running Pyrefly on top OSS projectsv1-verifiedIn both V1 milestone and top-ranked (verified by ranking pipeline)In both V1 milestone and top-ranked (verified by ranking pipeline)