Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 53 additions & 3 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -5637,13 +5637,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
Expand Down Expand Up @@ -5677,7 +5684,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
Expand Down Expand Up @@ -5707,6 +5717,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
#
Expand Down
2 changes: 1 addition & 1 deletion mypy/semanal_typeargs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change does not belong here, but I don't think it warrants a separate PR - i was just unused, I noticed it when tracing a PlaceholderType

context = ctx if arg.line < 0 else arg
if isinstance(tvar, TypeVarType):
if isinstance(arg, ParamSpecType):
Expand Down
24 changes: 24 additions & 0 deletions test-data/unit/check-python312.test
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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]