copy context path when cloning inference context, else we may skip undesired inference branches. closes #77187

authorSylvain Thénault <sylvain.thenault@logilab.fr>
changesetae207dfe2aa0
branchdefault
phasepublic
hiddenno
parent revision#579e68649037 closes #77188: support lgc.decorators.classproperty
child revision#e35543060167 closes #77253: provide a way for user code to register astng transformers
files modified by this revision
ChangeLog
bases.py
scoped_nodes.py
test/unittest_inference.py
# HG changeset patch
# User Sylvain Thénault <sylvain.thenault@logilab.fr>
# Date 1317298425 -7200
# Thu Sep 29 14:13:45 2011 +0200
# Node ID ae207dfe2aa02514d7e9635e0db33ad4a49442a2
# Parent 579e686490372724c2c34be27db2014b577ab3c8
copy context path when cloning inference context, else we may skip undesired inference branches. closes #77187

diff --git a/ChangeLog b/ChangeLog
@@ -1,9 +1,12 @@
1  Change log for the astng package
2  ================================
3 
4  --
5 +    * #77187: ancestor() only returns the first class when inheriting
6 +      from two classes coming from the same module
7 +
8      * #76159: putting module's parent directory on the path causes problems
9        linting when file names clash
10 
11      * #74746: should return empty module when __main__ is imported (patch by
12        google)
diff --git a/bases.py b/bases.py
@@ -22,10 +22,11 @@
13  inference utils.
14  """
15 
16  __docformat__ = "restructuredtext en"
17 
18 +from contextlib import contextmanager
19 
20  from logilab.common.compat import builtins
21 
22  from logilab.astng import BUILTINS_MODULE
23  from logilab.astng.exceptions import InferenceError, ASTNGError, \
@@ -78,10 +79,16 @@
24          clone = InferenceContext(self.path)
25          clone.callcontext = self.callcontext
26          clone.boundnode = self.boundnode
27          return clone
28 
29 +    @contextmanager
30 +    def restore_path(self):
31 +        path = set(self.path)
32 +        yield
33 +        self.path = path
34 +
35  def copy_context(context):
36      if context is not None:
37          return context.clone()
38      else:
39          return InferenceContext()
diff --git a/scoped_nodes.py b/scoped_nodes.py
@@ -19,10 +19,11 @@
40  # with logilab-astng. If not, see <http://www.gnu.org/licenses/>.
41  """This module contains the classes for "scoped" node, i.e. which are opening a
42  new local scope in the language definition : Module, Class, Function (and
43  Lambda, GenExpr, DictComp and SetComp to some extent).
44  """
45 +from __future__ import with_statement
46 
47  __doctype__ = "restructuredtext en"
48 
49  import sys
50  from itertools import chain
@@ -770,28 +771,29 @@
51          # manipulation in the builder module for instance)
52          yielded = set([self])
53          if context is None:
54              context = InferenceContext()
55          for stmt in self.bases:
56 -            try:
57 -                for baseobj in stmt.infer(context):
58 -                    if not isinstance(baseobj, Class):
59 -                        # duh ?
60 -                        continue
61 -                    if baseobj in yielded:
62 -                        continue # cf xxx above
63 -                    yielded.add(baseobj)
64 -                    yield baseobj
65 -                    if recurs:
66 -                        for grandpa in baseobj.ancestors(True, context):
67 -                            if grandpa in yielded:
68 -                                continue # cf xxx above
69 -                            yielded.add(grandpa)
70 -                            yield grandpa
71 -            except InferenceError:
72 -                # XXX log error ?
73 -                continue
74 +            with context.restore_path():
75 +                try:
76 +                    for baseobj in stmt.infer(context):
77 +                        if not isinstance(baseobj, Class):
78 +                            # duh ?
79 +                            continue
80 +                        if baseobj in yielded:
81 +                            continue # cf xxx above
82 +                        yielded.add(baseobj)
83 +                        yield baseobj
84 +                        if recurs:
85 +                            for grandpa in baseobj.ancestors(True, context):
86 +                                if grandpa in yielded:
87 +                                    continue # cf xxx above
88 +                                yielded.add(grandpa)
89 +                                yield grandpa
90 +                except InferenceError:
91 +                    # XXX log error ?
92 +                    continue
93 
94      def local_attr_ancestors(self, name, context=None):
95          """return an iterator on astng representation of parent classes
96          which have <name> defined in their locals
97          """
diff --git a/test/unittest_inference.py b/test/unittest_inference.py
@@ -1128,7 +1128,18 @@
98          self.assertRaises(InferenceError, list, astng['NewTest'].igetattr('arg'))
99          n = astng['n'].infer().next()
100          infered = list(n.igetattr('arg'))
101          self.assertEqual(len(infered), 1, infered)
102 
103 +
104 +    def test_two_parents_from_same_module(self):
105 +        code = '''
106 +from data import nonregr
107 +class Xxx(nonregr.Aaa, nonregr.Ccc):
108 +    "doc"
109 +        '''
110 +        astng = builder.string_build(code, __name__, __file__)
111 +        parents = list(astng['Xxx'].ancestors())
112 +        self.assertEqual(len(parents), 3, parents) # Aaa, Ccc, object
113 +
114  if __name__ == '__main__':
115      unittest_main()