52
52
53
53
from contextlib import contextmanager
54
54
from typing import Any , Callable , Collection , Final , Iterable , Iterator , List , TypeVar , cast
55
- from typing_extensions import TypeAlias as _TypeAlias
55
+ from typing_extensions import TypeAlias as _TypeAlias , TypeGuard
56
56
57
57
from mypy import errorcodes as codes , message_registry
58
58
from mypy .constant_fold import constant_fold_expr
@@ -2018,34 +2018,35 @@ def analyze_class_typevar_declaration(self, base: Type) -> tuple[TypeVarLikeList
2018
2018
2019
2019
def analyze_unbound_tvar (self , t : Type ) -> tuple [str , TypeVarLikeExpr ] | None :
2020
2020
if isinstance (t , UnpackType ) and isinstance (t .type , UnboundType ):
2021
- return self .analyze_unbound_tvar_impl (t .type , allow_tvt = True )
2021
+ return self .analyze_unbound_tvar_impl (t .type , is_unpacked = True )
2022
2022
if isinstance (t , UnboundType ):
2023
2023
sym = self .lookup_qualified (t .name , t )
2024
2024
if sym and sym .fullname in ("typing.Unpack" , "typing_extensions.Unpack" ):
2025
2025
inner_t = t .args [0 ]
2026
2026
if isinstance (inner_t , UnboundType ):
2027
- return self .analyze_unbound_tvar_impl (inner_t , allow_tvt = True )
2027
+ return self .analyze_unbound_tvar_impl (inner_t , is_unpacked = True )
2028
2028
return None
2029
2029
return self .analyze_unbound_tvar_impl (t )
2030
2030
return None
2031
2031
2032
2032
def analyze_unbound_tvar_impl (
2033
- self , t : UnboundType , allow_tvt : bool = False
2033
+ self , t : UnboundType , is_unpacked : bool = False , is_typealias_param : bool = False
2034
2034
) -> tuple [str , TypeVarLikeExpr ] | None :
2035
+ assert not is_unpacked or not is_typealias_param , "Mutually exclusive conditions"
2035
2036
sym = self .lookup_qualified (t .name , t )
2036
2037
if sym and isinstance (sym .node , PlaceholderNode ):
2037
2038
self .record_incomplete_ref ()
2038
- if not allow_tvt and sym and isinstance (sym .node , ParamSpecExpr ):
2039
+ if not is_unpacked and sym and isinstance (sym .node , ParamSpecExpr ):
2039
2040
if sym .fullname and not self .tvar_scope .allow_binding (sym .fullname ):
2040
2041
# It's bound by our type variable scope
2041
2042
return None
2042
2043
return t .name , sym .node
2043
- if allow_tvt and sym and isinstance (sym .node , TypeVarTupleExpr ):
2044
+ if ( is_unpacked or is_typealias_param ) and sym and isinstance (sym .node , TypeVarTupleExpr ):
2044
2045
if sym .fullname and not self .tvar_scope .allow_binding (sym .fullname ):
2045
2046
# It's bound by our type variable scope
2046
2047
return None
2047
2048
return t .name , sym .node
2048
- if sym is None or not isinstance (sym .node , TypeVarExpr ) or allow_tvt :
2049
+ if sym is None or not isinstance (sym .node , TypeVarExpr ) or is_unpacked :
2049
2050
return None
2050
2051
elif sym .fullname and not self .tvar_scope .allow_binding (sym .fullname ):
2051
2052
# It's bound by our type variable scope
@@ -3515,7 +3516,11 @@ def analyze_simple_literal_type(self, rvalue: Expression, is_final: bool) -> Typ
3515
3516
return typ
3516
3517
3517
3518
def analyze_alias (
3518
- self , name : str , rvalue : Expression , allow_placeholder : bool = False
3519
+ self ,
3520
+ name : str ,
3521
+ rvalue : Expression ,
3522
+ allow_placeholder : bool = False ,
3523
+ declared_type_vars : TypeVarLikeList | None = None ,
3519
3524
) -> tuple [Type | None , list [TypeVarLikeType ], set [str ], list [str ], bool ]:
3520
3525
"""Check if 'rvalue' is a valid type allowed for aliasing (e.g. not a type variable).
3521
3526
@@ -3540,9 +3545,10 @@ def analyze_alias(
3540
3545
found_type_vars = self .find_type_var_likes (typ )
3541
3546
tvar_defs : list [TypeVarLikeType ] = []
3542
3547
namespace = self .qualified_name (name )
3548
+ alias_type_vars = found_type_vars if declared_type_vars is None else declared_type_vars
3543
3549
last_tvar_name_with_default : str | None = None
3544
3550
with self .tvar_scope_frame (self .tvar_scope .class_frame (namespace )):
3545
- for name , tvar_expr in found_type_vars :
3551
+ for name , tvar_expr in alias_type_vars :
3546
3552
tvar_expr .default = tvar_expr .default .accept (
3547
3553
TypeVarDefaultTranslator (self , tvar_expr .name , typ )
3548
3554
)
@@ -3567,6 +3573,7 @@ def analyze_alias(
3567
3573
in_dynamic_func = dynamic ,
3568
3574
global_scope = global_scope ,
3569
3575
allowed_alias_tvars = tvar_defs ,
3576
+ has_type_params = declared_type_vars is not None ,
3570
3577
)
3571
3578
3572
3579
# There can be only one variadic variable at most, the error is reported elsewhere.
@@ -3579,7 +3586,7 @@ def analyze_alias(
3579
3586
variadic = True
3580
3587
new_tvar_defs .append (td )
3581
3588
3582
- qualified_tvars = [node .fullname for _name , node in found_type_vars ]
3589
+ qualified_tvars = [node .fullname for _name , node in alias_type_vars ]
3583
3590
empty_tuple_index = typ .empty_tuple_index if isinstance (typ , UnboundType ) else False
3584
3591
return analyzed , new_tvar_defs , depends_on , qualified_tvars , empty_tuple_index
3585
3592
@@ -3612,7 +3619,19 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
3612
3619
# unless using PEP 613 `cls: TypeAlias = A`
3613
3620
return False
3614
3621
3615
- if isinstance (s .rvalue , CallExpr ) and s .rvalue .analyzed :
3622
+ # It can be `A = TypeAliasType('A', ...)` call, in this case,
3623
+ # we just take the second argument and analyze it:
3624
+ type_params : TypeVarLikeList | None
3625
+ if self .check_type_alias_type_call (s .rvalue , name = lvalue .name ):
3626
+ rvalue = s .rvalue .args [1 ]
3627
+ pep_695 = True
3628
+ type_params = self .analyze_type_alias_type_params (s .rvalue )
3629
+ else :
3630
+ rvalue = s .rvalue
3631
+ pep_695 = False
3632
+ type_params = None
3633
+
3634
+ if isinstance (rvalue , CallExpr ) and rvalue .analyzed :
3616
3635
return False
3617
3636
3618
3637
existing = self .current_symbol_table ().get (lvalue .name )
@@ -3638,7 +3657,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
3638
3657
return False
3639
3658
3640
3659
non_global_scope = self .type or self .is_func_scope ()
3641
- if not pep_613 and isinstance (s . rvalue , RefExpr ) and non_global_scope :
3660
+ if not pep_613 and isinstance (rvalue , RefExpr ) and non_global_scope :
3642
3661
# Fourth rule (special case): Non-subscripted right hand side creates a variable
3643
3662
# at class and function scopes. For example:
3644
3663
#
@@ -3650,8 +3669,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
3650
3669
# without this rule, this typical use case will require a lot of explicit
3651
3670
# annotations (see the second rule).
3652
3671
return False
3653
- rvalue = s .rvalue
3654
- if not pep_613 and not self .can_be_type_alias (rvalue ):
3672
+ if not pep_613 and not pep_695 and not self .can_be_type_alias (rvalue ):
3655
3673
return False
3656
3674
3657
3675
if existing and not isinstance (existing .node , (PlaceholderNode , TypeAlias )):
@@ -3668,7 +3686,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
3668
3686
else :
3669
3687
tag = self .track_incomplete_refs ()
3670
3688
res , alias_tvars , depends_on , qualified_tvars , empty_tuple_index = self .analyze_alias (
3671
- lvalue .name , rvalue , allow_placeholder = True
3689
+ lvalue .name , rvalue , allow_placeholder = True , declared_type_vars = type_params
3672
3690
)
3673
3691
if not res :
3674
3692
return False
@@ -3698,13 +3716,15 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
3698
3716
# so we need to replace it with non-explicit Anys.
3699
3717
res = make_any_non_explicit (res )
3700
3718
# Note: with the new (lazy) type alias representation we only need to set no_args to True
3701
- # if the expected number of arguments is non-zero, so that aliases like A = List work.
3719
+ # if the expected number of arguments is non-zero, so that aliases like `A = List` work
3720
+ # but not aliases like `A = TypeAliasType("A", List)` as these need explicit type params.
3702
3721
# However, eagerly expanding aliases like Text = str is a nice performance optimization.
3703
3722
no_args = (
3704
3723
isinstance (res , ProperType )
3705
3724
and isinstance (res , Instance )
3706
3725
and not res .args
3707
3726
and not empty_tuple_index
3727
+ and not pep_695
3708
3728
)
3709
3729
if isinstance (res , ProperType ) and isinstance (res , Instance ):
3710
3730
if not validate_instance (res , self .fail , empty_tuple_index ):
@@ -3771,6 +3791,80 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
3771
3791
self .note ("Use variable annotation syntax to define protocol members" , s )
3772
3792
return True
3773
3793
3794
+ def check_type_alias_type_call (self , rvalue : Expression , * , name : str ) -> TypeGuard [CallExpr ]:
3795
+ if not isinstance (rvalue , CallExpr ):
3796
+ return False
3797
+
3798
+ names = ["typing_extensions.TypeAliasType" ]
3799
+ if self .options .python_version >= (3 , 12 ):
3800
+ names .append ("typing.TypeAliasType" )
3801
+ if not refers_to_fullname (rvalue .callee , tuple (names )):
3802
+ return False
3803
+
3804
+ return self .check_typevarlike_name (rvalue , name , rvalue )
3805
+
3806
+ def analyze_type_alias_type_params (self , rvalue : CallExpr ) -> TypeVarLikeList :
3807
+ if "type_params" in rvalue .arg_names :
3808
+ type_params_arg = rvalue .args [rvalue .arg_names .index ("type_params" )]
3809
+ if not isinstance (type_params_arg , TupleExpr ):
3810
+ self .fail (
3811
+ "Tuple literal expected as the type_params argument to TypeAliasType" ,
3812
+ type_params_arg ,
3813
+ )
3814
+ return []
3815
+ type_params = type_params_arg .items
3816
+ else :
3817
+ type_params = []
3818
+
3819
+ declared_tvars : TypeVarLikeList = []
3820
+ have_type_var_tuple = False
3821
+ for tp_expr in type_params :
3822
+ if isinstance (tp_expr , StarExpr ):
3823
+ tp_expr .valid = False
3824
+ self .analyze_type_expr (tp_expr )
3825
+ try :
3826
+ base = self .expr_to_unanalyzed_type (tp_expr )
3827
+ except TypeTranslationError :
3828
+ continue
3829
+ if not isinstance (base , UnboundType ):
3830
+ continue
3831
+
3832
+ tag = self .track_incomplete_refs ()
3833
+ tvar = self .analyze_unbound_tvar_impl (base , is_typealias_param = True )
3834
+ if tvar :
3835
+ if isinstance (tvar [1 ], TypeVarTupleExpr ):
3836
+ if have_type_var_tuple :
3837
+ self .fail (
3838
+ "Can only use one TypeVarTuple in type_params argument to TypeAliasType" ,
3839
+ base ,
3840
+ code = codes .TYPE_VAR ,
3841
+ )
3842
+ have_type_var_tuple = True
3843
+ continue
3844
+ have_type_var_tuple = True
3845
+ elif not self .found_incomplete_ref (tag ):
3846
+ self .fail (
3847
+ "Free type variable expected in type_params argument to TypeAliasType" ,
3848
+ base ,
3849
+ code = codes .TYPE_VAR ,
3850
+ )
3851
+ sym = self .lookup_qualified (base .name , base )
3852
+ if sym and sym .fullname in ("typing.Unpack" , "typing_extensions.Unpack" ):
3853
+ self .note (
3854
+ "Don't Unpack type variables in type_params" , base , code = codes .TYPE_VAR
3855
+ )
3856
+ continue
3857
+ if tvar in declared_tvars :
3858
+ self .fail (
3859
+ f'Duplicate type variable "{ tvar [0 ]} " in type_params argument to TypeAliasType' ,
3860
+ base ,
3861
+ code = codes .TYPE_VAR ,
3862
+ )
3863
+ continue
3864
+ if tvar :
3865
+ declared_tvars .append (tvar )
3866
+ return declared_tvars
3867
+
3774
3868
def disable_invalid_recursive_aliases (
3775
3869
self , s : AssignmentStmt , current_node : TypeAlias
3776
3870
) -> None :
@@ -5187,6 +5281,12 @@ def visit_call_expr(self, expr: CallExpr) -> None:
5187
5281
expr .analyzed = OpExpr ("divmod" , expr .args [0 ], expr .args [1 ])
5188
5282
expr .analyzed .line = expr .line
5189
5283
expr .analyzed .accept (self )
5284
+ elif refers_to_fullname (
5285
+ expr .callee , ("typing.TypeAliasType" , "typing_extensions.TypeAliasType" )
5286
+ ):
5287
+ with self .allow_unbound_tvars_set ():
5288
+ for a in expr .args :
5289
+ a .accept (self )
5190
5290
else :
5191
5291
# Normal call expression.
5192
5292
for a in expr .args :
0 commit comments