diff --git a/mypy/applytype.py b/mypy/applytype.py index e87bf939c81a..cdf38ddfa00e 100644 --- a/mypy/applytype.py +++ b/mypy/applytype.py @@ -90,6 +90,8 @@ def apply_generic_arguments( report_incompatible_typevar_value: Callable[[CallableType, Type, str, Context], None], context: Context, skip_unsatisfied: bool = False, + *, + prefer_defaults: bool = False, ) -> CallableType: """Apply generic type arguments to a callable type. @@ -100,6 +102,9 @@ def apply_generic_arguments( If `skip_unsatisfied` is True, then just skip the types that don't satisfy type variable bound or constraints, instead of giving an error. + + If `prefer_defaults` is True, we will use defaults of all variables that have one + instead of making the result generic in those. """ tvars = callable.variables assert len(orig_types) <= len(tvars) @@ -140,10 +145,6 @@ def apply_generic_arguments( callable = expand_type(callable, id_to_type) assert isinstance(callable, CallableType) return callable.copy_modified(variables=[tv for tv in tvars if tv.id not in id_to_type]) - else: - callable = callable.copy_modified( - arg_types=[expand_type(at, id_to_type) for at in callable.arg_types] - ) # Apply arguments to TypeGuard and TypeIs if any. if callable.type_guard is not None: @@ -167,12 +168,16 @@ def apply_generic_arguments( remaining_tvars.append(tv) continue # TypeVarLike isn't in id_to_type mapping. - # Only expand the TypeVar default here. - typ = expand_type(tv, id_to_type) - assert isinstance(typ, TypeVarLikeType) - remaining_tvars.append(typ) + if prefer_defaults: + # We were asked to avoid polymorphic results as much as possible. + id_to_type[tv.id] = expand_type(tv.default, id_to_type) + else: + # Only expand the TypeVar default here. + typ = tv.copy_modified(id=tv.id, default=expand_type(tv.default, id_to_type)) + remaining_tvars.append(typ) return callable.copy_modified( + arg_types=[expand_type(at, id_to_type) for at in callable.arg_types], ret_type=expand_type(callable.ret_type, id_to_type), variables=remaining_tvars, type_guard=type_guard, diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 8223ccfe4ca0..23efc71e1fd5 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -3292,6 +3292,8 @@ def apply_generic_arguments( types: Sequence[Type | None], context: Context, skip_unsatisfied: bool = False, + *, + prefer_defaults: bool = False, ) -> CallableType: """Simple wrapper around mypy.applytype.apply_generic_arguments.""" return applytype.apply_generic_arguments( @@ -3300,6 +3302,7 @@ def apply_generic_arguments( self.msg.incompatible_typevar_value, context, skip_unsatisfied=skip_unsatisfied, + prefer_defaults=prefer_defaults, ) def check_any_type_call(self, args: list[Expression], callee: Type) -> tuple[Type, Type]: @@ -5021,7 +5024,9 @@ def apply_type_arguments_to_callable( min_arg_count, len(type_vars), len(args), ctx ) return AnyType(TypeOfAny.from_error) - return self.apply_generic_arguments(tp, self.split_for_callable(tp, args, ctx), ctx) + return self.apply_generic_arguments( + tp, self.split_for_callable(tp, args, ctx), ctx, prefer_defaults=True + ) if isinstance(tp, Overloaded): for it in tp.items: if tp.is_type_obj(): @@ -5040,7 +5045,9 @@ def apply_type_arguments_to_callable( return AnyType(TypeOfAny.from_error) return Overloaded( [ - self.apply_generic_arguments(it, self.split_for_callable(it, args, ctx), ctx) + self.apply_generic_arguments( + it, self.split_for_callable(it, args, ctx), ctx, prefer_defaults=True + ) for it in tp.items ] ) diff --git a/mypy/tvar_scope.py b/mypy/tvar_scope.py index fe97a8359287..b9a79a6d3066 100644 --- a/mypy/tvar_scope.py +++ b/mypy/tvar_scope.py @@ -10,31 +10,39 @@ from mypy.types import ( ParamSpecFlavor, ParamSpecType, + TrivialSyntheticTypeTranslator, + TypeAliasType, TypeVarId, TypeVarLikeType, TypeVarTupleType, TypeVarType, ) -from mypy.typetraverser import TypeTraverserVisitor -class TypeVarLikeNamespaceSetter(TypeTraverserVisitor): +class TypeVarLikeNamespaceSetter(TrivialSyntheticTypeTranslator): """Set namespace for all TypeVarLikeTypes types.""" def __init__(self, namespace: str) -> None: + super().__init__() self.namespace = namespace - def visit_type_var(self, t: TypeVarType) -> None: - t.id.namespace = self.namespace - super().visit_type_var(t) + def visit_type_var(self, t: TypeVarType) -> TypeVarType: + return t.copy_modified( + id=TypeVarId(raw_id=t.id.raw_id, meta_level=t.id.meta_level, namespace=self.namespace) + ) - def visit_param_spec(self, t: ParamSpecType) -> None: - t.id.namespace = self.namespace - return super().visit_param_spec(t) + def visit_param_spec(self, t: ParamSpecType) -> ParamSpecType: + return t.copy_modified( + id=TypeVarId(raw_id=t.id.raw_id, meta_level=t.id.meta_level, namespace=self.namespace) + ) - def visit_type_var_tuple(self, t: TypeVarTupleType) -> None: - t.id.namespace = self.namespace - super().visit_type_var_tuple(t) + def visit_type_var_tuple(self, t: TypeVarTupleType) -> TypeVarTupleType: + return t.copy_modified( + id=TypeVarId(raw_id=t.id.raw_id, meta_level=t.id.meta_level, namespace=self.namespace) + ) + + def visit_type_alias_type(self, t: TypeAliasType, /) -> TypeAliasType: + return t class TypeVarLikeScope: @@ -106,7 +114,7 @@ def bind_new(self, name: str, tvar_expr: TypeVarLikeExpr) -> TypeVarLikeType: self.func_id -= 1 i = self.func_id namespace = self.namespace - tvar_expr.default.accept(TypeVarLikeNamespaceSetter(namespace)) + default = tvar_expr.default.accept(TypeVarLikeNamespaceSetter(namespace)) if isinstance(tvar_expr, TypeVarExpr): tvar_def: TypeVarLikeType = TypeVarType( @@ -115,7 +123,7 @@ def bind_new(self, name: str, tvar_expr: TypeVarLikeExpr) -> TypeVarLikeType: id=TypeVarId(i, namespace=namespace), values=tvar_expr.values, upper_bound=tvar_expr.upper_bound, - default=tvar_expr.default, + default=default, variance=tvar_expr.variance, line=tvar_expr.line, column=tvar_expr.column, @@ -127,7 +135,7 @@ def bind_new(self, name: str, tvar_expr: TypeVarLikeExpr) -> TypeVarLikeType: id=TypeVarId(i, namespace=namespace), flavor=ParamSpecFlavor.BARE, upper_bound=tvar_expr.upper_bound, - default=tvar_expr.default, + default=default, line=tvar_expr.line, column=tvar_expr.column, ) @@ -138,7 +146,7 @@ def bind_new(self, name: str, tvar_expr: TypeVarLikeExpr) -> TypeVarLikeType: id=TypeVarId(i, namespace=namespace), upper_bound=tvar_expr.upper_bound, tuple_fallback=tvar_expr.tuple_fallback, - default=tvar_expr.default, + default=default, line=tvar_expr.line, column=tvar_expr.column, ) diff --git a/test-data/unit/check-typevar-defaults.test b/test-data/unit/check-typevar-defaults.test index 22270e17787e..ab6498225ba5 100644 --- a/test-data/unit/check-typevar-defaults.test +++ b/test-data/unit/check-typevar-defaults.test @@ -159,7 +159,7 @@ T3 = TypeVar("T3", bytes, str, default=str) P1 = ParamSpec("P1", default=[int, str]) Ts1 = TypeVarTuple("Ts1", default=Unpack[Tuple[int, str]]) -def callback1(x: str) -> None: ... +def callback1(x: int) -> None: ... def func_a1(x: Union[int, T1]) -> T1: ... reveal_type(func_a1(2)) # N: Revealed type is "builtins.str" @@ -175,15 +175,112 @@ reveal_type(func_a3(2)) # N: Revealed type is "builtins.str" def func_a4(x: Union[int, T3]) -> T3: ... reveal_type(func_a4(2)) # N: Revealed type is "builtins.str" +def func_a5(x: Union[int, Callable[[T1], None]]) -> Callable[[T1], None]: ... +reveal_type(func_a5(callback1)) # N: Revealed type is "def (builtins.int)" +reveal_type(func_a5(2)) # N: Revealed type is "def (builtins.str)" + def func_b1(x: Union[int, Callable[P1, None]]) -> Callable[P1, None]: ... -reveal_type(func_b1(callback1)) # N: Revealed type is "def (x: builtins.str)" +reveal_type(func_b1(callback1)) # N: Revealed type is "def (x: builtins.int)" reveal_type(func_b1(2)) # N: Revealed type is "def (builtins.int, builtins.str)" def func_c1(x: Union[int, Callable[[Unpack[Ts1]], None]]) -> Tuple[Unpack[Ts1]]: ... -# reveal_type(func_c1(callback1)) # Revealed type is "Tuple[str]" # TODO +# reveal_type(func_c1(callback1)) # Revealed type is "Tuple[int]" # TODO reveal_type(func_c1(2)) # N: Revealed type is "tuple[builtins.int, builtins.str]" [builtins fixtures/tuple.pyi] +[case testTypeVarDefaultsClassInit] +from typing import Generic, TypeVar, ParamSpec, List, Union, Callable, Tuple +from typing_extensions import TypeVarTuple, Unpack + +T1 = TypeVar("T1", default=str) +T2 = TypeVar("T2", bound=str, default=str) +T3 = TypeVar("T3", bytes, str, default=str) +P1 = ParamSpec("P1", default=[int, str]) +Ts1 = TypeVarTuple("Ts1", default=Unpack[Tuple[int, str]]) + +def callback1(x: int) -> None: ... + +class A1(Generic[T1]): + def __init__(self, x: Union[int, T1]) -> None: ... + +reveal_type(A1(2)) # N: Revealed type is "__main__.A1[builtins.str]" +reveal_type(A1(2.1)) # N: Revealed type is "__main__.A1[builtins.float]" + +class A2(Generic[T2]): + def __init__(self, x: Union[int, T2]) -> None: ... +reveal_type(A2(2)) # N: Revealed type is "__main__.A2[builtins.str]" + +class A3(Generic[T3]): + def __init__(self, x: Union[int, T3]) -> None: ... +reveal_type(A3(2)) # N: Revealed type is "__main__.A3[builtins.str]" + +class A4(Generic[T2]): + def __init__(self, x: Union[int, Callable[[T1], None]]) -> None: ... +reveal_type(A4(callback1)) # N: Revealed type is "__main__.A4[builtins.str]" +reveal_type(A4(2)) # N: Revealed type is "__main__.A4[builtins.str]" + +class B1(Generic[P1]): + def __init__(self, x: Union[int, Callable[P1, None]]) -> None: ... +reveal_type(B1(callback1)) # N: Revealed type is "__main__.B1[[x: builtins.int]]" +reveal_type(B1(2)) # N: Revealed type is "__main__.B1[[builtins.int, builtins.str]]" + +class C1(Generic[Unpack[Ts1]]): + def __init__(self, x: Union[int, Callable[[Unpack[Ts1]], None]]) -> None: ... +reveal_type(C1(callback1)) # Revealed type is "Tuple[int]" # TODO # N: Revealed type is "__main__.C1[builtins.int]" +reveal_type(C1(2)) # N: Revealed type is "__main__.C1[builtins.int, builtins.str]" +[builtins fixtures/tuple.pyi] + +[case testTypeVarDefaultsInTypeApplications] +from typing import Generic +from typing_extensions import TypeVar + +T1 = TypeVar("T1") +T2 = TypeVar("T2", default=T1) + +class MyClass(Generic[T1, T2]): ... + +reveal_type(MyClass[int]) # N: Revealed type is "def () -> __main__.MyClass[builtins.int, builtins.int]" + +# `T2` now defaults to `int` for `MyClass` from now on +reveal_type(MyClass[str]) # N: Revealed type is "def () -> __main__.MyClass[builtins.str, builtins.str]" +reveal_type(MyClass[bytes]) # N: Revealed type is "def () -> __main__.MyClass[builtins.bytes, builtins.bytes]" + +c_int: type[MyClass[int]] +c_str: type[MyClass[str]] +a_str = MyClass[str]() + +reveal_type(c_int) # N: Revealed type is "type[__main__.MyClass[builtins.int, builtins.int]]" +reveal_type(c_str) # N: Revealed type is "type[__main__.MyClass[builtins.str, builtins.str]]" +reveal_type(a_str) # N: Revealed type is "__main__.MyClass[builtins.str, builtins.str]" + +class MyClass2(Generic[T1, T2]): + def __init__(self, t: T2) -> None: ... + +reveal_type(MyClass2[int]) # N: Revealed type is "def (t: builtins.int) -> __main__.MyClass2[builtins.int, builtins.int]" + +# `T2` now defaults to `int` for `MyClass` from now on +reveal_type(MyClass2[str]) # N: Revealed type is "def (t: builtins.str) -> __main__.MyClass2[builtins.str, builtins.str]" +reveal_type(MyClass2[bytes]) # N: Revealed type is "def (t: builtins.bytes) -> __main__.MyClass2[builtins.bytes, builtins.bytes]" + +d_int: type[MyClass2[int]] +d_str: type[MyClass2[str]] + +reveal_type(d_int) # N: Revealed type is "type[__main__.MyClass2[builtins.int, builtins.int]]" +reveal_type(d_str) # N: Revealed type is "type[__main__.MyClass2[builtins.str, builtins.str]]" +[builtins fixtures/tuple.pyi] + +[case testDefaultsApplicationInAliasNoCrash] +# https://github.com/python/mypy/issues/19186 +from typing import Generic, TypeVar, TypeAlias + +T1 = TypeVar("T1") +T2 = TypeVar("T2", default=T1) + +Alias: TypeAlias = "MyClass[T1, T2]" + +class MyClass(Generic["T1", "T2"]): ... +[builtins fixtures/tuple.pyi] + [case testTypeVarDefaultsClass1] # flags: --disallow-any-generics from typing import Generic, TypeVar, Union, overload diff --git a/test-data/unit/lib-stub/typing.pyi b/test-data/unit/lib-stub/typing.pyi index 86d542a918ee..0f6fab38cdb1 100644 --- a/test-data/unit/lib-stub/typing.pyi +++ b/test-data/unit/lib-stub/typing.pyi @@ -28,6 +28,7 @@ NoReturn = 0 Never = 0 NewType = 0 ParamSpec = 0 +TypeAlias = 0 TypeVarTuple = 0 Unpack = 0 Self = 0