From cc8d33ff0a4d45e0002586324e5c78b22af3001c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaquier=20Aur=C3=A9lien=20Tristan?= Date: Sun, 17 Jul 2022 11:16:55 +0100 Subject: [PATCH 01/18] Add simple unbound typevar check and rudimentary test --- mypy/checker.py | 4 ++++ test-data/unit/check-typevar-tuple.test | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/mypy/checker.py b/mypy/checker.py index d17871039332..288fda9e11e9 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -919,6 +919,10 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str]) self.fail(message_registry.RETURN_TYPE_CANNOT_BE_CONTRAVARIANT, typ.ret_type) + if not any([isinstance(argtype, TypeVarType) for argtype in typ.arg_types]): + self.fail(message_registry.RETURN_TYPE_CANNOT_BE_CONTRAVARIANT, + typ.ret_type) + # Check that Generator functions have the appropriate return type. if defn.is_generator: if defn.is_async_generator: diff --git a/test-data/unit/check-typevar-tuple.test b/test-data/unit/check-typevar-tuple.test index e98f5a69001e..421cb7ea723f 100644 --- a/test-data/unit/check-typevar-tuple.test +++ b/test-data/unit/check-typevar-tuple.test @@ -94,3 +94,16 @@ reveal_type(g(args)) # N: Revealed type is "Tuple[builtins.str, builtins.str, b reveal_type(h(args)) # N: Revealed type is "Tuple[builtins.str, builtins.str, builtins.int]" [builtins fixtures/tuple.pyi] + +[case testUnboundTypeVar] +from typing import TypeVar + +T = TypeVar('T') + +def f() -> T: # no error + return 0 + +if f(): + print() # Statement is unreachable +else: + print() # Statement is unreachable From cc8faff7f3ac3c478fc6e1f00c6ecc42c06c732e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaquier=20Aur=C3=A9lien=20Tristan?= Date: Sun, 17 Jul 2022 11:29:24 +0100 Subject: [PATCH 02/18] Add test for unbound func returning iterables of TypeVars --- test-data/unit/check-typevar-tuple.test | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/test-data/unit/check-typevar-tuple.test b/test-data/unit/check-typevar-tuple.test index 421cb7ea723f..0eed5bc92c0e 100644 --- a/test-data/unit/check-typevar-tuple.test +++ b/test-data/unit/check-typevar-tuple.test @@ -100,10 +100,18 @@ from typing import TypeVar T = TypeVar('T') -def f() -> T: # no error - return 0 +def f() -> T: # raise error + ... + +f() + + +[case testUnboundIterableOfTypeVars] +from typing import Iterable, TypeVar + +T = TypeVar('T') + +def f() -> Iterable[T]: # no error + ... -if f(): - print() # Statement is unreachable -else: - print() # Statement is unreachable +f() From a77139380f70bd08cff431194bfb32e4cf7bef82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaquier=20Aur=C3=A9lien=20Tristan?= Date: Sun, 17 Jul 2022 12:21:29 +0100 Subject: [PATCH 03/18] Add appropriate error message and make the new test pass --- mypy/checker.py | 3 +-- mypy/message_registry.py | 1 + test-data/unit/check-typevar-tuple.test | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 288fda9e11e9..7e6daa55d775 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -920,8 +920,7 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str]) typ.ret_type) if not any([isinstance(argtype, TypeVarType) for argtype in typ.arg_types]): - self.fail(message_registry.RETURN_TYPE_CANNOT_BE_CONTRAVARIANT, - typ.ret_type) + self.fail(message_registry.UNBOUND_TYPEVAR, typ.ret_type) # Check that Generator functions have the appropriate return type. if defn.is_generator: diff --git a/mypy/message_registry.py b/mypy/message_registry.py index 0f14d706ccca..7adaf82294b2 100644 --- a/mypy/message_registry.py +++ b/mypy/message_registry.py @@ -167,6 +167,7 @@ def format(self, *args: object, **kwargs: object) -> "ErrorMessage": TYPEVAR_VARIANCE_DEF: Final = 'TypeVar "{}" may only be a literal bool' TYPEVAR_BOUND_MUST_BE_TYPE: Final = 'TypeVar "bound" must be a type' TYPEVAR_UNEXPECTED_ARGUMENT: Final = 'Unexpected argument to "TypeVar()"' +UNBOUND_TYPEVAR: Final = 'A function returning TypeVar should receive at least one argument containing the same Typevar' # Super TOO_MANY_ARGS_FOR_SUPER: Final = ErrorMessage('Too many arguments for "super"') diff --git a/test-data/unit/check-typevar-tuple.test b/test-data/unit/check-typevar-tuple.test index 0eed5bc92c0e..0acceaa8057d 100644 --- a/test-data/unit/check-typevar-tuple.test +++ b/test-data/unit/check-typevar-tuple.test @@ -100,7 +100,7 @@ from typing import TypeVar T = TypeVar('T') -def f() -> T: # raise error +def f() -> T: # E: A function returning TypeVar should receive at least one argument containing the same Typevar ... f() From 714445ecd33453de2fcc9c2bca8f7ac91a716541 Mon Sep 17 00:00:00 2001 From: Anil Tuncel Date: Sun, 17 Jul 2022 12:55:34 +0100 Subject: [PATCH 04/18] add CollectArgTypes to get set of argument types --- mypy/checker.py | 16 +++++++++++++++- test-data/unit/check-typevar-tuple.test | 20 ++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 288fda9e11e9..6b3cb48b085d 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -40,6 +40,7 @@ get_proper_types, is_literal_type, TypeAliasType, TypeGuardedType, ParamSpecType, OVERLOAD_NAMES, UnboundType ) +from mypy.typetraverser import TypeTraverserVisitor from mypy.sametypes import is_same_type from mypy.messages import ( MessageBuilder, make_inferred_type_note, append_invariance_notes, pretty_seq, @@ -919,7 +920,11 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str]) self.fail(message_registry.RETURN_TYPE_CANNOT_BE_CONTRAVARIANT, typ.ret_type) - if not any([isinstance(argtype, TypeVarType) for argtype in typ.arg_types]): + arg_type_visitor = CollectArgTypes() + for argtype in typ.arg_types: + argtype.accept(arg_type_visitor) + + if typ.ret_type not in arg_type_visitor.arg_types: self.fail(message_registry.RETURN_TYPE_CANNOT_BE_CONTRAVARIANT, typ.ret_type) @@ -5866,6 +5871,15 @@ class Foo(Enum): and member_type.fallback.type == parent_type.type_object()) +class CollectArgTypes(TypeTraverserVisitor): + """Collects the non-nested argument types in a set.""" + def __init__(self) -> None: + self.arg_types: Set[Instance] = set() + + def visit_type_var(self, t: TypeVarType) -> None: + self.arg_types.add(t) + + @overload def conditional_types(current_type: Type, proposed_type_ranges: Optional[List[TypeRange]], diff --git a/test-data/unit/check-typevar-tuple.test b/test-data/unit/check-typevar-tuple.test index 0eed5bc92c0e..f36307ff100d 100644 --- a/test-data/unit/check-typevar-tuple.test +++ b/test-data/unit/check-typevar-tuple.test @@ -115,3 +115,23 @@ def f() -> Iterable[T]: # no error ... f() + +[case testBoundTypeVar] +from typing import TypeVar + +T = TypeVar('T') + +def f(a: T, b: T) -> T: # raise error + ... + + +[case testNestedBoundTypeVar] +from typing import Callable, Union, TypeVar + +T = TypeVar('T') + +def f(a: Union[int, T], b: str) -> T: # raise error + ... + +def g(a: Callable[..., T], b: str) -> T: # raise error + ... From db501a9e7e2f5d4f3521c227315732d06fa0d641 Mon Sep 17 00:00:00 2001 From: Anil Tuncel Date: Sun, 17 Jul 2022 13:45:54 +0100 Subject: [PATCH 05/18] lint fix --- mypy/checker.py | 1 - mypy/message_registry.py | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 20c1a8df2b0e..46ff17225576 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -927,7 +927,6 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str]) if typ.ret_type not in arg_type_visitor.arg_types: self.fail(message_registry.UNBOUND_TYPEVAR, typ.ret_type) - # Check that Generator functions have the appropriate return type. if defn.is_generator: if defn.is_async_generator: diff --git a/mypy/message_registry.py b/mypy/message_registry.py index 7adaf82294b2..8357f788886b 100644 --- a/mypy/message_registry.py +++ b/mypy/message_registry.py @@ -167,7 +167,9 @@ def format(self, *args: object, **kwargs: object) -> "ErrorMessage": TYPEVAR_VARIANCE_DEF: Final = 'TypeVar "{}" may only be a literal bool' TYPEVAR_BOUND_MUST_BE_TYPE: Final = 'TypeVar "bound" must be a type' TYPEVAR_UNEXPECTED_ARGUMENT: Final = 'Unexpected argument to "TypeVar()"' -UNBOUND_TYPEVAR: Final = 'A function returning TypeVar should receive at least one argument containing the same Typevar' +UNBOUND_TYPEVAR: Final = ( + 'A function returning TypeVar should receive at least ' + 'one argument containing the same Typevar') # Super TOO_MANY_ARGS_FOR_SUPER: Final = ErrorMessage('Too many arguments for "super"') From e31f967512ec05e9bad4d2c313972fc80edd10b7 Mon Sep 17 00:00:00 2001 From: Anil Tuncel Date: Sun, 17 Jul 2022 13:49:42 +0100 Subject: [PATCH 06/18] fix type error --- mypy/checker.py | 2 +- test-data/unit/check-typevar-tuple.test | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 46ff17225576..9a7b58d01915 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -5873,7 +5873,7 @@ class Foo(Enum): class CollectArgTypes(TypeTraverserVisitor): """Collects the non-nested argument types in a set.""" def __init__(self) -> None: - self.arg_types: Set[Instance] = set() + self.arg_types: Set[TypeVarType] = set() def visit_type_var(self, t: TypeVarType) -> None: self.arg_types.add(t) diff --git a/test-data/unit/check-typevar-tuple.test b/test-data/unit/check-typevar-tuple.test index 124c0896e02d..e4b7826798ce 100644 --- a/test-data/unit/check-typevar-tuple.test +++ b/test-data/unit/check-typevar-tuple.test @@ -111,7 +111,7 @@ from typing import Iterable, TypeVar T = TypeVar('T') -def f() -> Iterable[T]: # no error +def f() -> Iterable[T]: ... f() @@ -121,7 +121,7 @@ from typing import TypeVar T = TypeVar('T') -def f(a: T, b: T) -> T: # raise error +def f(a: T, b: T, c: int) -> T: ... @@ -130,8 +130,8 @@ from typing import Callable, Union, TypeVar T = TypeVar('T') -def f(a: Union[int, T], b: str) -> T: # raise error +def f(a: Union[int, T], b: str) -> T: ... -def g(a: Callable[..., T], b: str) -> T: # raise error +def g(a: Callable[..., T], b: str) -> T: ... From 951a54d2f50331f979ea900d77cf66f22d66079c Mon Sep 17 00:00:00 2001 From: Anil Tuncel Date: Sun, 17 Jul 2022 14:48:58 +0100 Subject: [PATCH 07/18] extract check_unbound_return_typevar as method --- mypy/checker.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 9a7b58d01915..a0fce34c6e5a 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -919,13 +919,7 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str]) if typ.ret_type.variance == CONTRAVARIANT: self.fail(message_registry.RETURN_TYPE_CANNOT_BE_CONTRAVARIANT, typ.ret_type) - - arg_type_visitor = CollectArgTypes() - for argtype in typ.arg_types: - argtype.accept(arg_type_visitor) - - if typ.ret_type not in arg_type_visitor.arg_types: - self.fail(message_registry.UNBOUND_TYPEVAR, typ.ret_type) + self.check_unbound_return_typevar(typ) # Check that Generator functions have the appropriate return type. if defn.is_generator: @@ -1070,6 +1064,15 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str]) self.binder = old_binder + def check_unbound_return_typevar(self, typ: CallableType) -> None: + """Fails when the return typevar is not defined in arguments.""" + arg_type_visitor = CollectArgTypes() + for argtype in typ.arg_types: + argtype.accept(arg_type_visitor) + + if typ.ret_type not in arg_type_visitor.arg_types: + self.fail(message_registry.UNBOUND_TYPEVAR, typ.ret_type) + def check_default_args(self, item: FuncItem, body_is_trivial: bool) -> None: for arg in item.arguments: if arg.initializer is None: From b9b1561685bbba13e7e381e83fd959d76ec236da Mon Sep 17 00:00:00 2001 From: Anil Tuncel Date: Sun, 17 Jul 2022 15:08:04 +0100 Subject: [PATCH 08/18] check if return type is instantiated in typ.variables --- mypy/checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index a0fce34c6e5a..42b27826b0fd 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1070,7 +1070,7 @@ def check_unbound_return_typevar(self, typ: CallableType) -> None: for argtype in typ.arg_types: argtype.accept(arg_type_visitor) - if typ.ret_type not in arg_type_visitor.arg_types: + if typ.ret_type not in arg_type_visitor.arg_types and typ.ret_type in typ.variables: self.fail(message_registry.UNBOUND_TYPEVAR, typ.ret_type) def check_default_args(self, item: FuncItem, body_is_trivial: bool) -> None: From 0335cc1067e9397c5cc51cb1799db73cd72cba0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaquier=20Aur=C3=A9lien=20Tristan?= Date: Sun, 17 Jul 2022 15:13:14 +0100 Subject: [PATCH 09/18] Fix some tests that are affected by new implementation --- test-data/unit/check-classes.test | 3 ++- test-data/unit/check-errorcodes.test | 2 +- test-data/unit/check-generics.test | 10 +++++----- test-data/unit/check-inference-context.test | 2 ++ test-data/unit/check-inference.test | 4 ++-- test-data/unit/check-overloading.test | 2 +- test-data/unit/check-parameter-specification.test | 4 ++-- 7 files changed, 15 insertions(+), 12 deletions(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index e326e24df0e6..54abdec4e720 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -3269,10 +3269,11 @@ def new_pro(pro_c: Type[P]) -> P: return new_user(pro_c) wiz = new_pro(WizUser) reveal_type(wiz) -def error(u_c: Type[U]) -> P: +def error(u_c: Type[U]) -> P: # Error here, see below return new_pro(u_c) # Error here, see below [out] main:11: note: Revealed type is "__main__.WizUser" +main:12: error: A function returning TypeVar should receive at least one argument containing the same Typevar main:13: error: Value of type variable "P" of "new_pro" cannot be "U" main:13: error: Incompatible return value type (got "U", expected "P") diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index 9fde5ce0d0cc..c85064bd4474 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -335,7 +335,7 @@ z: y # E: Variable "__main__.y" is not valid as a type [valid-type] \ from typing import TypeVar T = TypeVar('T') -def f() -> T: pass +def f() -> T: # E: A function returning TypeVar should receive at least one argument containing the same Typevar x = f() # E: Need type annotation for "x" [var-annotated] y = [] # E: Need type annotation for "y" (hint: "y: List[] = ...") [var-annotated] [builtins fixtures/list.pyi] diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index b228e76a32d1..b70c862092cb 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -1533,9 +1533,9 @@ A = TypeVar('A') B = TypeVar('B') def f1(x: A) -> A: ... -def f2(x: A) -> B: ... +def f2(x: A) -> B: ... # E: A function returning TypeVar should receive at least one argument containing the same Typevar def f3(x: B) -> B: ... -def f4(x: int) -> A: ... +def f4(x: int) -> A: ... # E: A function returning TypeVar should receive at least one argument containing the same Typevar y1 = f1 if int(): @@ -1584,8 +1584,8 @@ B = TypeVar('B') T = TypeVar('T') def outer(t: T) -> None: def f1(x: A) -> A: ... - def f2(x: A) -> B: ... - def f3(x: T) -> A: ... + def f2(x: A) -> B: ... # E: A function returning TypeVar should receive at least one argument containing the same Typevar + def f3(x: T) -> A: ... # E: A function returning TypeVar should receive at least one argument containing the same Typevar def f4(x: A) -> T: ... def f5(x: T) -> T: ... @@ -1754,7 +1754,7 @@ from typing import TypeVar A = TypeVar('A') B = TypeVar('B') def f1(x: int, y: A) -> A: ... -def f2(x: int, y: A) -> B: ... +def f2(x: int, y: A) -> B: ... # E: A function returning TypeVar should receive at least one argument containing the same Typevar def f3(x: A, y: B) -> B: ... g = f1 g = f2 diff --git a/test-data/unit/check-inference-context.test b/test-data/unit/check-inference-context.test index 3bab79f5aec2..2934ec07539e 100644 --- a/test-data/unit/check-inference-context.test +++ b/test-data/unit/check-inference-context.test @@ -885,6 +885,7 @@ S = TypeVar('S') def f(a: T, b: S) -> None: c = lambda x: x # type: Callable[[T], S] [out] +main:5: error: A function returning TypeVar should receive at least one argument containing the same Typevar main:5: error: Incompatible types in assignment (expression has type "Callable[[T], T]", variable has type "Callable[[T], S]") main:5: error: Incompatible return value type (got "T", expected "S") @@ -896,6 +897,7 @@ class A(Generic[T]): def f(self, b: S) -> None: c = lambda x: x # type: Callable[[T], S] [out] +main:6: error: A function returning TypeVar should receive at least one argument containing the same Typevar main:6: error: Incompatible types in assignment (expression has type "Callable[[T], T]", variable has type "Callable[[T], S]") main:6: error: Incompatible return value type (got "T", expected "S") diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 21c96bf2df45..a7f63d4916df 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -448,7 +448,7 @@ g(None) # Ok f() # Ok because not used to infer local variable type g(a) -def f() -> T: pass +def f() -> T: pass # E: A function returning TypeVar should receive at least one argument containing the same Typevar def g(a: T) -> None: pass [out] @@ -2341,7 +2341,7 @@ def main() -> None: [case testDontMarkUnreachableAfterInferenceUninhabited] from typing import TypeVar T = TypeVar('T') -def f() -> T: pass +def f() -> T: pass # E: A function returning TypeVar should receive at least one argument containing the same Typevar class C: x = f() # E: Need type annotation for "x" diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 312d7a6cc7ae..eca8d8e81c6a 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -5310,7 +5310,7 @@ class fakeint: U = TypeVar('U') V = TypeVar('V') W = TypeVar('W') -def compose(f: Callable[[U], V], g: Callable[[W], U]) -> Callable[[W], V]: +def compose(f: Callable[[U], V], g: Callable[[W], U]) -> Callable[[W], V]: # E: A function returning TypeVar should receive at least one argument containing the same Typevar return lambda x: f(g(x)) ID = NewType("ID", fakeint) diff --git a/test-data/unit/check-parameter-specification.test b/test-data/unit/check-parameter-specification.test index 682ce93cb7ea..58e6c713e3d7 100644 --- a/test-data/unit/check-parameter-specification.test +++ b/test-data/unit/check-parameter-specification.test @@ -477,7 +477,7 @@ R = TypeVar("R") class Request: ... -def with_request(f: Callable[Concatenate[Request, P], R]) -> Callable[P, R]: +def with_request(f: Callable[Concatenate[Request, P], R]) -> Callable[P, R]: # E: A function returning TypeVar should receive at least one argument containing the same Typevar def inner(*args: P.args, **kwargs: P.kwargs) -> R: return f(Request(), *args, **kwargs) return inner @@ -1062,7 +1062,7 @@ def callback(func: Callable[[Any], Any]) -> None: ... class Job(Generic[P]): ... @callback -def run_job(job: Job[...]) -> T: ... +def run_job(job: Job[...]) -> T: ... # E: A function returning TypeVar should receive at least one argument containing the same Typevar [builtins fixtures/tuple.pyi] [case testTupleAndDictOperationsOnParamSpecArgsAndKwargs] From 1ddf301cdec3212dce19fb72d93224b60920c809 Mon Sep 17 00:00:00 2001 From: Anil Tuncel Date: Sun, 17 Jul 2022 15:49:04 +0100 Subject: [PATCH 10/18] add testInnerFunctionTypeVar test --- test-data/unit/check-typevar-tuple.test | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test-data/unit/check-typevar-tuple.test b/test-data/unit/check-typevar-tuple.test index e4b7826798ce..65e9bca947d6 100644 --- a/test-data/unit/check-typevar-tuple.test +++ b/test-data/unit/check-typevar-tuple.test @@ -106,6 +106,18 @@ def f() -> T: # E: A function returning TypeVar should receive at least one argu f() +[case testInnerFunctionTypeVar] + +from typing import TypeVar + +T = TypeVar('T') + +def g(a: T) -> T: + def f() -> T: + ... + return f() + + [case testUnboundIterableOfTypeVars] from typing import Iterable, TypeVar From 32c0c846bd082494b7dbd8df6c6a8f9b8a07baa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaquier=20Aur=C3=A9lien=20Tristan?= Date: Sun, 17 Jul 2022 15:49:17 +0100 Subject: [PATCH 11/18] fix testErrorCodeNeedTypeAnnotation test --- test-data/unit/check-errorcodes.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index c85064bd4474..769d3f7f5863 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -335,7 +335,7 @@ z: y # E: Variable "__main__.y" is not valid as a type [valid-type] \ from typing import TypeVar T = TypeVar('T') -def f() -> T: # E: A function returning TypeVar should receive at least one argument containing the same Typevar +def f() -> T: pass # E: A function returning TypeVar should receive at least one argument containing the same Typevar x = f() # E: Need type annotation for "x" [var-annotated] y = [] # E: Need type annotation for "y" (hint: "y: List[] = ...") [var-annotated] [builtins fixtures/list.pyi] From 1175d5358f1759033264771f0fc9f5aa68dca6ac Mon Sep 17 00:00:00 2001 From: Anil Tuncel Date: Sun, 17 Jul 2022 15:50:06 +0100 Subject: [PATCH 12/18] add TYPE_VAR error code to unbound error failure --- mypy/checker.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 42b27826b0fd..02cbd4846d95 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -12,6 +12,7 @@ from typing_extensions import Final, TypeAlias as _TypeAlias from mypy.backports import nullcontext +from mypy.errorcodes import TYPE_VAR from mypy.errors import Errors, report_internal_error, ErrorWatcher from mypy.nodes import ( SymbolTable, Statement, MypyFile, Var, Expression, Lvalue, Node, @@ -1071,7 +1072,7 @@ def check_unbound_return_typevar(self, typ: CallableType) -> None: argtype.accept(arg_type_visitor) if typ.ret_type not in arg_type_visitor.arg_types and typ.ret_type in typ.variables: - self.fail(message_registry.UNBOUND_TYPEVAR, typ.ret_type) + self.fail(message_registry.UNBOUND_TYPEVAR, typ.ret_type, code=TYPE_VAR) def check_default_args(self, item: FuncItem, body_is_trivial: bool) -> None: for arg in item.arguments: From 402a4e3ee09589a301c93c2f973ba82c028d5316 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaquier=20Aur=C3=A9lien=20Tristan?= Date: Sun, 17 Jul 2022 16:11:32 +0100 Subject: [PATCH 13/18] Fix the tests --- test-data/unit/check-errorcodes.test | 2 +- test-data/unit/check-inference-context.test | 2 -- test-data/unit/check-literal.test | 4 ++-- test-data/unit/check-overloading.test | 2 +- test-data/unit/check-parameter-specification.test | 2 +- 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index 769d3f7f5863..f29da02689bd 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -335,7 +335,7 @@ z: y # E: Variable "__main__.y" is not valid as a type [valid-type] \ from typing import TypeVar T = TypeVar('T') -def f() -> T: pass # E: A function returning TypeVar should receive at least one argument containing the same Typevar +def f() -> T: pass # E: A function returning TypeVar should receive at least one argument containing the same Typevar [type-var] x = f() # E: Need type annotation for "x" [var-annotated] y = [] # E: Need type annotation for "y" (hint: "y: List[] = ...") [var-annotated] [builtins fixtures/list.pyi] diff --git a/test-data/unit/check-inference-context.test b/test-data/unit/check-inference-context.test index 2934ec07539e..3bab79f5aec2 100644 --- a/test-data/unit/check-inference-context.test +++ b/test-data/unit/check-inference-context.test @@ -885,7 +885,6 @@ S = TypeVar('S') def f(a: T, b: S) -> None: c = lambda x: x # type: Callable[[T], S] [out] -main:5: error: A function returning TypeVar should receive at least one argument containing the same Typevar main:5: error: Incompatible types in assignment (expression has type "Callable[[T], T]", variable has type "Callable[[T], S]") main:5: error: Incompatible return value type (got "T", expected "S") @@ -897,7 +896,6 @@ class A(Generic[T]): def f(self, b: S) -> None: c = lambda x: x # type: Callable[[T], S] [out] -main:6: error: A function returning TypeVar should receive at least one argument containing the same Typevar main:6: error: Incompatible types in assignment (expression has type "Callable[[T], T]", variable has type "Callable[[T], S]") main:6: error: Incompatible return value type (got "T", expected "S") diff --git a/test-data/unit/check-literal.test b/test-data/unit/check-literal.test index ab6154428343..f28e82447e20 100644 --- a/test-data/unit/check-literal.test +++ b/test-data/unit/check-literal.test @@ -972,7 +972,7 @@ b: bt # E: Variable "__main__.bt" is not valid as a ty [out] [case testLiteralDisallowTypeVar] -from typing import TypeVar +from typing import TypeVar, Tuple from typing_extensions import Literal T = TypeVar('T') @@ -980,7 +980,7 @@ T = TypeVar('T') at = Literal[T] # E: Parameter 1 of Literal[...] is invalid a: at -def foo(b: Literal[T]) -> T: pass # E: Parameter 1 of Literal[...] is invalid +def foo(b: Literal[T]) -> Tuple[T]: pass # E: Parameter 1 of Literal[...] is invalid [builtins fixtures/tuple.pyi] [out] diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index eca8d8e81c6a..312d7a6cc7ae 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -5310,7 +5310,7 @@ class fakeint: U = TypeVar('U') V = TypeVar('V') W = TypeVar('W') -def compose(f: Callable[[U], V], g: Callable[[W], U]) -> Callable[[W], V]: # E: A function returning TypeVar should receive at least one argument containing the same Typevar +def compose(f: Callable[[U], V], g: Callable[[W], U]) -> Callable[[W], V]: return lambda x: f(g(x)) ID = NewType("ID", fakeint) diff --git a/test-data/unit/check-parameter-specification.test b/test-data/unit/check-parameter-specification.test index 58e6c713e3d7..ae9b8e6d84a0 100644 --- a/test-data/unit/check-parameter-specification.test +++ b/test-data/unit/check-parameter-specification.test @@ -477,7 +477,7 @@ R = TypeVar("R") class Request: ... -def with_request(f: Callable[Concatenate[Request, P], R]) -> Callable[P, R]: # E: A function returning TypeVar should receive at least one argument containing the same Typevar +def with_request(f: Callable[Concatenate[Request, P], R]) -> Callable[P, R]: def inner(*args: P.args, **kwargs: P.kwargs) -> R: return f(Request(), *args, **kwargs) return inner From 34dc475fc1887c462f682b761d25585262e302ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaquier=20Aur=C3=A9lien=20Tristan?= Date: Sun, 17 Jul 2022 16:14:18 +0100 Subject: [PATCH 14/18] move new tests in new test file --- test-data/unit/check-typevar-tuple.test | 54 ----------------------- test-data/unit/check-typevar-unbound.test | 53 ++++++++++++++++++++++ 2 files changed, 53 insertions(+), 54 deletions(-) create mode 100644 test-data/unit/check-typevar-unbound.test diff --git a/test-data/unit/check-typevar-tuple.test b/test-data/unit/check-typevar-tuple.test index 65e9bca947d6..ac7bca34879d 100644 --- a/test-data/unit/check-typevar-tuple.test +++ b/test-data/unit/check-typevar-tuple.test @@ -93,57 +93,3 @@ args: Tuple[bool, int, str, int, str, object] reveal_type(g(args)) # N: Revealed type is "Tuple[builtins.str, builtins.str, builtins.int]" reveal_type(h(args)) # N: Revealed type is "Tuple[builtins.str, builtins.str, builtins.int]" [builtins fixtures/tuple.pyi] - - -[case testUnboundTypeVar] -from typing import TypeVar - -T = TypeVar('T') - -def f() -> T: # E: A function returning TypeVar should receive at least one argument containing the same Typevar - ... - -f() - - -[case testInnerFunctionTypeVar] - -from typing import TypeVar - -T = TypeVar('T') - -def g(a: T) -> T: - def f() -> T: - ... - return f() - - -[case testUnboundIterableOfTypeVars] -from typing import Iterable, TypeVar - -T = TypeVar('T') - -def f() -> Iterable[T]: - ... - -f() - -[case testBoundTypeVar] -from typing import TypeVar - -T = TypeVar('T') - -def f(a: T, b: T, c: int) -> T: - ... - - -[case testNestedBoundTypeVar] -from typing import Callable, Union, TypeVar - -T = TypeVar('T') - -def f(a: Union[int, T], b: str) -> T: - ... - -def g(a: Callable[..., T], b: str) -> T: - ... diff --git a/test-data/unit/check-typevar-unbound.test b/test-data/unit/check-typevar-unbound.test new file mode 100644 index 000000000000..2a2ee630cf3d --- /dev/null +++ b/test-data/unit/check-typevar-unbound.test @@ -0,0 +1,53 @@ + +[case testUnboundTypeVar] +from typing import TypeVar + +T = TypeVar('T') + +def f() -> T: # E: A function returning TypeVar should receive at least one argument containing the same Typevar + ... + +f() + + +[case testInnerFunctionTypeVar] + +from typing import TypeVar + +T = TypeVar('T') + +def g(a: T) -> T: + def f() -> T: + ... + return f() + + +[case testUnboundIterableOfTypeVars] +from typing import Iterable, TypeVar + +T = TypeVar('T') + +def f() -> Iterable[T]: + ... + +f() + +[case testBoundTypeVar] +from typing import TypeVar + +T = TypeVar('T') + +def f(a: T, b: T, c: int) -> T: + ... + + +[case testNestedBoundTypeVar] +from typing import Callable, Union, TypeVar + +T = TypeVar('T') + +def f(a: Union[int, T], b: str) -> T: + ... + +def g(a: Callable[..., T], b: str) -> T: + ... From b78b92691cfb1b00fd6e0a4bfa073eb699e6c259 Mon Sep 17 00:00:00 2001 From: Anil Tuncel Date: Mon, 18 Jul 2022 17:24:19 +0200 Subject: [PATCH 15/18] add 'check-typevar-unbound.test' to testcheck --- mypy/test/testcheck.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mypy/test/testcheck.py b/mypy/test/testcheck.py index 9367750c96d0..876216209de1 100644 --- a/mypy/test/testcheck.py +++ b/mypy/test/testcheck.py @@ -99,6 +99,7 @@ 'check-annotated.test', 'check-parameter-specification.test', 'check-typevar-tuple.test', + 'check-typevar-unbound.test', 'check-generic-alias.test', 'check-typeguard.test', 'check-functools.test', From ce9ceaf509196c44e7efa28a35b77c3486d35736 Mon Sep 17 00:00:00 2001 From: Anil Tuncel Date: Mon, 18 Jul 2022 17:24:38 +0200 Subject: [PATCH 16/18] add more nested tests for unbound type --- test-data/unit/check-typevar-unbound.test | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test-data/unit/check-typevar-unbound.test b/test-data/unit/check-typevar-unbound.test index 2a2ee630cf3d..34fd0e0de4ff 100644 --- a/test-data/unit/check-typevar-unbound.test +++ b/test-data/unit/check-typevar-unbound.test @@ -42,7 +42,7 @@ def f(a: T, b: T, c: int) -> T: [case testNestedBoundTypeVar] -from typing import Callable, Union, TypeVar +from typing import Callable, List, Union, Tuple, TypeVar T = TypeVar('T') @@ -51,3 +51,9 @@ def f(a: Union[int, T], b: str) -> T: def g(a: Callable[..., T], b: str) -> T: ... + +def h(a: List[Union[Callable[..., T]]]) -> T: + ... + +def j(a: List[Union[Callable[..., Tuple[T, T]], int]]) -> T: + ... From 64b183423abfd419a7ae94c5a8cf68f12c5bf2f8 Mon Sep 17 00:00:00 2001 From: Anil Tuncel Date: Mon, 18 Jul 2022 17:33:40 +0200 Subject: [PATCH 17/18] microoptimise check_unbound_return_typevar check ret_type first --- mypy/checker.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 02cbd4846d95..c131e80d47f0 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1067,12 +1067,13 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str]) def check_unbound_return_typevar(self, typ: CallableType) -> None: """Fails when the return typevar is not defined in arguments.""" - arg_type_visitor = CollectArgTypes() - for argtype in typ.arg_types: - argtype.accept(arg_type_visitor) + if (typ.ret_type in typ.variables): + arg_type_visitor = CollectArgTypes() + for argtype in typ.arg_types: + argtype.accept(arg_type_visitor) - if typ.ret_type not in arg_type_visitor.arg_types and typ.ret_type in typ.variables: - self.fail(message_registry.UNBOUND_TYPEVAR, typ.ret_type, code=TYPE_VAR) + if typ.ret_type not in arg_type_visitor.arg_types: + self.fail(message_registry.UNBOUND_TYPEVAR, typ.ret_type, code=TYPE_VAR) def check_default_args(self, item: FuncItem, body_is_trivial: bool) -> None: for arg in item.arguments: From 4352bc3d8c4f3c1464173ac8254f58bb9c57e90b Mon Sep 17 00:00:00 2001 From: Anil Tuncel Date: Mon, 18 Jul 2022 23:43:53 +0200 Subject: [PATCH 18/18] add missing builtins fixture to failing test --- test-data/unit/check-typevar-unbound.test | 1 + 1 file changed, 1 insertion(+) diff --git a/test-data/unit/check-typevar-unbound.test b/test-data/unit/check-typevar-unbound.test index 34fd0e0de4ff..a233a9c7af13 100644 --- a/test-data/unit/check-typevar-unbound.test +++ b/test-data/unit/check-typevar-unbound.test @@ -57,3 +57,4 @@ def h(a: List[Union[Callable[..., T]]]) -> T: def j(a: List[Union[Callable[..., Tuple[T, T]], int]]) -> T: ... +[builtins fixtures/tuple.pyi]