Skip to content

Commit 643d548

Browse files
committed
Add tests of failing behaviour of some property inference
Ref #940. Would cause `Enum.__members__` to fail for enum classes defined as subclasses of `enum.Enum`
1 parent f2b197a commit 643d548

File tree

2 files changed

+114
-4
lines changed

2 files changed

+114
-4
lines changed

astroid/scoped_nodes.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2560,7 +2560,7 @@ def igetattr(self, name, context=None, class_context=True):
25602560
context = contextmod.copy_context(context)
25612561
context.lookupname = name
25622562

2563-
metaclass = self.declared_metaclass(context=context)
2563+
metaclass = self.metaclass(context=context)
25642564
try:
25652565
attributes = self.getattr(name, context, class_context=class_context)
25662566
# If we have more than one attribute, make sure that those starting from
@@ -2593,9 +2593,11 @@ def igetattr(self, name, context=None, class_context=True):
25932593
yield from function.infer_call_result(
25942594
caller=self, context=context
25952595
)
2596-
# If we have a metaclass, we're accessing this attribute through
2597-
# the class itself, which means we can solve the property
2598-
elif metaclass:
2596+
# If we're in a class context, we need to determine if the property
2597+
# was defined in the metaclass (a derived class must be a subclass of the metaclass
2598+
# of all its bases), in which case we can resolve the property. If not, i.e. the
2599+
# property is defined in some base class instead, then we return the property object
2600+
elif metaclass and function.parent.scope() is metaclass:
25992601
# Resolve a property as long as it is not accessed through
26002602
# the class itself.
26012603
yield from function.infer_call_result(

tests/unittest_scoped_nodes.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1923,6 +1923,114 @@ def update(self):
19231923
builder.parse(data)
19241924

19251925

1926+
def test_issue940_metaclass_subclass_property():
1927+
node = builder.extract_node(
1928+
"""
1929+
class BaseMeta(type):
1930+
@property
1931+
def __members__(cls):
1932+
return ['a', 'property']
1933+
class Parent(metaclass=BaseMeta):
1934+
pass
1935+
class Derived(Parent):
1936+
pass
1937+
Derived.__members__
1938+
"""
1939+
)
1940+
inferred = next(node.infer())
1941+
assert isinstance(inferred, nodes.List)
1942+
assert [c.value for c in inferred.elts] == ['a', 'property']
1943+
1944+
1945+
def test_issue940_property_grandchild():
1946+
node = builder.extract_node(
1947+
"""
1948+
class Grandparent:
1949+
@property
1950+
def __members__(self):
1951+
return ['a', 'property']
1952+
class Parent(Grandparent):
1953+
pass
1954+
class Child(Parent):
1955+
pass
1956+
Child().__members__
1957+
"""
1958+
)
1959+
inferred = next(node.infer())
1960+
assert isinstance(inferred, nodes.List)
1961+
assert [c.value for c in inferred.elts] == ['a', 'property']
1962+
1963+
1964+
def test_issue940_metaclass_property():
1965+
node = builder.extract_node(
1966+
"""
1967+
class BaseMeta(type):
1968+
@property
1969+
def __members__(cls):
1970+
return ['a', 'property']
1971+
class Parent(metaclass=BaseMeta):
1972+
pass
1973+
Parent.__members__
1974+
"""
1975+
)
1976+
inferred = next(node.infer())
1977+
assert isinstance(inferred, nodes.List)
1978+
assert [c.value for c in inferred.elts] == ['a', 'property']
1979+
1980+
1981+
def test_issue940_with_metaclass_class_context_property():
1982+
node = builder.extract_node(
1983+
"""
1984+
class BaseMeta(type):
1985+
pass
1986+
class Parent(metaclass=BaseMeta):
1987+
@property
1988+
def __members__(cls):
1989+
return ['a', 'property']
1990+
class Derived(Parent):
1991+
pass
1992+
Derived.__members__
1993+
"""
1994+
)
1995+
inferred = next(node.infer())
1996+
assert not isinstance(inferred, nodes.List)
1997+
assert isinstance(inferred, objects.Property)
1998+
1999+
2000+
def test_issue940_metaclass_values_funcdef():
2001+
node = builder.extract_node(
2002+
"""
2003+
class BaseMeta(type):
2004+
def __members__(cls):
2005+
return ['a', 'func']
2006+
class Parent(metaclass=BaseMeta):
2007+
pass
2008+
Parent.__members__()
2009+
"""
2010+
)
2011+
inferred = next(node.infer())
2012+
assert isinstance(inferred, nodes.List)
2013+
assert [c.value for c in inferred.elts] == ['a', 'func']
2014+
2015+
2016+
def test_issue940_metaclass_derived_funcdef():
2017+
node = builder.extract_node(
2018+
"""
2019+
class BaseMeta(type):
2020+
def __members__(cls):
2021+
return ['a', 'func']
2022+
class Parent(metaclass=BaseMeta):
2023+
pass
2024+
class Derived(Parent):
2025+
pass
2026+
Derived.__members__()
2027+
"""
2028+
)
2029+
inferred_result = next(node.infer())
2030+
assert isinstance(inferred_result, nodes.List)
2031+
assert [c.value for c in inferred_result.elts] == ['a', 'func']
2032+
2033+
19262034
def test_metaclass_cannot_infer_call_yields_an_instance():
19272035
node = builder.extract_node(
19282036
"""

0 commit comments

Comments
 (0)