diff --git a/mypy/message_registry.py b/mypy/message_registry.py index 82885065934f..86b37a1cd948 100644 --- a/mypy/message_registry.py +++ b/mypy/message_registry.py @@ -181,6 +181,7 @@ def with_additional_msg(self, info: str) -> ErrorMessage: 'Implicit generic "Any". Use "{}" and specify generic parameters' ) NO_CYCLIC_DEFAULT: Final = "Cyclic type variable defaults are not supported" +NO_DEFAULT_AFTER_TYPEVAR_TUPLE: Final = "A type variable with default cannot follow TypVarTuple" INVALID_UNPACK: Final = "{} cannot be unpacked (must be tuple or TypeVarTuple)" INVALID_UNPACK_POSITION: Final = "Unpack is only valid in a variadic position" INVALID_PARAM_SPEC_LOCATION: Final = "Invalid location for ParamSpec {}" diff --git a/mypy/semanal.py b/mypy/semanal.py index 84ac54bfba72..d67fff8bc48e 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -546,8 +546,13 @@ def __init__( # to create the set lazily. self.types_fixed: set[TypeInfo | TypeAlias] | None = None + # Stack of type variables that have been removed from current class because they + # cannot be bound unambiguously. This can happen if a (regular) type variable + # with a default follows a type variable tuple. + self.removed_type_vars: list[list[TypeVarType]] = [[]] + # mypyc doesn't properly handle implementing an abstractproperty - # with a regular attribute so we make them properties + # with a regular attribute, so we make them properties @property def type(self) -> TypeInfo | None: return self._type @@ -1836,8 +1841,9 @@ def visit_class_def(self, defn: ClassDef) -> None: if self.push_type_args(defn.type_args, defn) is None: self.mark_incomplete(defn.name, defn) return - + self.removed_type_vars.append([]) self.analyze_class(defn) + self.removed_type_vars.pop() self.pop_type_args(defn.type_args) self.incomplete_type_stack.pop() @@ -2084,7 +2090,21 @@ def check_type_alias_bases(self, bases: list[Expression]) -> None: ) def setup_type_vars(self, defn: ClassDef, tvar_defs: list[TypeVarLikeType]) -> None: - defn.type_vars = tvar_defs + seen_tvt = False + valid_tvar_defs = [] + for tv in tvar_defs: + if seen_tvt and isinstance(tv, TypeVarType) and tv.has_default(): + self.fail( + message_registry.NO_DEFAULT_AFTER_TYPEVAR_TUPLE, defn, code=codes.TYPE_VAR + ) + # Remove the ambiguous type variable, and record it, so that we can replace + # all its uses with Any. + self.removed_type_vars[-1].append(tv) + continue + if isinstance(tv, TypeVarTupleType): + seen_tvt = True + valid_tvar_defs.append(tv) + defn.type_vars = valid_tvar_defs defn.info.type_vars = [] # we want to make sure any additional logic in add_type_vars gets run defn.info.add_type_vars() @@ -4017,6 +4037,28 @@ def analyze_alias( with self.allow_unbound_tvars_set(): rvalue.accept(self) + new_tvar_defs = [] + erase_tvar_defs = [] + variadic = False + for td in tvar_defs: + if variadic and isinstance(td, TypeVarType) and td.has_default(): + self.fail( + message_registry.NO_DEFAULT_AFTER_TYPEVAR_TUPLE, + rvalue, + code=codes.TYPE_VAR, + ) + # Remove the ambiguous type variable, and record it, so that we can + # replace all its uses with Any. + erase_tvar_defs.append(td) + continue + if isinstance(td, TypeVarTupleType): + # There can be only one variadic variable at most, + # the error is reported elsewhere. + if variadic: + continue + variadic = True + new_tvar_defs.append(td) + analyzed, depends_on = analyze_type_alias( typ, self, @@ -4029,20 +4071,11 @@ def analyze_alias( in_dynamic_func=dynamic, global_scope=global_scope, allowed_alias_tvars=tvar_defs, + erase_tvar_defs=erase_tvar_defs, alias_type_params_names=all_declared_type_params_names, python_3_12_type_alias=python_3_12_type_alias, ) - # There can be only one variadic variable at most, the error is reported elsewhere. - new_tvar_defs = [] - variadic = False - for td in tvar_defs: - if isinstance(td, TypeVarTupleType): - if variadic: - continue - variadic = True - new_tvar_defs.append(td) - indexed = bool(isinstance(typ, UnboundType) and (typ.args or typ.empty_tuple_index)) default_depends = {} for _, tv in alias_type_vars: @@ -7789,6 +7822,7 @@ def type_analyzer( prohibit_special_class_field_types=prohibit_special_class_field_types, allow_type_any=allow_type_any, analyzing_tvar_def=analyzing_tvar_def, + erase_tvar_defs=self.removed_type_vars[-1], ) tpan.in_dynamic_func = bool(self.function_stack and self.function_stack[-1].is_dynamic()) tpan.global_scope = not self.type and not self.function_stack diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 3d0bda77fe39..a4aca77cb421 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -154,6 +154,7 @@ def analyze_type_alias( in_dynamic_func: bool = False, global_scope: bool = True, allowed_alias_tvars: list[TypeVarLikeType] | None = None, + erase_tvar_defs: list[TypeVarType] | None = None, alias_type_params_names: list[str] | None = None, python_3_12_type_alias: bool = False, ) -> tuple[Type, set[str]]: @@ -174,6 +175,7 @@ def analyze_type_alias( allow_placeholder=allow_placeholder, prohibit_self_type="type alias target", allowed_alias_tvars=allowed_alias_tvars, + erase_tvar_defs=erase_tvar_defs, alias_type_params_names=alias_type_params_names, python_3_12_type_alias=python_3_12_type_alias, ) @@ -220,6 +222,7 @@ def __init__( prohibit_self_type: str | None = None, prohibit_special_class_field_types: str | None = None, allowed_alias_tvars: list[TypeVarLikeType] | None = None, + erase_tvar_defs: list[TypeVarType] | None = None, allow_type_any: bool = False, alias_type_params_names: list[str] | None = None, analyzing_tvar_def: bool = False, @@ -240,6 +243,11 @@ def __init__( if allowed_alias_tvars is None: allowed_alias_tvars = [] self.allowed_alias_tvars = allowed_alias_tvars + # Should we erase some type variables? This can be used to mass-erase type + # variables that were found to be invalid at the class/alias definition. + if erase_tvar_defs is None: + erase_tvar_defs = [] + self.erase_tvar_defs = erase_tvar_defs self.alias_type_params_names = alias_type_params_names # If false, record incomplete ref if we generate PlaceholderType. self.allow_placeholder = allow_placeholder @@ -403,6 +411,13 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) msg = f'Can\'t use bound type variable "{t.name}" to define generic alias' self.fail(msg, t, code=codes.VALID_TYPE) return AnyType(TypeOfAny.from_error) + if ( + isinstance(sym.node, TypeVarExpr) + and tvar_def is not None + and tvar_def in self.erase_tvar_defs + ): + # The caller should have already given a relevant error. + return AnyType(TypeOfAny.from_error) if isinstance(sym.node, TypeVarExpr) and tvar_def is not None: assert isinstance(tvar_def, TypeVarType) if len(t.args) > 0: @@ -2105,7 +2120,10 @@ def fix_instance( """ used_default = False arg_count = len(t.args) - min_tv_count = sum(not tv.has_default() for tv in t.type.defn.type_vars) + min_tv_count = sum( + not tv.has_default() and not isinstance(tv, TypeVarTupleType) + for tv in t.type.defn.type_vars + ) max_tv_count = len(t.type.type_vars) if arg_count < min_tv_count or arg_count > max_tv_count: # Don't use existing args if arg_count doesn't match @@ -2114,9 +2132,10 @@ def fix_instance( disallow_any = False t.args = () - args: list[Type] = [*(t.args[:max_tv_count])] + args: list[Type] = list(t.args) any_type: AnyType | None = None env: dict[TypeVarId, Type] = {} + tvt_no_default = False for tv, arg in itertools.zip_longest(t.type.defn.type_vars, t.args, fillvalue=None): if tv is None: @@ -2149,16 +2168,27 @@ def fix_instance( arg = any_type else: assert arg is not None - with state.strict_optional_set(options.strict_optional): - # Gradually expand defaults, as they may depend on previous variables. - if tv.has_default(): - arg = expand_type(arg, env) - env[tv.id] = arg - args.append(arg) + if use_any and isinstance(tv, TypeVarTupleType): + tvt_no_default = True + # Default such as *tuple[int, str] should be unpacked into individual items. + if isinstance(arg, UnpackType) and isinstance( + unpack := get_proper_type(arg.type), TupleType + ): + unpacked = unpack.items + else: + unpacked = [arg] + for arg in unpacked: + with state.strict_optional_set(options.strict_optional): + # Gradually expand defaults, as they may depend on previous variables. + if tv.has_default(): + arg = expand_type(arg, env) + env[tv.id] = arg + args.append(arg) else: env[tv.id] = arg t.args = tuple(args) - fix_type_var_tuple_argument(t) + if tvt_no_default: + fix_type_var_tuple_argument(t) return used_default @@ -2374,15 +2404,22 @@ def set_any_tvars( else: arg = any_type used_any_type = True - if isinstance(tv, TypeVarTupleType): - # TODO Handle TypeVarTuple defaults + if used_any_type and isinstance(tv, TypeVarTupleType): arg = UnpackType(Instance(tv.tuple_fallback.type, [any_type])) - with state.strict_optional_set(options.strict_optional): - # Gradually expand defaults, as they may depend on previous variables. - if tv.has_default(): - arg = expand_type(arg, env) - env[tv.id] = arg - args.append(arg) + # Default such as *tuple[int, str] should be unpacked into individual items. + if isinstance(arg, UnpackType) and isinstance( + unpack := get_proper_type(arg.type), TupleType + ): + unpacked = unpack.items + else: + unpacked = [arg] + for arg in unpacked: + with state.strict_optional_set(options.strict_optional): + # Gradually expand defaults, as they may depend on previous variables. + if tv.has_default(): + arg = expand_type(arg, env) + env[tv.id] = arg + args.append(arg) else: env[tv.id] = arg t = TypeAliasType(node, args, newline, newcolumn) diff --git a/test-data/unit/check-python313.test b/test-data/unit/check-python313.test index 497b293e48a3..0b15dc56d5f6 100644 --- a/test-data/unit/check-python313.test +++ b/test-data/unit/check-python313.test @@ -140,7 +140,7 @@ reveal_type(func_b1(callback1)) # N: Revealed type is "def (x: builtins.str)" reveal_type(func_b1(2)) # N: Revealed type is "def (builtins.int, builtins.str)" def func_c1[*Ts = *tuple[int, str]](x: int | Callable[[*Ts], None]) -> tuple[*Ts]: ... -# reveal_type(func_c1(callback1)) # Revealed type is "Tuple[str]" # TODO +reveal_type(func_c1(callback1)) # N: Revealed type is "tuple[builtins.str]" reveal_type(func_c1(2)) # N: Revealed type is "tuple[builtins.int, builtins.str]" [builtins fixtures/tuple.pyi] @@ -195,7 +195,7 @@ def func_c1( a: ClassC1, b: ClassC1[float], ) -> None: - # reveal_type(a) # Revealed type is "__main__.ClassC1[builtins.int, builtins.str]" # TODO + reveal_type(a) # N: Revealed type is "__main__.ClassC1[builtins.int, builtins.str]" reveal_type(b) # N: Revealed type is "__main__.ClassC1[builtins.float]" k = ClassC1() @@ -250,7 +250,7 @@ def func_c1( a: TC1, b: TC1[float], ) -> None: - # reveal_type(a) # Revealed type is "Tuple[builtins.int, builtins.str]" # TODO + reveal_type(a) # N: Revealed type is "tuple[builtins.int, builtins.str]" reveal_type(b) # N: Revealed type is "tuple[builtins.float]" [builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-typevar-defaults.test b/test-data/unit/check-typevar-defaults.test index 2f454b70747f..b6c2c573d91d 100644 --- a/test-data/unit/check-typevar-defaults.test +++ b/test-data/unit/check-typevar-defaults.test @@ -362,7 +362,7 @@ def func_c1( a: ClassC1, b: ClassC1[float], ) -> None: - # reveal_type(a) # Revealed type is "__main__.ClassC1[builtins.int, builtins.str]" # TODO + reveal_type(a) # N: Revealed type is "__main__.ClassC1[builtins.int, builtins.str]" reveal_type(b) # N: Revealed type is "__main__.ClassC1[builtins.float]" k = ClassC1() @@ -375,18 +375,14 @@ class ClassC2(Generic[T3, Unpack[Ts3]]): ... def func_c2( a: ClassC2, b: ClassC2[int], - c: ClassC2[int, Unpack[Tuple[()]]], ) -> None: reveal_type(a) # N: Revealed type is "__main__.ClassC2[builtins.str, Unpack[builtins.tuple[builtins.float, ...]]]" - # reveal_type(b) # Revealed type is "__main__.ClassC2[builtins.int, Unpack[builtins.tuple[builtins.float, ...]]]" # TODO - reveal_type(c) # N: Revealed type is "__main__.ClassC2[builtins.int]" + reveal_type(b) # N: Revealed type is "__main__.ClassC2[builtins.int]" k = ClassC2() reveal_type(k) # N: Revealed type is "__main__.ClassC2[builtins.str, Unpack[builtins.tuple[builtins.float, ...]]]" l = ClassC2[int]() - # reveal_type(l) # Revealed type is "__main__.ClassC2[builtins.int, Unpack[builtins.tuple[builtins.float, ...]]]" # TODO - m = ClassC2[int, Unpack[Tuple[()]]]() - reveal_type(m) # N: Revealed type is "__main__.ClassC2[builtins.int]" + reveal_type(l) # N: Revealed type is "__main__.ClassC2[builtins.int]" class ClassC3(Generic[T3, Unpack[Ts4]]): ... @@ -395,7 +391,7 @@ def func_c3( b: ClassC3[int], c: ClassC3[int, Unpack[Tuple[float]]] ) -> None: - # reveal_type(a) # Revealed type is "__main__.ClassC3[builtins.str]" # TODO + reveal_type(a) # N: Revealed type is "__main__.ClassC3[builtins.str]" reveal_type(b) # N: Revealed type is "__main__.ClassC3[builtins.int]" reveal_type(c) # N: Revealed type is "__main__.ClassC3[builtins.int, builtins.float]" @@ -406,21 +402,24 @@ def func_c3( m = ClassC3[int, Unpack[Tuple[float]]]() reveal_type(m) # N: Revealed type is "__main__.ClassC3[builtins.int, builtins.float]" -class ClassC4(Generic[T1, Unpack[Ts1], T3]): ... +class ClassC4(Generic[T1, Unpack[Ts1], T3]): # E: A type variable with default cannot follow TypVarTuple + x: T3 + +reveal_type(ClassC4().x) # N: Revealed type is "Any" def func_c4( a: ClassC4, # E: Missing type arguments for generic type "ClassC4" b: ClassC4[int], c: ClassC4[int, float], ) -> None: - reveal_type(a) # N: Revealed type is "__main__.ClassC4[Any, Unpack[builtins.tuple[Any, ...]], builtins.str]" - # reveal_type(b) # Revealed type is "__main__.ClassC4[builtins.int, builtins.str]" # TODO + reveal_type(a) # N: Revealed type is "__main__.ClassC4[Any, Unpack[builtins.tuple[Any, ...]]]" + reveal_type(b) # N: Revealed type is "__main__.ClassC4[builtins.int]" reveal_type(c) # N: Revealed type is "__main__.ClassC4[builtins.int, builtins.float]" k = ClassC4() # E: Need type annotation for "k" - reveal_type(k) # N: Revealed type is "__main__.ClassC4[Any, Unpack[builtins.tuple[Any, ...]], builtins.str]" + reveal_type(k) # N: Revealed type is "__main__.ClassC4[Any, Unpack[builtins.tuple[Any, ...]]]" l = ClassC4[int]() - # reveal_type(l) # Revealed type is "__main__.ClassC4[builtins.int, builtins.str]" # TODO + reveal_type(l) # N: Revealed type is "__main__.ClassC4[builtins.int]" m = ClassC4[int, float]() reveal_type(m) # N: Revealed type is "__main__.ClassC4[builtins.int, builtins.float]" [builtins fixtures/tuple.pyi] @@ -678,7 +677,7 @@ def func_c1( a: TC1, b: TC1[float], ) -> None: - # reveal_type(a) # Revealed type is "Tuple[builtins.int, builtins.str]" # TODO + reveal_type(a) # N: Revealed type is "tuple[builtins.int, builtins.str]" reveal_type(b) # N: Revealed type is "tuple[builtins.float]" TC2 = Tuple[T3, Unpack[Ts3]] @@ -686,11 +685,9 @@ TC2 = Tuple[T3, Unpack[Ts3]] def func_c2( a: TC2, b: TC2[int], - c: TC2[int, Unpack[Tuple[()]]], ) -> None: - # reveal_type(a) # Revealed type is "Tuple[builtins.str, Unpack[builtins.tuple[builtins.float, ...]]]" # TODO - # reveal_type(b) # Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]]]" # TODO - reveal_type(c) # N: Revealed type is "tuple[builtins.int]" + reveal_type(a) # N: Revealed type is "tuple[builtins.str, Unpack[builtins.tuple[builtins.float, ...]]]" + reveal_type(b) # N: Revealed type is "tuple[builtins.int]" TC3 = Tuple[T3, Unpack[Ts4]] @@ -699,20 +696,20 @@ def func_c3( b: TC3[int], c: TC3[int, Unpack[Tuple[float]]], ) -> None: - # reveal_type(a) # Revealed type is "Tuple[builtins.str]" # TODO + reveal_type(a) # N: Revealed type is "tuple[builtins.str]" reveal_type(b) # N: Revealed type is "tuple[builtins.int]" reveal_type(c) # N: Revealed type is "tuple[builtins.int, builtins.float]" -TC4 = Tuple[T1, Unpack[Ts1], T3] +TC4 = Tuple[T1, Unpack[Ts1], T3] # E: A type variable with default cannot follow TypVarTuple def func_c4( a: TC4, # E: Missing type arguments for generic type "TC4" b: TC4[int], c: TC4[int, float], ) -> None: - reveal_type(a) # N: Revealed type is "tuple[Any, Unpack[builtins.tuple[Any, ...]], builtins.str]" - # reveal_type(b) # Revealed type is "Tuple[builtins.int, builtins.str]" # TODO - reveal_type(c) # N: Revealed type is "tuple[builtins.int, builtins.float]" + reveal_type(a) # N: Revealed type is "tuple[Any, Unpack[builtins.tuple[Any, ...]], Any]" + reveal_type(b) # N: Revealed type is "tuple[builtins.int, Any]" + reveal_type(c) # N: Revealed type is "tuple[builtins.int, builtins.float, Any]" [builtins fixtures/tuple.pyi] [case testTypeVarDefaultsTypeAliasRecursive1]