Skip to content

Commit 45aa599

Browse files
authored
Do not abort constructing TypeAlias if only type parameters hold us back. (#20162)
Fixes #20135. I am not fully certain that this is the right way. If the type alias can be reasonably constructed and only type parameters prevent that, we can create an "almost" equivalent alias with all placeholder-bearing typevars replaced with their "simple" equivalents with default values/bound/default values. This would cause current iteration to not emit some parameterization errors later, but, since we already defer the alias as a whole, we will recheck all of them again. This obviously adds some pointless work (we check parameterization that will not be used later), but probably is not that big of a deal, recursive aliases are relatively rare in wild. If this turns out to be a bottleneck, we can add a `parameters_ready` flag to aliases and skip the heavy parts if it is False, but I think it isn't necessary now.
1 parent 9ed7bb4 commit 45aa599

File tree

3 files changed

+78
-4
lines changed

3 files changed

+78
-4
lines changed

mypy/semanal.py

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5637,13 +5637,20 @@ def visit_type_alias_stmt(self, s: TypeAliasStmt) -> None:
56375637
else:
56385638
incomplete_target = has_placeholder(res)
56395639

5640-
incomplete_tv = any(has_placeholder(tv) for tv in alias_tvars)
5641-
if self.found_incomplete_ref(tag) or incomplete_target or incomplete_tv:
5640+
if self.found_incomplete_ref(tag) or incomplete_target:
56425641
# Since we have got here, we know this must be a type alias (incomplete refs
56435642
# may appear in nested positions), therefore use becomes_typeinfo=True.
56445643
self.mark_incomplete(s.name.name, s.value, becomes_typeinfo=True)
56455644
return
56465645

5646+
# Now go through all new variables and temporary replace all tvars that still
5647+
# refer to some placeholders. We defer the whole alias and will revisit it again,
5648+
# as well as all its dependents.
5649+
for i, tv in enumerate(alias_tvars):
5650+
if has_placeholder(tv):
5651+
self.mark_incomplete(s.name.name, s.value, becomes_typeinfo=True)
5652+
alias_tvars[i] = self._trivial_typevarlike_like(tv)
5653+
56475654
self.add_type_alias_deps(depends_on)
56485655
check_for_explicit_any(
56495656
res, self.options, self.is_typeshed_stub_file, self.msg, context=s
@@ -5677,7 +5684,10 @@ def visit_type_alias_stmt(self, s: TypeAliasStmt) -> None:
56775684
):
56785685
updated = False
56795686
if isinstance(existing.node, TypeAlias):
5680-
if existing.node.target != res:
5687+
if (
5688+
existing.node.target != res
5689+
or existing.node.alias_tvars != alias_node.alias_tvars
5690+
):
56815691
# Copy expansion to the existing alias, this matches how we update base classes
56825692
# for a TypeInfo _in place_ if there are nested placeholders.
56835693
existing.node.target = res
@@ -5707,6 +5717,46 @@ def visit_type_alias_stmt(self, s: TypeAliasStmt) -> None:
57075717
finally:
57085718
self.pop_type_args(s.type_args)
57095719

5720+
def _trivial_typevarlike_like(self, tv: TypeVarLikeType) -> TypeVarLikeType:
5721+
object_type = self.named_type("builtins.object")
5722+
if isinstance(tv, TypeVarType):
5723+
return TypeVarType(
5724+
tv.name,
5725+
tv.fullname,
5726+
tv.id,
5727+
values=[],
5728+
upper_bound=object_type,
5729+
default=AnyType(TypeOfAny.from_omitted_generics),
5730+
variance=tv.variance,
5731+
line=tv.line,
5732+
column=tv.column,
5733+
)
5734+
elif isinstance(tv, TypeVarTupleType):
5735+
tuple_type = self.named_type("builtins.tuple", [object_type])
5736+
return TypeVarTupleType(
5737+
tv.name,
5738+
tv.fullname,
5739+
tv.id,
5740+
upper_bound=tuple_type,
5741+
tuple_fallback=tuple_type,
5742+
default=AnyType(TypeOfAny.from_omitted_generics),
5743+
line=tv.line,
5744+
column=tv.column,
5745+
)
5746+
elif isinstance(tv, ParamSpecType):
5747+
return ParamSpecType(
5748+
tv.name,
5749+
tv.fullname,
5750+
tv.id,
5751+
flavor=tv.flavor,
5752+
upper_bound=object_type,
5753+
default=AnyType(TypeOfAny.from_omitted_generics),
5754+
line=tv.line,
5755+
column=tv.column,
5756+
)
5757+
else:
5758+
assert False, f"Unknown TypeVarLike: {tv!r}"
5759+
57105760
#
57115761
# Expressions
57125762
#

mypy/semanal_typeargs.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ def validate_args(
151151

152152
is_error = False
153153
is_invalid = False
154-
for (i, arg), tvar in zip(enumerate(args), type_vars):
154+
for arg, tvar in zip(args, type_vars):
155155
context = ctx if arg.line < 0 else arg
156156
if isinstance(tvar, TypeVarType):
157157
if isinstance(arg, ParamSpecType):

test-data/unit/check-python312.test

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2057,6 +2057,7 @@ reveal_type(AA.XX) # N: Revealed type is "def () -> __main__.XX"
20572057
y: B.Y
20582058
reveal_type(y) # N: Revealed type is "__main__.Y"
20592059
[builtins fixtures/tuple.pyi]
2060+
[typing fixtures/typing-full.pyi]
20602061

20612062
[case testPEP695TypeAliasRecursiveInvalid]
20622063
type X = X # E: Cannot resolve name "X" (possible cyclic definition)
@@ -2168,3 +2169,26 @@ x: MyTuple[int, str]
21682169
reveal_type(x[0]) # N: Revealed type is "Any"
21692170
[builtins fixtures/tuple.pyi]
21702171
[typing fixtures/typing-full.pyi]
2172+
2173+
[case testPEP695TypeAliasRecursiveInParameterBound]
2174+
from typing import Any
2175+
2176+
type A1[T: B1] = list[int]
2177+
type B1 = None | A1[B1]
2178+
x1: A1[B1]
2179+
y1: A1[int] # E: Type argument "int" of "A1" must be a subtype of "B1"
2180+
z1: A1[None]
2181+
2182+
type A2[T: B2] = list[T]
2183+
type B2 = None | A2[Any]
2184+
x2: A2[B2]
2185+
y2: A2[int] # E: Type argument "int" of "A2" must be a subtype of "B2"
2186+
z2: A2[None]
2187+
2188+
type A3[T: B3] = list[T]
2189+
type B3 = None | A3[B3]
2190+
x3: A3[B3]
2191+
y3: A3[int] # E: Type argument "int" of "A3" must be a subtype of "B3"
2192+
z3: A3[None]
2193+
[builtins fixtures/tuple.pyi]
2194+
[typing fixtures/typing-full.pyi]

0 commit comments

Comments
 (0)