Repository navigation
Support instantiation of type[SomeTypeVar] -> SomeTypeVar #14791
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| -- Test cases for type[SomeTypeVar] treatment | ||
|
|
||
|
|
||
| [case testTypeOfTypeVarInstantiation] | ||
| from typing import TypeVar, Type | ||
| T = TypeVar('T') | ||
| def f(x: Type[T]) -> T: | ||
| return x() | ||
|
|
||
| [case testTypeOfTypeVarBoundInstantiationInitParams] | ||
| from typing import TypeVar, Type | ||
| class A: | ||
| def __init__(self, x: str) -> None: pass | ||
| T = TypeVar('T', bound=A) | ||
| def f(x: Type[T]) -> T: | ||
| return x('a') | ||
| def f2(x: Type[T]) -> T: | ||
| return x(1) # E: Argument 1 to "A" has incompatible type "int"; expected "str" | ||
|
|
||
| [case testTypeOfTypeVarBoundInstantiationGeneric] | ||
| from typing import Generic, TypeVar, Type | ||
| class A: | ||
| def __init__(self, x: str) -> None: pass | ||
|
|
||
| T = TypeVar('T', bound=object) | ||
| V = TypeVar('V', bound=A) | ||
|
|
||
| class Test(Generic[T, V]): | ||
| X: Type[T] | ||
| Y: Type[V] | ||
| def f1(self) -> T: | ||
| return self.X() | ||
| def f2(self) -> V: | ||
| return self.Y() # E: Missing positional argument "x" in call to "A" | ||
| def f3(self) -> V: | ||
| return self.Y(1) # E: Argument 1 to "A" has incompatible type "int"; expected "str" | ||
| def f4(self) -> V: | ||
| return self.Y('foo') | ||
|
|
||
| [case testTypeOfTypeVarInstantiationInitParamsUnionBound] | ||
| from typing import TypeVar, Type, Union | ||
| class A: | ||
| def __init__(self, x: str) -> None: pass | ||
| class B: | ||
| def __init__(self, x: int) -> None: pass | ||
| class C: | ||
| def __init__(self, x: int) -> None: pass | ||
|
|
||
| T = TypeVar('T', bound=Union[A, B]) | ||
| def f(x: Type[T]) -> T: | ||
| return x(1) # E: Argument 1 to "A" has incompatible type "int"; expected "str" | ||
|
|
||
| T2 = TypeVar('T2', bound=Union[B, C]) | ||
| def f2(x: Type[T2]) -> T2: | ||
| return x(1) | ||
|
|
||
| [case testTypeOfTypeVarInstantiationVariants] | ||
| from typing import TypeVar, Type | ||
| class A: | ||
| def __init__(self, x: str) -> None: pass | ||
| class B: | ||
| def __init__(self, x: int) -> None: pass | ||
| class C: | ||
| def __init__(self, x: int) -> None: pass | ||
|
|
||
| T = TypeVar('T', A, B) | ||
| def f(x: Type[T]) -> T: | ||
| return x(1) # E: Argument 1 to "A" has incompatible type "int"; expected "str" | ||
|
|
||
| T2 = TypeVar('T2', B, C) | ||
| def f2(x: Type[T2]) -> T2: | ||
| return x(1) | ||
|
|
||
| def return_indirect(x: Type[T2]) -> T2: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need indirect tests? 🤔
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because |
||
| ret = x(1) | ||
| return ret | ||
|
|
||
| def return_indirect_condition(x: Type[T2], flag: bool) -> T2: | ||
| if flag: | ||
| ret = x(1) | ||
| else: | ||
| ret = x(2) | ||
| return ret | ||
|
|
||
| def return_indirect_condition_invalid(x: Type[T2], flag: bool) -> T2: | ||
| if flag: | ||
| ret = x(1) | ||
| else: | ||
| ret = x('foo') # E: Argument 1 to "B" has incompatible type "str"; expected "int" \ | ||
| # E: Argument 1 to "C" has incompatible type "str"; expected "int" | ||
| return ret | ||
|
|
||
| def return_indirect_condition_invalid_different(x: Type[T], flag: bool) -> T: | ||
| if flag: | ||
| ret = x(1) # E: Argument 1 to "A" has incompatible type "int"; expected "str" | ||
| else: | ||
| ret = x('foo') # E: Argument 1 to "B" has incompatible type "str"; expected "int" | ||
| return ret | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you investigated why the return type is inferred incorrectly in
check_call? It may be better to fix this there instead. Right now this feels like we may have a bug in type inference and we are papering over it by patching the result afterwards. It's still possible that there is some fundamental reason why we can't do the right thing incheck_calland this approach is the best option, but I'd prefer to better understand if that is the case.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I considered this, but it looks like this scenario is specific to one context.
check_callis not responsible here, the issue arises whenanalyze_type_type_calleeis called withTypeVar(bound=A|B)(tvar with union upper bound). This case is expanded to a Union of two callables corresponding to constructors of union items (line 1589). Currentlyanalyze_type_type_calleegivesAlternative solution would be to handle
UnionTypethere, but it looks less clear to me: we'll have then several TypeVar instances with same ids (so that they still correspond to original TypeVar), but different upper bounds, like the following:where first
T`-1is bound toAand second - toB. This is at least confusing and, I guess, error prone.The mentioned problem is related to specifically
Type[...]instantiation, and it's easier to say thatType[SomeTVar]givesSomeTVarafter successful instantiation. This is a "hack" mentioned in comment inanalyze_type_type_callee: an upper bound is analyzed instead of typevar itself.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, I'm no longer sure that the latter solution is worse.
@JukkaL Is there any precedent of creating TypeVar copies with same id and different bounds? Is there any fundamental reason not to do so? Should I open a separate issue with this question?
With such approach we'll have proper typevar narrowing for free in cases like
(this currently fails in
ifdue to narrowing that discards TypeVar part)