Skip to content

Commit

Permalink
Fix getattr inference with empty annotation assignments (#1834)
Browse files Browse the repository at this point in the history
  • Loading branch information
cdce8p authored and Pierre-Sassoulas committed Oct 19, 2022
1 parent a0ee56c commit 19623e7
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 17 deletions.
8 changes: 6 additions & 2 deletions ChangeLog
Expand Up @@ -20,8 +20,6 @@ What's New in astroid 2.12.12?
==============================
Release date: TBA



* Add the ``length`` parameter to ``hash.digest`` & ``hash.hexdigest`` in the ``hashlib`` brain.

Refs PyCQA/pylint#4039
Expand All @@ -30,6 +28,12 @@ Release date: TBA

Refs PyCQA/pylint#7592

* Fix inferring attributes with empty annotation assignments if parent
class contains valid assignment.

Refs PyCQA/pylint#7631


What's New in astroid 2.12.11?
==============================
Release date: 2022-10-10
Expand Down
35 changes: 20 additions & 15 deletions astroid/nodes/scoped_nodes/scoped_nodes.py
Expand Up @@ -46,6 +46,7 @@
from astroid.nodes.scoped_nodes.mixin import ComprehensionScope, LocalsDictNodeNG
from astroid.nodes.scoped_nodes.utils import builtin_lookup
from astroid.nodes.utils import Position
from astroid.typing import InferenceResult

if sys.version_info >= (3, 8):
from functools import cached_property
Expand Down Expand Up @@ -2519,7 +2520,12 @@ def instantiate_class(self) -> bases.Instance:
pass
return bases.Instance(self)

def getattr(self, name, context=None, class_context=True):
def getattr(
self,
name: str,
context: InferenceContext | None = None,
class_context: bool = True,
) -> list[NodeNG]:
"""Get an attribute from this class, using Python's attribute semantic.
This method doesn't look in the :attr:`instance_attrs` dictionary
Expand All @@ -2535,13 +2541,10 @@ def getattr(self, name, context=None, class_context=True):
metaclass will be done.
:param name: The attribute to look for.
:type name: str
:param class_context: Whether the attribute can be accessed statically.
:type class_context: bool
:returns: The attribute.
:rtype: list(NodeNG)
:raises AttributeInferenceError: If the attribute cannot be inferred.
"""
Expand All @@ -2564,17 +2567,16 @@ def getattr(self, name, context=None, class_context=True):
if class_context:
values += self._metaclass_lookup_attribute(name, context)

if not values:
raise AttributeInferenceError(target=self, attribute=name, context=context)

# Look for AnnAssigns, which are not attributes in the purest sense.
for value in values:
# Remove AnnAssigns without value, which are not attributes in the purest sense.
for value in values.copy():
if isinstance(value, node_classes.AssignName):
stmt = value.statement(future=True)
if isinstance(stmt, node_classes.AnnAssign) and stmt.value is None:
raise AttributeInferenceError(
target=self, attribute=name, context=context
)
values.pop(values.index(value))

if not values:
raise AttributeInferenceError(target=self, attribute=name, context=context)

return values

def _metaclass_lookup_attribute(self, name, context):
Expand Down Expand Up @@ -2616,14 +2618,17 @@ def _get_attribute_from_metaclass(self, cls, name, context):
else:
yield bases.BoundMethod(attr, self)

def igetattr(self, name, context=None, class_context=True):
def igetattr(
self,
name: str,
context: InferenceContext | None = None,
class_context: bool = True,
) -> Iterator[InferenceResult]:
"""Infer the possible values of the given variable.
:param name: The name of the variable to infer.
:type name: str
:returns: The inferred possible values.
:rtype: iterable(NodeNG or Uninferable)
"""
# set lookup name since this is necessary to infer on import nodes for
# instance
Expand Down
16 changes: 16 additions & 0 deletions tests/unittest_scoped_nodes.py
Expand Up @@ -1268,6 +1268,22 @@ class Past(Present):
self.assertIsInstance(attr1, nodes.AssignName)
self.assertEqual(attr1.name, "attr")

@staticmethod
def test_getattr_with_enpty_annassign() -> None:
code = """
class Parent:
attr: int = 2
class Child(Parent): #@
attr: int
"""
child = extract_node(code)
attr = child.getattr("attr")
assert len(attr) == 1
assert isinstance(attr[0], nodes.AssignName)
assert attr[0].name == "attr"
assert attr[0].lineno == 3

def test_function_with_decorator_lineno(self) -> None:
data = """
@f(a=2,
Expand Down

0 comments on commit 19623e7

Please sign in to comment.