5252
5353from contextlib import contextmanager
5454from 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
5656
5757from mypy import errorcodes as codes , message_registry
5858from mypy .constant_fold import constant_fold_expr
@@ -2018,34 +2018,35 @@ def analyze_class_typevar_declaration(self, base: Type) -> tuple[TypeVarLikeList
20182018
20192019 def analyze_unbound_tvar (self , t : Type ) -> tuple [str , TypeVarLikeExpr ] | None :
20202020 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 )
20222022 if isinstance (t , UnboundType ):
20232023 sym = self .lookup_qualified (t .name , t )
20242024 if sym and sym .fullname in ("typing.Unpack" , "typing_extensions.Unpack" ):
20252025 inner_t = t .args [0 ]
20262026 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 )
20282028 return None
20292029 return self .analyze_unbound_tvar_impl (t )
20302030 return None
20312031
20322032 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
20342034 ) -> tuple [str , TypeVarLikeExpr ] | None :
2035+ assert not is_unpacked or not is_typealias_param , "Mutually exclusive conditions"
20352036 sym = self .lookup_qualified (t .name , t )
20362037 if sym and isinstance (sym .node , PlaceholderNode ):
20372038 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 ):
20392040 if sym .fullname and not self .tvar_scope .allow_binding (sym .fullname ):
20402041 # It's bound by our type variable scope
20412042 return None
20422043 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 ):
20442045 if sym .fullname and not self .tvar_scope .allow_binding (sym .fullname ):
20452046 # It's bound by our type variable scope
20462047 return None
20472048 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 :
20492050 return None
20502051 elif sym .fullname and not self .tvar_scope .allow_binding (sym .fullname ):
20512052 # It's bound by our type variable scope
@@ -3515,7 +3516,11 @@ def analyze_simple_literal_type(self, rvalue: Expression, is_final: bool) -> Typ
35153516 return typ
35163517
35173518 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 ,
35193524 ) -> tuple [Type | None , list [TypeVarLikeType ], set [str ], list [str ], bool ]:
35203525 """Check if 'rvalue' is a valid type allowed for aliasing (e.g. not a type variable).
35213526
@@ -3540,9 +3545,10 @@ def analyze_alias(
35403545 found_type_vars = self .find_type_var_likes (typ )
35413546 tvar_defs : list [TypeVarLikeType ] = []
35423547 namespace = self .qualified_name (name )
3548+ alias_type_vars = found_type_vars if declared_type_vars is None else declared_type_vars
35433549 last_tvar_name_with_default : str | None = None
35443550 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 :
35463552 tvar_expr .default = tvar_expr .default .accept (
35473553 TypeVarDefaultTranslator (self , tvar_expr .name , typ )
35483554 )
@@ -3567,6 +3573,7 @@ def analyze_alias(
35673573 in_dynamic_func = dynamic ,
35683574 global_scope = global_scope ,
35693575 allowed_alias_tvars = tvar_defs ,
3576+ has_type_params = declared_type_vars is not None ,
35703577 )
35713578
35723579 # There can be only one variadic variable at most, the error is reported elsewhere.
@@ -3579,7 +3586,7 @@ def analyze_alias(
35793586 variadic = True
35803587 new_tvar_defs .append (td )
35813588
3582- qualified_tvars = [node .fullname for _name , node in found_type_vars ]
3589+ qualified_tvars = [node .fullname for _name , node in alias_type_vars ]
35833590 empty_tuple_index = typ .empty_tuple_index if isinstance (typ , UnboundType ) else False
35843591 return analyzed , new_tvar_defs , depends_on , qualified_tvars , empty_tuple_index
35853592
@@ -3612,7 +3619,19 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
36123619 # unless using PEP 613 `cls: TypeAlias = A`
36133620 return False
36143621
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 :
36163635 return False
36173636
36183637 existing = self .current_symbol_table ().get (lvalue .name )
@@ -3638,7 +3657,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
36383657 return False
36393658
36403659 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 :
36423661 # Fourth rule (special case): Non-subscripted right hand side creates a variable
36433662 # at class and function scopes. For example:
36443663 #
@@ -3650,8 +3669,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
36503669 # without this rule, this typical use case will require a lot of explicit
36513670 # annotations (see the second rule).
36523671 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 ):
36553673 return False
36563674
36573675 if existing and not isinstance (existing .node , (PlaceholderNode , TypeAlias )):
@@ -3668,7 +3686,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
36683686 else :
36693687 tag = self .track_incomplete_refs ()
36703688 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
36723690 )
36733691 if not res :
36743692 return False
@@ -3698,13 +3716,15 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
36983716 # so we need to replace it with non-explicit Anys.
36993717 res = make_any_non_explicit (res )
37003718 # 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.
37023721 # However, eagerly expanding aliases like Text = str is a nice performance optimization.
37033722 no_args = (
37043723 isinstance (res , ProperType )
37053724 and isinstance (res , Instance )
37063725 and not res .args
37073726 and not empty_tuple_index
3727+ and not pep_695
37083728 )
37093729 if isinstance (res , ProperType ) and isinstance (res , Instance ):
37103730 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:
37713791 self .note ("Use variable annotation syntax to define protocol members" , s )
37723792 return True
37733793
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+
37743868 def disable_invalid_recursive_aliases (
37753869 self , s : AssignmentStmt , current_node : TypeAlias
37763870 ) -> None :
@@ -5187,6 +5281,12 @@ def visit_call_expr(self, expr: CallExpr) -> None:
51875281 expr .analyzed = OpExpr ("divmod" , expr .args [0 ], expr .args [1 ])
51885282 expr .analyzed .line = expr .line
51895283 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 )
51905290 else :
51915291 # Normal call expression.
51925292 for a in expr .args :
0 commit comments