From 52d349b246929ac35f9488a8fc8174150a3fa0c4 Mon Sep 17 00:00:00 2001 From: STerliakov Date: Sat, 25 Feb 2023 00:27:46 +0300 Subject: [PATCH 1/4] Support instantiation of type[SomeTypeVar] -> SomeTypeVar --- mypy/checkexpr.py | 8 ++- test-data/unit/check-type-typevar.test | 98 ++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 test-data/unit/check-type-typevar.test diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 38b5c2419d953..d760e6a275e28 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1346,7 +1346,13 @@ def check_call( ) elif isinstance(callee, TypeType): item = self.analyze_type_type_callee(callee.item, context) - return self.check_call(item, args, arg_kinds, context, arg_names, callable_node) + # We always check the validity of the call (constructor args) + checked = self.check_call(item, args, arg_kinds, context, arg_names, callable_node) + if isinstance(callee.item, TypeVarType) and not isinstance(checked, AnyType): + # If it was `type[SomeTypeVar]`, successful instantiation produces SomeTypeVar + return callee.item, callee + else: + return checked elif isinstance(callee, TupleType): return self.check_call( tuple_fallback(callee), diff --git a/test-data/unit/check-type-typevar.test b/test-data/unit/check-type-typevar.test new file mode 100644 index 0000000000000..55d737d1f514f --- /dev/null +++ b/test-data/unit/check-type-typevar.test @@ -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: + 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 From 9a8ab64ace0a0adc43a989d10f2bae2e7b127569 Mon Sep 17 00:00:00 2001 From: STerliakov Date: Sun, 26 Feb 2023 21:09:34 +0300 Subject: [PATCH 2/4] Fix isinstance check: return type is 2-tuple --- mypy/checkexpr.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index d760e6a275e28..378917809e4f6 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1347,12 +1347,12 @@ def check_call( elif isinstance(callee, TypeType): item = self.analyze_type_type_callee(callee.item, context) # We always check the validity of the call (constructor args) - checked = self.check_call(item, args, arg_kinds, context, arg_names, callable_node) - if isinstance(callee.item, TypeVarType) and not isinstance(checked, AnyType): + result_type, callee_type = self.check_call(item, args, arg_kinds, context, arg_names, callable_node) + if isinstance(callee.item, TypeVarType) and not isinstance(result_type, AnyType): # If it was `type[SomeTypeVar]`, successful instantiation produces SomeTypeVar - return callee.item, callee + return callee.item, callee_type else: - return checked + return result_type, callee_type elif isinstance(callee, TupleType): return self.check_call( tuple_fallback(callee), From 2f8e9724ad66095135b95c86cac86f2d4a19a7d5 Mon Sep 17 00:00:00 2001 From: STerliakov Date: Sun, 26 Feb 2023 21:11:17 +0300 Subject: [PATCH 3/4] Lint --- mypy/checkexpr.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 378917809e4f6..4a0eaae3e46b3 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1347,7 +1347,9 @@ def check_call( elif isinstance(callee, TypeType): item = self.analyze_type_type_callee(callee.item, context) # We always check the validity of the call (constructor args) - result_type, callee_type = self.check_call(item, args, arg_kinds, context, arg_names, callable_node) + result_type, callee_type = self.check_call( + item, args, arg_kinds, context, arg_names, callable_node + ) if isinstance(callee.item, TypeVarType) and not isinstance(result_type, AnyType): # If it was `type[SomeTypeVar]`, successful instantiation produces SomeTypeVar return callee.item, callee_type From 710a2fab7ead7d512d815b3582bb97722931168a Mon Sep 17 00:00:00 2001 From: STerliakov Date: Sun, 26 Feb 2023 21:31:35 +0300 Subject: [PATCH 4/4] Add missing get_proper_type --- mypy/checkexpr.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 4a0eaae3e46b3..74170fd5c5c67 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1350,7 +1350,9 @@ def check_call( result_type, callee_type = self.check_call( item, args, arg_kinds, context, arg_names, callable_node ) - if isinstance(callee.item, TypeVarType) and not isinstance(result_type, AnyType): + if isinstance(callee.item, TypeVarType) and not isinstance( + get_proper_type(result_type), AnyType + ): # If it was `type[SomeTypeVar]`, successful instantiation produces SomeTypeVar return callee.item, callee_type else: