summaryrefslogtreecommitdiff
path: root/astroid
diff options
context:
space:
mode:
authorClaudiu Popa <pcmanticore@gmail.com>2020-03-13 11:52:11 +0100
committerClaudiu Popa <pcmanticore@gmail.com>2020-03-13 11:52:11 +0100
commitab9d147d71c3dff39972b7d2173aa66a616ff5b7 (patch)
tree2c90deaa0c137d1f01240b061ee1a0ed0adf92f4 /astroid
parent252dd194944c0a4d64678d4ea8cc69c412b7a640 (diff)
downloadastroid-git-ab9d147d71c3dff39972b7d2173aa66a616ff5b7.tar.gz
Allow slots added dynamically to a class to still be inferred
In 2aa27e9aed6ffcba4a61655e291e852ecd001549 `ClassDef.igetattr` was modified to only grab the first item from the result of `getattr`, in order to avoid looking up attributes in the ancestors path when inferring attributes for a given class. This had the side effect that we'd omit attribute definitions happening in the same scope, such as augmented assignments, which in turn might have affected other capabilities, such as slots inference. This commit changes the approach a bit and keeps all attributes as long as all of them are from the same class (be it current or an ancestor) Close PyCQA/pylint#2334
Diffstat (limited to 'astroid')
-rw-r--r--astroid/scoped_nodes.py18
1 files changed, 15 insertions, 3 deletions
diff --git a/astroid/scoped_nodes.py b/astroid/scoped_nodes.py
index 4364aa6b..6b0e8dd5 100644
--- a/astroid/scoped_nodes.py
+++ b/astroid/scoped_nodes.py
@@ -2514,8 +2514,20 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, node_classes.Statement
metaclass = self.declared_metaclass(context=context)
try:
- attr = self.getattr(name, context, class_context=class_context)[0]
- for inferred in bases._infer_stmts([attr], context, frame=self):
+ attributes = self.getattr(name, context, class_context=class_context)
+ # If we have more than one attribute, make sure that those starting from
+ # the second one are from the same scope. This is to account for modifications
+ # to the attribute happening *after* the attribute's definition (e.g. AugAssigns on lists)
+ if len(attributes) > 1:
+ first_attr, attributes = attributes[0], attributes[1:]
+ first_scope = first_attr.scope()
+ attributes = [first_attr] + [
+ attr
+ for attr in attributes
+ if attr.parent and attr.parent.scope() == first_scope
+ ]
+
+ for inferred in bases._infer_stmts(attributes, context, frame=self):
# yield Uninferable object instead of descriptors when necessary
if not isinstance(inferred, node_classes.Const) and isinstance(
inferred, bases.Instance
@@ -2815,7 +2827,7 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, node_classes.Statement
if not all(slot is not None for slot in slots):
return None
- return sorted(slots, key=lambda item: item.value)
+ return sorted(set(slots), key=lambda item: item.value)
def _inferred_bases(self, context=None):
# Similar with .ancestors, but the difference is when one base is inferred,