Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Only the first item when unpacking a function return value gets completions #2040

Open
bluthej opened this issue Dec 13, 2024 · 10 comments
Open
Labels

Comments

@bluthej
Copy link
Contributor

bluthej commented Dec 13, 2024

When unpacking the return value of a function which returns a tuple, only the first item of the tuple gets completions.

For example:

import jedi

code = """\
def func() -> tuple[int, int]:
    return 1, 2

a, b = func()
a.
b."""

script = jedi.Script(code, path="example.py")
print(script.complete(5, 2))
print(script.complete(6, 2))

yields a correct completion list for a. but an empty list for b..

Output

Output for a.:

[<Completion: as_integer_ratio>, <Completion: bit_length>, <Completion: conjugate>, <Completion: denominator>, <Completion: from_bytes>, <Completion: imag>, <Completion: numerator>, <Completion: real>, <Completion: to_bytes>, <Completion: __abs__>, <Completion: __add__>, <Completion: __and__>, <Completion: __annotations__>, <Completion: __bool__>, <Completion: __ceil__>, <Completion: __class__>, <Completion: __delattr__>, <Completion: __dict__>, <Completion: __dir__>, <Completion: __divmod__>, <Completion: __doc__>, <Completion: __eq__>, <Completion: __float__>, <Completion: __floor__>, <Completion: __floordiv__>, <Completion: __format__>, <Completion: __ge__>, <Completion: __getattribute__>, <Completion: __getnewargs__>, <Completion: __gt__>, <Completion: __hash__>, <Completion: __index__>, <Completion: __init__>, <Completion: __init_subclass__>, <Completion: __int__>, <Completion: __invert__>, <Completion: __le__>, <Completion: __lshift__>, <Completion: __lt__>, <Completion: __mod__>, <Completion: __module__>, <Completion: __mul__>, <Completion: __ne__>, <Completion: __neg__>, <Completion: __new__>, <Completion: __or__>, <Completion: __pos__>, <Completion: __pow__>, <Completion: __radd__>, <Completion: __rand__>, <Completion: __rdivmod__>, <Completion: __reduce__>, <Completion: __reduce_ex__>, <Completion: __repr__>, <Completion: __rfloordiv__>, <Completion: __rlshift__>, <Completion: __rmod__>, <Completion: __rmul__>, <Completion: __ror__>, <Completion: __round__>, <Completion: __rpow__>, <Completion: __rrshift__>, <Completion: __rshift__>, <Completion: __rsub__>, <Completion: __rtruediv__>, <Completion: __rxor__>, <Completion: __setattr__>, <Completion: __sizeof__>, <Completion: __slots__>, <Completion: __str__>, <Completion: __sub__>, <Completion: __truediv__>, <Completion: __trunc__>, <Completion: __xor__>]

Output for b.:

[]

This sounds a little like #1389, but this issue is marked as solved so it might be something else.

System setup

I am using jedi==0.19.2. I noticed this while using it through pylsp==1.12.0, but as shown above in the example I was able to reproduce it with the jedi library.

Note

It works fine when unpacking a regular tuple:

import jedi

code = """\
a, b = (1, 2)
a.
b."""

script = jedi.Script(code, path="example.py")
print(script.complete(2, 2))
print(script.complete(3, 2))

So it seems this is related to function return types.

@davidhalter
Copy link
Owner

Is this also happening without syntax errors. This means if you write it like a.foo and b.foo?

@bluthej
Copy link
Contributor Author

bluthej commented Dec 13, 2024

Yes it does

@davidhalter
Copy link
Owner

Sorry, but I cannot reproduce that in Python 3.8 and in Python 3.13. Are you on some weird OS or non-standard thing?

You could also try writing a test (see test/completion/... for how to do that) and create a PR, so we can see what the CI thinks in different Python versions/OSes.

@bluthej
Copy link
Contributor Author

bluthej commented Dec 14, 2024

