From 3578d64bcf483323373e93a7d2689a8ce47ee00d Mon Sep 17 00:00:00 2001 From: STerliakov Date: Sat, 1 Nov 2025 21:40:25 +0100 Subject: [PATCH] Constructing the alias if only type variables cause its deferral --- mypy/semanal.py | 56 +++++++++++++++++++++++++++-- mypy/semanal_typeargs.py | 2 +- test-data/unit/check-python312.test | 24 +++++++++++++ 3 files changed, 78 insertions(+), 4 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index d7b50bd09496..7cad77ff63f9 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -5591,13 +5591,20 @@ def visit_type_alias_stmt(self, s: TypeAliasStmt) -> None: else: incomplete_target = has_placeholder(res) - incomplete_tv = any(has_placeholder(tv) for tv in alias_tvars) - if self.found_incomplete_ref(tag) or incomplete_target or incomplete_tv: + if self.found_incomplete_ref(tag) or incomplete_target: # Since we have got here, we know this must be a type alias (incomplete refs # may appear in nested positions), therefore use becomes_typeinfo=True. self.mark_incomplete(s.name.name, s.value, becomes_typeinfo=True) return + # Now go through all new variables and temporary replace all tvars that still + # refer to some placeholders. We defer the whole alias and will revisit it again, + # as well as all its dependents. + for i, tv in enumerate(alias_tvars): + if has_placeholder(tv): + self.mark_incomplete(s.name.name, s.value, becomes_typeinfo=True) + alias_tvars[i] = self._trivial_typevarlike_like(tv) + self.add_type_alias_deps(depends_on) check_for_explicit_any( res, self.options, self.is_typeshed_stub_file, self.msg, context=s @@ -5631,7 +5638,10 @@ def visit_type_alias_stmt(self, s: TypeAliasStmt) -> None: ): updated = False if isinstance(existing.node, TypeAlias): - if existing.node.target != res: + if ( + existing.node.target != res + or existing.node.alias_tvars != alias_node.alias_tvars + ): # Copy expansion to the existing alias, this matches how we update base classes # for a TypeInfo _in place_ if there are nested placeholders. existing.node.target = res @@ -5661,6 +5671,46 @@ def visit_type_alias_stmt(self, s: TypeAliasStmt) -> None: finally: self.pop_type_args(s.type_args) + def _trivial_typevarlike_like(self, tv: TypeVarLikeType) -> TypeVarLikeType: + object_type = self.named_type("builtins.object") + if isinstance(tv, TypeVarType): + return TypeVarType( + tv.name, + tv.fullname, + tv.id, + values=[], + upper_bound=object_type, + default=AnyType(TypeOfAny.from_omitted_generics), + variance=tv.variance, + line=tv.line, + column=tv.column, + ) + elif isinstance(tv, TypeVarTupleType): + tuple_type = self.named_type("builtins.tuple", [object_type]) + return TypeVarTupleType( + tv.name, + tv.fullname, + tv.id, + upper_bound=tuple_type, + tuple_fallback=tuple_type, + default=AnyType(TypeOfAny.from_omitted_generics), + line=tv.line, + column=tv.column, + ) + elif isinstance(tv, ParamSpecType): + return ParamSpecType( + tv.name, + tv.fullname, + tv.id, + flavor=tv.flavor, + upper_bound=object_type, + default=AnyType(TypeOfAny.from_omitted_generics), + line=tv.line, + column=tv.column, + ) + else: + assert False, f"Unknown TypeVarLike: {tv!r}" + # # Expressions # diff --git a/mypy/semanal_typeargs.py b/mypy/semanal_typeargs.py index 686e7a57042d..86f8a8700def 100644 --- a/mypy/semanal_typeargs.py +++ b/mypy/semanal_typeargs.py @@ -151,7 +151,7 @@ def validate_args( is_error = False is_invalid = False - for (i, arg), tvar in zip(enumerate(args), type_vars): + for arg, tvar in zip(args, type_vars): context = ctx if arg.line < 0 else arg if isinstance(tvar, TypeVarType): if isinstance(arg, ParamSpecType): diff --git a/test-data/unit/check-python312.test b/test-data/unit/check-python312.test index be46ff6ee5c0..840a708fecf3 100644 --- a/test-data/unit/check-python312.test +++ b/test-data/unit/check-python312.test @@ -2057,6 +2057,7 @@ reveal_type(AA.XX) # N: Revealed type is "def () -> __main__.XX" y: B.Y reveal_type(y) # N: Revealed type is "__main__.Y" [builtins fixtures/tuple.pyi] +[typing fixtures/typing-full.pyi] [case testPEP695TypeAliasRecursiveInvalid] type X = X # E: Cannot resolve name "X" (possible cyclic definition) @@ -2168,3 +2169,26 @@ x: MyTuple[int, str] reveal_type(x[0]) # N: Revealed type is "Any" [builtins fixtures/tuple.pyi] [typing fixtures/typing-full.pyi] + +[case testPEP695TypeAliasRecursiveInParameterBound] +from typing import Any + +type A1[T: B1] = list[int] +type B1 = None | A1[B1] +x1: A1[B1] +y1: A1[int] # E: Type argument "int" of "A1" must be a subtype of "B1" +z1: A1[None] + +type A2[T: B2] = list[T] +type B2 = None | A2[Any] +x2: A2[B2] +y2: A2[int] # E: Type argument "int" of "A2" must be a subtype of "B2" +z2: A2[None] + +type A3[T: B3] = list[T] +type B3 = None | A3[B3] +x3: A3[B3] +y3: A3[int] # E: Type argument "int" of "A3" must be a subtype of "B3" +z3: A3[None] +[builtins fixtures/tuple.pyi] +[typing fixtures/typing-full.pyi]