Repository: pylint-dev/astroid

Issue:
infer_stmts cannot infer multiple uses of the same AssignName
Given multiple assignments to the same target which both reference the same AssignName, infer_stmts fails for subsequent attempts after the first.

### Steps to reproduce

This appears to be a minimum working example, removing any part removes the effect:

```python
fails = astroid.extract_node("""
    pair = [1, 2]
    ex = pair[0]
    if 1 + 1 == 2:
        ex = pair[1]
    ex
""")
print(list(fails.infer()))
# [<Const.int l.2 at 0x...>, Uninferable]
```

For some context, I originally saw this with attributes on an imported module, i.e.

```python
import mod
ex = mod.One()
# later ... or in some branch
ex = mod.Two()
```

### Current behavior

See above.

### Expected behavior

Inlining the variable or switching to a different name works fine:

```python
works = astroid.extract_node("""
    # pair = [1, 2]
    ex = [1, 2][0]
    if 1 + 1 == 2:
        ex = [1, 2][1]
    ex
""")
print(list(works.infer()))
# [<Const.int l.3 at 0x...>, <Const.int l.5 at 0x...>]

works = astroid.extract_node("""
    first = [1, 2]
    second = [1, 2]
    ex = first[0]
    if 1 + 1 == 2:
        ex = second[1]
    ex
""")
print(list(works.infer()))
# [<Const.int l.2 at 0x...>, <Const.int l.3 at 0x...>]
```

I would expect that the first failing example would work similarly. This (only) worked
in astroid 2.5 and appears to have been "broken" by the revert of cc3bfc5 in 03d15b0 (astroid 2.5.1 and above).

### ``python -c "from astroid import __pkginfo__; print(__pkginfo__.version)"`` output

```
$ python -c "from astroid import __pkginfo__; print(__pkginfo__.version)"
2.5-dev
$ git rev-parse HEAD
03d15b0f32f7d7c9b2cb062b9321e531bd954344
```



Discussion:
It appears that this is caused by `InferenceContext` maintaining a strong reference to the mutable set that is shared between clones, see this simplified example:

```python
class Context:
    def __init__(self, path=None):
        self.path = path or set()
    def clone(self):
        return Context(path=self.path)

a = Context()
a.path.add('hello')
b = a.clone()
b.path.add('world')
print(a.path, b.path)
# (set(['world', 'hello']), set(['world', 'hello']))
print(a.path is b.path)
# True
```

Patch:
diff --git a/astroid/context.py b/astroid/context.py
--- a/astroid/context.py
+++ b/astroid/context.py
@@ -102,7 +102,7 @@ def clone(self):
         starts with the same context but diverge as each side is inferred
         so the InferenceContext will need be cloned"""
         # XXX copy lookupname/callcontext ?
-        clone = InferenceContext(self.path, inferred=self.inferred)
+        clone = InferenceContext(self.path.copy(), inferred=self.inferred.copy())
         clone.callcontext = self.callcontext
         clone.boundnode = self.boundnode
         clone.extra_context = self.extra_context


Test patch:
diff --git a/tests/unittest_brain_numpy_core_umath.py b/tests/unittest_brain_numpy_core_umath.py
--- a/tests/unittest_brain_numpy_core_umath.py
+++ b/tests/unittest_brain_numpy_core_umath.py
@@ -14,7 +14,7 @@
 except ImportError:
     HAS_NUMPY = False
 
-from astroid import bases, builder, nodes, util
+from astroid import bases, builder, nodes
 
 
 @unittest.skipUnless(HAS_NUMPY, "This test requires the numpy library.")
@@ -220,9 +220,7 @@ def test_numpy_core_umath_functions_return_type(self):
             with self.subTest(typ=func_):
                 inferred_values = list(self._inferred_numpy_func_call(func_))
                 self.assertTrue(
-                    len(inferred_values) == 1
-                    or len(inferred_values) == 2
-                    and inferred_values[-1].pytype() is util.Uninferable,
+                    len(inferred_values) == 1,
                     msg="Too much inferred values ({}) for {:s}".format(
                         inferred_values[-1].pytype(), func_
                     ),
diff --git a/tests/unittest_inference.py b/tests/unittest_inference.py
--- a/tests/unittest_inference.py
+++ b/tests/unittest_inference.py
@@ -1704,7 +1704,8 @@ def __init__(self):
         """
         ast = extract_node(code, __name__)
         expr = ast.func.expr
-        self.assertIs(next(expr.infer()), util.Uninferable)
+        with pytest.raises(exceptions.InferenceError):
+            next(expr.infer())
 
     def test_tuple_builtin_inference(self):
         code = """
@@ -6032,5 +6033,37 @@ def test_infer_list_of_uninferables_does_not_crash():
     assert not inferred.elts
 
 
+# https://github.com/PyCQA/astroid/issues/926
+def test_issue926_infer_stmts_referencing_same_name_is_not_uninferable():
+    code = """
+    pair = [1, 2]
+    ex = pair[0]
+    if 1 + 1 == 2:
+        ex = pair[1]
+    ex
+    """
+    node = extract_node(code)
+    inferred = list(node.infer())
+    assert len(inferred) == 2
+    assert isinstance(inferred[0], nodes.Const)
+    assert inferred[0].value == 1
+    assert isinstance(inferred[1], nodes.Const)
+    assert inferred[1].value == 2
+
+
+# https://github.com/PyCQA/astroid/issues/926
+def test_issue926_binop_referencing_same_name_is_not_uninferable():
+    code = """
+    pair = [1, 2]
+    ex = pair[0] + pair[1]
+    ex
+    """
+    node = extract_node(code)
+    inferred = list(node.infer())
+    assert len(inferred) == 1
+    assert isinstance(inferred[0], nodes.Const)
+    assert inferred[0].value == 3
+
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/tests/unittest_regrtest.py b/tests/unittest_regrtest.py
--- a/tests/unittest_regrtest.py
+++ b/tests/unittest_regrtest.py
@@ -99,7 +99,7 @@ def test_numpy_crash(self):
         astroid = builder.string_build(data, __name__, __file__)
         callfunc = astroid.body[1].value.func
         inferred = callfunc.inferred()
-        self.assertEqual(len(inferred), 2)
+        self.assertEqual(len(inferred), 1)
 
     def test_nameconstant(self):
         # used to fail for Python 3.4