pylint-dev / astroid

A common base representation of python source code for pylint and other projects
https://pylint.readthedocs.io/projects/astroid/en/latest/
GNU Lesser General Public License v2.1
533 stars 276 forks source link

Only first __getitem__ inference result is used #1335

Open eugene57 opened 2 years ago

eugene57 commented 2 years ago

Steps to reproduce

import astroid

code = """
class Test:
  def __getitem__(self, key):
    if key == 1:
      return 42
    else:
      return None

x = Test()
result = x[42]  #@
"""

node = astroid.extract_node(code)
x = node.value.inferred()
print(x)

Current behavior

Only int is inferred:

[<Const.int l.5 at 0x7fffe8958640>]

Expected behavior

I expect to see both int and None as possible values.

python -c "from astroid import __pkginfo__; print(__pkginfo__.version)" output

2.7.3

DanielNoord commented 2 years ago

I don't think this is a bug. node.value is actually referring to the Subscript node which is 42 and thus an int. See:

❯ cat test.py
import astroid

code = """
class Test:
  def __getitem__(self, key):
    if key == 1:
      return 42
    else:
      return None

x = Test()
result = x[42]  #@
"""

node = astroid.extract_node(code)
x = node.value.inferred()
print(type(node.value))
print(type(x[0]))
print(x[0].value)
❯ python3 test.py
<class 'astroid.nodes.node_classes.Subscript'>
<class 'astroid.nodes.node_classes.Const'>
42
eugene57 commented 2 years ago

No-no. Actually, node.value is x[42] as you can see below:

print(node.value.repr_tree())
Subscript(
   ctx=<Context.Load: 1>,
   value=Name(name='x'),
   slice=Index(value=Const(value=42)))

Inference of x[42] checks call result of Test.__getitem__ but uses only first inferred value, not all the possible ones. See https://github.com/PyCQA/astroid/blob/main/astroid/nodes/scoped_nodes/scoped_nodes.py#L2926.

Maybe example would be a bit simpler if we use result = x["abc"] instead of result = x[42]. This way:

import astroid

code = """
class Test:
  def __getitem__(self, key):
    if key == 1:
      return 42
    else:
      return None

x = Test()
result = x["abc"]  #@
"""

node = astroid.extract_node(code)
x = node.value.inferred()
print(x)

It still prints [<Const.int l.5 at 0x7fd3ae65fad0>] --- first inference result of Test.__getitem__ call.

DanielNoord commented 2 years ago

Ah my bad, I misinterpreted the result.

This will be difficult to fix though, as we can't simply change getitem to return a list or tuple instead of a single node.. Not sure what the best way to approach this is.

jacobtylerwalls commented 7 months ago

This will be difficult to fix though, as we can't simply change getitem to return a list or tuple instead of a single node..

I think that's exactly what we need to do.