Jedi: Cannot complete on call of inherited classmethod

Created on 24 Jan 2020  路  7Comments  路  Source: davidhalter/jedi

I'm trying to use Jedi with a codebase roughly equivalent to

import jedi
from typing import Generic, TypeVar

T = TypeVar("T")

class Reader(Generic[T]):
    @classmethod
    def read(cls) -> T:
        return cls()

class Foo(Reader["Foo"]):
    def transform(self) -> int:
        return 42

I'd expect that once I call Foo.read() and follow with .t Jedi will give transform as possible completion (in other words will correctly infer type of Foo.read() as Foo).

Actual behavior:

jedi.Interpreter("Foo.read().t", [globals()]).completions()
## []

Expected behavior:

expected = jedi.Interpreter("Foo().t", [globals()]).completions()
actual = jedi.Interpreter("Foo.read().t", [globals()]).completions()

assert actual == expected

Tested with:

  • jedi==0.16.0 (installed from 0c56aa4d4be211b2fd691dcd68fc1b06de2c6c52)
  • Python 3.7.6
bug

All 7 comments

Thanks for reproducing this!

@zero323 BTW, there's a mistake in your example, it should be Reader[str].

I don't think there is. The runtime behavior is:

>>> class Reader: 
...     @classmethod 
...     def read(cls): 
...         return cls() 
...  
... class Foo(Reader): 
...     def transform(self): 
...         return 42 
...                                                                                                     
>>> type(Foo.read())                                                                                    
<class '__main__.Foo'>

which is what I am trying to express.

"Foo" is used in Generic because you cannot use type in forward references in py file. If I used stubs we'd have

class Reader:
    @classmethod
    def read(cls):
        return cls()

class Foo(Reader):
    def transform(self):
        return 42

in .py and

from typing import Generic, TypeVar

T = TypeVar("T")

class Reader(Generic[T]):
    @classmethod
    def read(cls) -> T: ...

class Foo(Reader[Foo]):
    def transform(self) -> int: ...

in .pyi.

If we append


reveal_type(Foo.read())

to the latter and run it through mypy

mypy __init__.pyi

it yields expected output

__init__.pyi:12: note: Revealed type is '__main__.Foo*'

In such simple case roughly equivalent would be

from typing import Type, TypeVar

T = TypeVar("T")

class Reader:
    @classmethod
    def read(cls: Type[T]) -> T:
        return cls()

class Foo(Reader):
    def transform(self) -> int:
        return 42

This works better in Pycharm, but doesn't resolve the problem here.

"Foo" is used in Generic because you cannot use type in forward references in py file. If I used stubs we'd have

My bad. You're right of course. I have almost forgotten about these again (even though I implemented it in Jedi).

This was a lot more work than I initially thought it would be. I thought a lot about how we could get this to work perfectly, but I also realized that it's not going to be possible.

So now if you have a file that contains the code you posted, it works well. If you copy paste your code into IPython, it won't work.

This is because of a limitation in Python: It's not really possible to work with runtime information of TypeVars. typing.py doesn't really have an helpers that would make it clear what generic class actually implies. There are just a lot of limitations that runtime Python has (that static analysis hasn't) in this case. So I'm closing, because I feel like the important parts have been fixed.

Sorry, this message might be a bit incomprehensible.

Thank you so much for your work. I've tested with pyspark-stubs and it works perfectly.

I think it is perfectly satisfying resolution - I realistically don't see much place for writing generic annotations inside interactive sessions anyway.

Was this page helpful?
0 / 5 - 0 ratings