pytype icon indicating copy to clipboard operation
pytype copied to clipboard

Inferred TypeVar information lost through function calls

Open rickeylev opened this issue 7 years ago • 6 comments

When a typevar is used in a function call, sub-types of the typevar are lost. e.g. def foo(T) -> T, when given List[int] as the input will return List. This only seems to happen when the generic'd type is inferred.

from typing import TypeVar, List

T = TypeVar('T')

def accept(x: T) -> T:
  return x

a: List[int] = [1, 2]
b = [1, 2]

reveal_type(a)  # outputs List[int]
a_out = accept(a)
reveal_type(a_out)  # outputs List[int]

reveal_type(b)  # outputs List[int]
b_out = accept(b)
reveal_type(b_out)  # outputs list (unexpected)

Expected: the last reveal_type should output List[int] Actual: the last reveal_type outputs list

rickeylev avatar Dec 18 '18 22:12 rickeylev

Weird. We have a bug for losing information about functions when they're passed through annotated decorators, but I'm surprised that the same happens for other types.

rchen152 avatar Dec 19 '18 18:12 rchen152

I ran into the same for decorators specifically. If I add @pytypes.typechecked to a class to add runtime type checking, that sadly hobbles the static type inference and aliases the type to Any.

@rchen152 was that bug about decorators in some other tracker? In GitHub I only found this bug about decorators.

dbarnett avatar May 03 '20 22:05 dbarnett

Ah yes, the decorators bug was in our internal tracker (and will be fixed in the next release, actually). The bug there was that things like

def decorator(func: T) -> T: ...
@decorator
def f(): ...

didn't work properly when the decorator and the decorator function were in the same file.

The @pytypes.typechecked issue is probably caused by us having no type information at all for pytypes. To fix that, someone would have to add stubs for pytypes to https://github.com/python/typeshed.

rchen152 avatar May 04 '20 21:05 rchen152

I also took another look at the original issue that @rickeylev opened. The problem there is that for (x: T) -> T, we get the return type from the .cls attribute of the input value (https://github.com/google/pytype/blob/9c6e5f1bdbb037f25cb84475a7ccafa5b2b14988/pytype/convert.py#L338), which for a list instance with an inferred type is List[Any] (which makes sense in most cases, since the contained type might change as things are appended to the list).

rchen152 avatar May 04 '20 21:05 rchen152

Ah thanks @rchen152! I had tried several workarounds like defining my own decorator wrappers with explicit type annotations and using the more literal MyType = typechecked(MyType) syntax instead, but definitely wouldn't have thought to try defining them in a separate file.

I filed python/typeshed#3991 to add pytypes to typeshed.

dbarnett avatar May 14 '20 05:05 dbarnett

Oh, defining your own wrapper should work. (Support for that in pytype is quite recent, so it might not have been working when you originally tried it.) Something like:

from typing import TypeVar
import pytypes

T = TypeVar('T')
def typechecked(x: T) -> T:
  return pytypes.typechecked(x)

should do it.

rchen152 avatar May 14 '20 21:05 rchen152