Oh that's interesting! Well I am on Arch Linux, but I don't know how non-standard that is. I just tried a bunch of different stuff and I always get the same result:

  • Create venv with uv for Python 3.13.1 which I built from source, then install jedi 0.19.2 with uv
  • Same thing but with Python 3.8.20 installed with uv
  • Same thing but with my system Python 3.12.7 installed by my package manager (pacman)
  • Then I tried again my system Python 3.12.7 and used pip to install jedi

I also tried the same script in a Windows VM that I have and still got the same result.

When you say you cannot reproduce that, do you mean that both print statements in my example return a non-empty list?

I will try what you suggested to try that in CI.

@PeterJCLaw
Copy link
Collaborator

PeterJCLaw commented Dec 14, 2024

This reproduces for me on 3.8, 3.11 and 3.13 (other version untested) on Ubuntu 22.04.
It's probably something to do with the annotation handling being different to the inferred types handling, given that the inferred approach works but the annotation doesn't (edit: this holds even just removing the annotation from the function and letting jedi infer the return type).

Aside: for some reason I misread the report initially and tried the tuple unpacking first, which then didn't show any issues.

@bluthej
Copy link
Contributor Author

bluthej commented Dec 14, 2024

Yes I confirm that on my end as well removing the annotation on the return type makes the completion work for all the tuple items. I didn't think of trying that because I didn't even know that there was type inference!

Also, I tried that through pylsp and got the same result, I do have completion in my editor for all tuple items when I remove the annotation on the return type.

@davidhalter
Copy link
Owner

Sorry, I guess I copy-pasted the wrong sample... If I had to bet, this has to do with the fact that tuple[...] behaves now like Tuple[...] and we should handle the former case as well.

@bluthej
Copy link
Contributor Author

bluthej commented Dec 21, 2024

So I did some additional testing and I get the exact same behavior with all minor releases until 0.12.0 (I haven't been able to run my example successfully with 0.11.0 because there is a from parso import file_io statement that errors out with the version of parso that gets installed with 0.11.0). So it looks like it has been like this for a while.

I was not familiar with the codebase at all but I tried to dig a little and this led me to the check_tuple_assignment function of jedi.inference.syntax_tree. I noticed that when I run my original example with annotations, the iterated = value_set.iterate(cn) generator only yields 1 value, whereas when I remove the annotation it yields 2 as expected. What's weird is that the value_set looks like this:

S{_GenericInstanceWrapper(<TreeInstance of <GenericClass: <ClassValue: <Class: tuple@671-695>>[S{<ClassValue: <Class: int@164-240>>}, S{<ClassValue: <Class: int@164-240>>}]>(<ValuesArguments: []>)>)}

so it looks like the annotation is in fact correctly identified as a tuple[int, int] and I wonder why value_set.iterate(cn) only yields a single value.

@davidhalter
Copy link
Owner

The problem is probably that tuple is defined as tuple[T] and only has one generic in typeshed. So in that case the multiple generics won't affect anything and we would need to special case this one. Tuple[...] and a lot of other cases are already special cased for typing.*.

@bluthej
Copy link
Contributor Author

bluthej commented Dec 22, 2024

I see. So in fact this has nothing to do with return values from functions, but only with type annotations for tuples. I confirmed that by changing my initial example to:

import jedi

code = """\
tup: tuple[int, int] = (1, 2)
a, b = tup
a.foo
b.foo
"""

script = jedi.Script(code, path="example.py")
print(script.complete(3, 2))
print(script.complete(4, 2))

which yields exactly the same behavior I observed (no completion for b.).

Also, interestingly, if I replace tuple[int, int] with Tuple[int, int] after importing Tuple from the typing module, then completions work flawlessly for all items of the tuple, so it looks like it might just be a matter of getting tuple to behave like Tuple in this case, right? (I tried this because you mentioned that stuff from typing.* is already special cased)

I propose to update the title and description of the issue to reflect this information, what do you think?

Also, does this seem tractable for a first contribution to you? If so, I would appreciate some guidance as to where this special casing should be taking place in the repo :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants