Skip to content

Commit 39b0812

Browse files
committed
Propagate analyzed to PEP695 typealias rvalues
1 parent 9274a07 commit 39b0812

File tree

3 files changed

+107
-57
lines changed

3 files changed

+107
-57
lines changed

mypy/semanal.py

Lines changed: 62 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -4041,44 +4041,11 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
40414041
eager=eager,
40424042
python_3_12_type_alias=pep_695,
40434043
)
4044-
if isinstance(s.rvalue, (IndexExpr, CallExpr, OpExpr)) and (
4045-
not isinstance(rvalue, OpExpr)
4046-
or (self.options.python_version >= (3, 10) or self.is_stub_file)
4047-
):
4048-
# Note: CallExpr is for "void = type(None)" and OpExpr is for "X | Y" union syntax.
4049-
if not isinstance(s.rvalue.analyzed, TypeAliasExpr):
4050-
# Any existing node will be updated in-place below.
4051-
s.rvalue.analyzed = TypeAliasExpr(alias_node)
4052-
s.rvalue.analyzed.line = s.line
4053-
# we use the column from resulting target, to get better location for errors
4054-
s.rvalue.analyzed.column = res.column
4055-
elif isinstance(s.rvalue, RefExpr):
4056-
s.rvalue.is_alias_rvalue = True
4044+
self._link_type_alias_to_rvalue(s.rvalue, alias_node, s.line)
40574045

40584046
if existing:
4059-
# An alias gets updated.
4060-
updated = False
4061-
if isinstance(existing.node, TypeAlias):
4062-
if existing.node.target != res:
4063-
# Copy expansion to the existing alias, this matches how we update base classes
4064-
# for a TypeInfo _in place_ if there are nested placeholders.
4065-
existing.node.target = res
4066-
existing.node.alias_tvars = alias_tvars
4067-
existing.node.no_args = no_args
4068-
updated = True
4069-
# Invalidate recursive status cache in case it was previously set.
4070-
existing.node._is_recursive = None
4071-
else:
4072-
# Otherwise just replace existing placeholder with type alias.
4073-
existing.node = alias_node
4074-
updated = True
4075-
if updated:
4076-
if self.final_iteration:
4077-
self.cannot_resolve_name(lvalue.name, "name", s)
4078-
return True
4079-
else:
4080-
# We need to defer so that this change can get propagated to base classes.
4081-
self.defer(s, force_progress=True)
4047+
if not self._update_type_alias(existing, alias_node, lvalue.name, s):
4048+
return True
40824049
else:
40834050
self.add_symbol(lvalue.name, alias_node, s)
40844051
if isinstance(rvalue, RefExpr) and isinstance(rvalue.node, TypeAlias):
@@ -4094,6 +4061,58 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
40944061
self.note("Use variable annotation syntax to define protocol members", s)
40954062
return True
40964063

4064+
def _link_type_alias_to_rvalue(
4065+
self, rvalue: Expression, alias_node: TypeAlias, line: int
4066+
) -> None:
4067+
if isinstance(rvalue, (IndexExpr, CallExpr)) or (
4068+
isinstance(rvalue, OpExpr)
4069+
and (self.options.python_version >= (3, 10) or self.is_stub_file)
4070+
):
4071+
# Note: CallExpr is for "void = type(None)" and OpExpr is for "X | Y" union syntax.
4072+
if not isinstance(rvalue.analyzed, TypeAliasExpr):
4073+
# Any existing node will be updated in-place below.
4074+
rvalue.analyzed = TypeAliasExpr(alias_node)
4075+
rvalue.analyzed.line = line
4076+
# we use the column from resulting target, to get better location for errors
4077+
rvalue.analyzed.column = alias_node.target.column
4078+
elif isinstance(rvalue, RefExpr):
4079+
rvalue.is_alias_rvalue = True
4080+
4081+
def _update_type_alias(
4082+
self,
4083+
existing: SymbolTableNode,
4084+
new: TypeAlias,
4085+
name: str,
4086+
stmt: AssignmentStmt | TypeAliasStmt,
4087+
) -> bool:
4088+
"""Store updated type alias information.
4089+
4090+
Returns `False` to indicate early exit (attempt to defer during final iteration).
4091+
"""
4092+
updated = False
4093+
if isinstance(existing.node, TypeAlias):
4094+
if existing.node.target != new.target:
4095+
# Copy expansion to the existing alias, this matches how we update base classes
4096+
# for a TypeInfo _in place_ if there are nested placeholders.
4097+
existing.node.target = new.target
4098+
existing.node.alias_tvars = new.alias_tvars
4099+
existing.node.no_args = new.no_args
4100+
updated = True
4101+
# Invalidate recursive status cache in case it was previously set.
4102+
existing.node._is_recursive = None
4103+
else:
4104+
# Otherwise just replace existing placeholder with type alias.
4105+
existing.node = new
4106+
updated = True
4107+
if updated:
4108+
if self.final_iteration:
4109+
self.cannot_resolve_name(name, "name", stmt)
4110+
return False
4111+
else:
4112+
# We need to defer so that this change can get propagated to base classes.
4113+
self.defer(stmt, force_progress=True)
4114+
return True
4115+
40974116
def check_type_alias_type_call(self, rvalue: Expression, *, name: str) -> TypeGuard[CallExpr]:
40984117
if not isinstance(rvalue, CallExpr):
40994118
return False
@@ -5548,31 +5567,18 @@ def visit_type_alias_stmt(self, s: TypeAliasStmt) -> None:
55485567
)
55495568
s.alias_node = alias_node
55505569

5570+
alias_ret = s.value.body.body[0]
5571+
assert isinstance(alias_ret, ReturnStmt)
5572+
assert alias_ret.expr is not None
5573+
self._link_type_alias_to_rvalue(alias_ret.expr, alias_node, s.line)
5574+
55515575
if (
55525576
existing
55535577
and isinstance(existing.node, (PlaceholderNode, TypeAlias))
55545578
and existing.node.line == s.line
55555579
):
5556-
updated = False
5557-
if isinstance(existing.node, TypeAlias):
5558-
if existing.node.target != res:
5559-
# Copy expansion to the existing alias, this matches how we update base classes
5560-
# for a TypeInfo _in place_ if there are nested placeholders.
5561-
existing.node.target = res
5562-
existing.node.alias_tvars = alias_tvars
5563-
updated = True
5564-
else:
5565-
# Otherwise just replace existing placeholder with type alias.
5566-
existing.node = alias_node
5567-
updated = True
5568-
5569-
if updated:
5570-
if self.final_iteration:
5571-
self.cannot_resolve_name(s.name.name, "name", s)
5572-
return
5573-
else:
5574-
# We need to defer so that this change can get propagated to base classes.
5575-
self.defer(s, force_progress=True)
5580+
if not self._update_type_alias(existing, alias_node, s.name.name, s):
5581+
return
55765582
else:
55775583
self.add_symbol(s.name.name, alias_node, s)
55785584

test-data/unit/check-python312.test

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ type MyInt2 = int
1313

1414
def h(x: MyInt2) -> MyInt2:
1515
return reveal_type(x) # N: Revealed type is "builtins.int"
16+
[builtins fixtures/tuple.pyi]
17+
[typing fixtures/typing-full.pyi]
1618

1719
[case testPEP695Class]
1820
class MyGen[T]:
@@ -49,6 +51,7 @@ def func1[T: int](x: T) -> T: ...
4951
def func2[**P](x: Callable[P, int]) -> Callable[P, str]: ...
5052
def func3[*Ts](x: tuple[*Ts]) -> tuple[int, *Ts]: ...
5153
[builtins fixtures/tuple.pyi]
54+
[typing fixtures/typing-full.pyi]
5255

5356
[case testPEP695TypeAliasType]
5457
from typing import Callable, TypeAliasType, TypeVar, TypeVarTuple
@@ -603,6 +606,7 @@ type A4 = int | str
603606
a4: A4
604607
reveal_type(a4) # N: Revealed type is "Union[builtins.int, builtins.str]"
605608
[builtins fixtures/type.pyi]
609+
[typing fixtures/typing-full.pyi]
606610

607611
[case testPEP695TypeAliasNotValidAsBaseClass]
608612
from typing import TypeAlias
@@ -635,7 +639,8 @@ class Good5(B3): pass
635639
[file m.py]
636640
type A1 = str
637641
type A2[T] = list[T]
638-
[typing fixtures/typing-medium.pyi]
642+
[builtins fixtures/tuple.pyi]
643+
[typing fixtures/typing-full.pyi]
639644

640645
[case testPEP695TypeAliasWithUnusedTypeParams]
641646
type A[T] = int
@@ -649,6 +654,8 @@ a: A[int]
649654
reveal_type(a) # N: Revealed type is "__main__.C[builtins.int]"
650655

651656
class C[T]: pass
657+
[builtins fixtures/tuple.pyi]
658+
[typing fixtures/typing-full.pyi]
652659

653660
[case testPEP695TypeAliasForwardReference2]
654661
type X = C
@@ -670,6 +677,8 @@ reveal_type(a) # N: Revealed type is "__main__.C[__main__.D]"
670677

671678
class C[T]: pass
672679
class D: pass
680+
[builtins fixtures/tuple.pyi]
681+
[typing fixtures/typing-full.pyi]
673682

674683
[case testPEP695TypeAliasForwardReference4]
675684
type A = C
@@ -692,6 +701,8 @@ c: C[str]
692701
reveal_type(a) # N: Revealed type is "builtins.str"
693702
reveal_type(b) # N: Revealed type is "__main__.C[builtins.int]"
694703
reveal_type(c) # N: Revealed type is "__main__.C[builtins.str]"
704+
[builtins fixtures/tuple.pyi]
705+
[typing fixtures/typing-full.pyi]
695706

696707
[case testPEP695TypeAliasWithUndefineName]
697708
type A[T] = XXX # E: Name "XXX" is not defined
@@ -707,10 +718,13 @@ type B = int + str # E: Invalid type alias: expression is not a valid type
707718
b: B
708719
reveal_type(b) # N: Revealed type is "Any"
709720
[builtins fixtures/type.pyi]
721+
[typing fixtures/typing-full.pyi]
710722

711723
[case testPEP695TypeAliasBoundForwardReference]
712724
type B[T: Foo] = list[T]
713725
class Foo: pass
726+
[builtins fixtures/tuple.pyi]
727+
[typing fixtures/typing-full.pyi]
714728

715729
[case testPEP695UpperBound]
716730
class D:
@@ -777,6 +791,8 @@ reveal_type(a) # N: Revealed type is "__main__.C[__main__.D[__main__.X]]"
777791
reveal_type(b) # N: Revealed type is "__main__.C[__main__.E[__main__.X]]"
778792

779793
c: C[D[int]] # E: Type argument "D[int]" of "C" must be a subtype of "D[X]"
794+
[builtins fixtures/tuple.pyi]
795+
[typing fixtures/typing-full.pyi]
780796

781797
[case testPEP695UpperBoundForwardReference4]
782798
def f[T: D](a: T) -> T:
@@ -935,6 +951,7 @@ type C[*Ts] = tuple[*Ts, int]
935951
a: C[str, None]
936952
reveal_type(a) # N: Revealed type is "Tuple[builtins.str, None, builtins.int]"
937953
[builtins fixtures/tuple.pyi]
954+
[typing fixtures/typing-full.pyi]
938955

939956
[case testPEP695IncrementalFunction]
940957
import a
@@ -1036,6 +1053,7 @@ class Foo[T]: pass
10361053
type B[T] = Foo[T]
10371054

10381055
[builtins fixtures/tuple.pyi]
1056+
[typing fixtures/typing-full.pyi]
10391057
[out2]
10401058
tmp/a.py:3: note: Revealed type is "builtins.str"
10411059
tmp/a.py:5: note: Revealed type is "b.Foo[builtins.int]"
@@ -1249,6 +1267,7 @@ type B[T] = C[T] | list[B[T]]
12491267
b: B[int]
12501268
reveal_type(b) # N: Revealed type is "Union[__main__.C[builtins.int], builtins.list[...]]"
12511269
[builtins fixtures/type.pyi]
1270+
[typing fixtures/typing-full.pyi]
12521271

12531272
[case testPEP695BadRecursiveTypeAlias]
12541273
type A = A # E: Cannot resolve name "A" (possible cyclic definition)
@@ -1277,6 +1296,7 @@ f(C[C[str]]())
12771296
f(1) # E: Argument 1 to "f" has incompatible type "int"; expected "A"
12781297
f(C[int]()) # E: Argument 1 to "f" has incompatible type "C[int]"; expected "A"
12791298
[builtins fixtures/isinstance.pyi]
1299+
[typing fixtures/typing-full.pyi]
12801300

12811301
[case testPEP695InvalidGenericOrProtocolBaseClass]
12821302
from typing import Generic, Protocol, TypeVar
@@ -1579,6 +1599,8 @@ else:
15791599
x: T # E: Name "T" is not defined
15801600
a: A[int]
15811601
reveal_type(a) # N: Revealed type is "builtins.list[builtins.int]"
1602+
[builtins fixtures/tuple.pyi]
1603+
[typing fixtures/typing-full.pyi]
15821604

15831605
[case testPEP695UndefinedNameInAnnotation]
15841606
def f[T](x: foobar, y: T) -> T: ... # E: Name "foobar" is not defined
@@ -1592,6 +1614,8 @@ reveal_type(a) # N: Revealed type is "builtins.list[builtins.int]"
15921614
type B[T: (int,)] = list[T] # E: Type variable must have at least two constrained types
15931615
b: B[str]
15941616
reveal_type(b) # N: Revealed type is "builtins.list[builtins.str]"
1617+
[builtins fixtures/tuple.pyi]
1618+
[typing fixtures/typing-full.pyi]
15951619

15961620
[case testPEP695UsingTypeVariableInOwnBoundOrConstraint]
15971621
type A[T: list[T]] = str # E: Name "T" is not defined
@@ -1611,6 +1635,8 @@ a: A
16111635
reveal_type(a) # N: Revealed type is "builtins.list[Any]"
16121636
b: B
16131637
reveal_type(b) # N: Revealed type is "Any"
1638+
[builtins fixtures/tuple.pyi]
1639+
[typing fixtures/typing-full.pyi]
16141640

16151641
[case testPEP695GenericNamedTuple]
16161642
from typing import NamedTuple
@@ -1795,6 +1821,7 @@ reveal_type(y) # N: Revealed type is "Union[builtins.int, builtins.str]"
17951821
reveal_type(z) # N: Revealed type is "builtins.int"
17961822
reveal_type(zz) # N: Revealed type is "builtins.str"
17971823
[builtins fixtures/tuple.pyi]
1824+
[typing fixtures/typing-full.pyi]
17981825

17991826
[case testPEP695NestedGenericClass1]
18001827
class C[T]:
@@ -1972,3 +1999,19 @@ class D:
19721999
class G[Q]:
19732000
def g(self, x: Q): ...
19742001
d: G[str]
2002+
2003+
[case testTypeAliasNormalization]
2004+
from collections.abc import Callable
2005+
from typing import Unpack
2006+
from typing_extensions import TypeAlias
2007+
2008+
type RK_function_args = tuple[float, int]
2009+
type RK_functionBIS = Callable[[Unpack[RK_function_args], int], int]
2010+
2011+
def ff(a: float, b: int, c: int) -> int:
2012+
return 2
2013+
2014+
bis: RK_functionBIS = ff
2015+
res: int = bis(1.0, 2, 3)
2016+
[builtins fixtures/tuple.pyi]
2017+
[typing fixtures/typing-full.pyi]

test-data/unit/fixtures/typing-full.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ Final = 0
3636
TypedDict = 0
3737
NoReturn = 0
3838
NewType = 0
39+
TypeAlias = 0
3940
Self = 0
4041
Unpack = 0
4142
Callable: _SpecialForm

0 commit comments

Comments
 (0)