diff options
| author | Claudiu Popa <pcmanticore@gmail.com> | 2020-03-13 11:52:11 +0100 |
|---|---|---|
| committer | Claudiu Popa <pcmanticore@gmail.com> | 2020-03-13 11:52:11 +0100 |
| commit | ab9d147d71c3dff39972b7d2173aa66a616ff5b7 (patch) | |
| tree | 2c90deaa0c137d1f01240b061ee1a0ed0adf92f4 /astroid | |
| parent | 252dd194944c0a4d64678d4ea8cc69c412b7a640 (diff) | |
| download | astroid-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.py | 18 |
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, |
