317
317
CORE_BUILTIN_CLASSES : Final = ["object" , "bool" , "function" ]
318
318
319
319
320
+ # Python has several different scope/namespace kinds with subtly different semantics.
321
+ SCOPE_GLOBAL : Final = 0 # Module top level
322
+ SCOPE_CLASS : Final = 1 # Class body
323
+ SCOPE_FUNC : Final = 2 # Function or lambda
324
+ SCOPE_COMPREHENSION : Final = 3 # Comprehension or generator expression
325
+ SCOPE_ANNOTATION : Final = 4 # Annotation scopes for type parameters and aliases (PEP 695)
326
+
327
+
320
328
# Used for tracking incomplete references
321
329
Tag : _TypeAlias = int
322
330
@@ -342,8 +350,8 @@ class SemanticAnalyzer(
342
350
nonlocal_decls : list [set [str ]]
343
351
# Local names of function scopes; None for non-function scopes.
344
352
locals : list [SymbolTable | None ]
345
- # Whether each scope is a comprehension scope.
346
- is_comprehension_stack : list [bool ]
353
+ # Type of each scope (SCOPE_*, indexes match locals)
354
+ scope_stack : list [int ]
347
355
# Nested block depths of scopes
348
356
block_depth : list [int ]
349
357
# TypeInfo of directly enclosing class (or None)
@@ -417,7 +425,7 @@ def __init__(
417
425
errors: Report analysis errors using this instance
418
426
"""
419
427
self .locals = [None ]
420
- self .is_comprehension_stack = [False ]
428
+ self .scope_stack = [SCOPE_GLOBAL ]
421
429
# Saved namespaces from previous iteration. Every top-level function/method body is
422
430
# analyzed in several iterations until all names are resolved. We need to save
423
431
# the local namespaces for the top level function and all nested functions between
@@ -880,6 +888,7 @@ def analyze_func_def(self, defn: FuncDef) -> None:
880
888
# Don't store not ready types (including placeholders).
881
889
if self .found_incomplete_ref (tag ) or has_placeholder (result ):
882
890
self .defer (defn )
891
+ # TODO: pop type args
883
892
return
884
893
assert isinstance (result , ProperType )
885
894
if isinstance (result , CallableType ):
@@ -1645,6 +1654,8 @@ def push_type_args(
1645
1654
) -> list [tuple [str , TypeVarLikeExpr ]] | None :
1646
1655
if not type_args :
1647
1656
return []
1657
+ self .locals .append (SymbolTable ())
1658
+ self .scope_stack .append (SCOPE_ANNOTATION )
1648
1659
tvs : list [tuple [str , TypeVarLikeExpr ]] = []
1649
1660
for p in type_args :
1650
1661
tv = self .analyze_type_param (p )
@@ -1653,10 +1664,23 @@ def push_type_args(
1653
1664
tvs .append ((p .name , tv ))
1654
1665
1655
1666
for name , tv in tvs :
1656
- self .add_symbol (name , tv , context , no_progress = True )
1667
+ if self .is_defined_type_param (name ):
1668
+ self .fail (f'"{ name } " already defined as a type parameter' , context )
1669
+ else :
1670
+ self .add_symbol (name , tv , context , no_progress = True , type_param = True )
1657
1671
1658
1672
return tvs
1659
1673
1674
+ def is_defined_type_param (self , name : str ) -> bool :
1675
+ for names in self .locals :
1676
+ if names is None :
1677
+ continue
1678
+ if name in names :
1679
+ node = names [name ].node
1680
+ if isinstance (node , TypeVarLikeExpr ):
1681
+ return True
1682
+ return False
1683
+
1660
1684
def analyze_type_param (self , type_param : TypeParam ) -> TypeVarLikeExpr | None :
1661
1685
fullname = self .qualified_name (type_param .name )
1662
1686
if type_param .upper_bound :
@@ -1681,10 +1705,15 @@ def analyze_type_param(self, type_param: TypeParam) -> TypeVarLikeExpr | None:
1681
1705
upper_bound = upper_bound ,
1682
1706
default = default ,
1683
1707
variance = VARIANCE_NOT_READY ,
1708
+ is_new_style = True ,
1684
1709
)
1685
1710
elif type_param .kind == PARAM_SPEC_KIND :
1686
1711
return ParamSpecExpr (
1687
- name = type_param .name , fullname = fullname , upper_bound = upper_bound , default = default
1712
+ name = type_param .name ,
1713
+ fullname = fullname ,
1714
+ upper_bound = upper_bound ,
1715
+ default = default ,
1716
+ is_new_style = True ,
1688
1717
)
1689
1718
else :
1690
1719
assert type_param .kind == TYPE_VAR_TUPLE_KIND
@@ -1696,14 +1725,14 @@ def analyze_type_param(self, type_param: TypeParam) -> TypeVarLikeExpr | None:
1696
1725
upper_bound = tuple_fallback .copy_modified (),
1697
1726
tuple_fallback = tuple_fallback ,
1698
1727
default = default ,
1728
+ is_new_style = True ,
1699
1729
)
1700
1730
1701
1731
def pop_type_args (self , type_args : list [TypeParam ] | None ) -> None :
1702
1732
if not type_args :
1703
1733
return
1704
- for tv in type_args :
1705
- names = self .current_symbol_table ()
1706
- del names [tv .name ]
1734
+ self .locals .pop ()
1735
+ self .scope_stack .pop ()
1707
1736
1708
1737
def analyze_class (self , defn : ClassDef ) -> None :
1709
1738
fullname = self .qualified_name (defn .name )
@@ -1785,8 +1814,18 @@ def analyze_class(self, defn: ClassDef) -> None:
1785
1814
defn .info .is_protocol = is_protocol
1786
1815
self .recalculate_metaclass (defn , declared_metaclass )
1787
1816
defn .info .runtime_protocol = False
1817
+
1818
+ if defn .type_args :
1819
+ # PEP 695 type parameters are not in scope in class decorators, so
1820
+ # temporarily disable type parameter namespace.
1821
+ type_params_names = self .locals .pop ()
1822
+ self .scope_stack .pop ()
1788
1823
for decorator in defn .decorators :
1789
1824
self .analyze_class_decorator (defn , decorator )
1825
+ if defn .type_args :
1826
+ self .locals .append (type_params_names )
1827
+ self .scope_stack .append (SCOPE_ANNOTATION )
1828
+
1790
1829
self .analyze_class_body_common (defn )
1791
1830
1792
1831
def setup_type_vars (self , defn : ClassDef , tvar_defs : list [TypeVarLikeType ]) -> None :
@@ -1938,7 +1977,7 @@ def enter_class(self, info: TypeInfo) -> None:
1938
1977
# Remember previous active class
1939
1978
self .type_stack .append (self .type )
1940
1979
self .locals .append (None ) # Add class scope
1941
- self .is_comprehension_stack .append (False )
1980
+ self .scope_stack .append (SCOPE_CLASS )
1942
1981
self .block_depth .append (- 1 ) # The class body increments this to 0
1943
1982
self .loop_depth .append (0 )
1944
1983
self ._type = info
@@ -1949,7 +1988,7 @@ def leave_class(self) -> None:
1949
1988
self .block_depth .pop ()
1950
1989
self .loop_depth .pop ()
1951
1990
self .locals .pop ()
1952
- self .is_comprehension_stack .pop ()
1991
+ self .scope_stack .pop ()
1953
1992
self ._type = self .type_stack .pop ()
1954
1993
self .missing_names .pop ()
1955
1994
@@ -2923,8 +2962,8 @@ class C:
2923
2962
[(j := i) for i in [1, 2, 3]]
2924
2963
is a syntax error that is not enforced by Python parser, but at later steps.
2925
2964
"""
2926
- for i , is_comprehension in enumerate (reversed (self .is_comprehension_stack )):
2927
- if not is_comprehension and i < len (self .locals ) - 1 :
2965
+ for i , scope_type in enumerate (reversed (self .scope_stack )):
2966
+ if scope_type != SCOPE_COMPREHENSION and i < len (self .locals ) - 1 :
2928
2967
if self .locals [- 1 - i ] is None :
2929
2968
self .fail (
2930
2969
"Assignment expression within a comprehension"
@@ -5188,8 +5227,14 @@ def visit_nonlocal_decl(self, d: NonlocalDecl) -> None:
5188
5227
self .fail ("nonlocal declaration not allowed at module level" , d )
5189
5228
else :
5190
5229
for name in d .names :
5191
- for table in reversed (self .locals [:- 1 ]):
5230
+ for table , scope_type in zip (
5231
+ reversed (self .locals [:- 1 ]), reversed (self .scope_stack [:- 1 ])
5232
+ ):
5192
5233
if table is not None and name in table :
5234
+ if scope_type == SCOPE_ANNOTATION :
5235
+ self .fail (
5236
+ f'nonlocal binding not allowed for type parameter "{ name } "' , d
5237
+ )
5193
5238
break
5194
5239
else :
5195
5240
self .fail (f'No binding for nonlocal "{ name } " found' , d )
@@ -5350,7 +5395,7 @@ def visit_star_expr(self, expr: StarExpr) -> None:
5350
5395
def visit_yield_from_expr (self , e : YieldFromExpr ) -> None :
5351
5396
if not self .is_func_scope ():
5352
5397
self .fail ('"yield from" outside function' , e , serious = True , blocker = True )
5353
- elif self .is_comprehension_stack [- 1 ]:
5398
+ elif self .scope_stack [- 1 ] == SCOPE_COMPREHENSION :
5354
5399
self .fail (
5355
5400
'"yield from" inside comprehension or generator expression' ,
5356
5401
e ,
@@ -5848,7 +5893,7 @@ def visit__promote_expr(self, expr: PromoteExpr) -> None:
5848
5893
def visit_yield_expr (self , e : YieldExpr ) -> None :
5849
5894
if not self .is_func_scope ():
5850
5895
self .fail ('"yield" outside function' , e , serious = True , blocker = True )
5851
- elif self .is_comprehension_stack [- 1 ]:
5896
+ elif self .scope_stack [- 1 ] == SCOPE_COMPREHENSION :
5852
5897
self .fail (
5853
5898
'"yield" inside comprehension or generator expression' ,
5854
5899
e ,
@@ -6281,6 +6326,7 @@ def add_symbol(
6281
6326
can_defer : bool = True ,
6282
6327
escape_comprehensions : bool = False ,
6283
6328
no_progress : bool = False ,
6329
+ type_param : bool = False ,
6284
6330
) -> bool :
6285
6331
"""Add symbol to the currently active symbol table.
6286
6332
@@ -6303,7 +6349,7 @@ def add_symbol(
6303
6349
kind , node , module_public = module_public , module_hidden = module_hidden
6304
6350
)
6305
6351
return self .add_symbol_table_node (
6306
- name , symbol , context , can_defer , escape_comprehensions , no_progress
6352
+ name , symbol , context , can_defer , escape_comprehensions , no_progress , type_param
6307
6353
)
6308
6354
6309
6355
def add_symbol_skip_local (self , name : str , node : SymbolNode ) -> None :
@@ -6336,6 +6382,7 @@ def add_symbol_table_node(
6336
6382
can_defer : bool = True ,
6337
6383
escape_comprehensions : bool = False ,
6338
6384
no_progress : bool = False ,
6385
+ type_param : bool = False ,
6339
6386
) -> bool :
6340
6387
"""Add symbol table node to the currently active symbol table.
6341
6388
@@ -6355,7 +6402,9 @@ def add_symbol_table_node(
6355
6402
can_defer: if True, defer current target if adding a placeholder
6356
6403
context: error context (see above about None value)
6357
6404
"""
6358
- names = self .current_symbol_table (escape_comprehensions = escape_comprehensions )
6405
+ names = self .current_symbol_table (
6406
+ escape_comprehensions = escape_comprehensions , type_param = type_param
6407
+ )
6359
6408
existing = names .get (name )
6360
6409
if isinstance (symbol .node , PlaceholderNode ) and can_defer :
6361
6410
if context is not None :
@@ -6673,7 +6722,7 @@ def enter(
6673
6722
names = self .saved_locals .setdefault (function , SymbolTable ())
6674
6723
self .locals .append (names )
6675
6724
is_comprehension = isinstance (function , (GeneratorExpr , DictionaryComprehension ))
6676
- self .is_comprehension_stack .append (is_comprehension )
6725
+ self .scope_stack .append (SCOPE_FUNC if not is_comprehension else SCOPE_COMPREHENSION )
6677
6726
self .global_decls .append (set ())
6678
6727
self .nonlocal_decls .append (set ())
6679
6728
# -1 since entering block will increment this to 0.
@@ -6684,19 +6733,22 @@ def enter(
6684
6733
yield
6685
6734
finally :
6686
6735
self .locals .pop ()
6687
- self .is_comprehension_stack .pop ()
6736
+ self .scope_stack .pop ()
6688
6737
self .global_decls .pop ()
6689
6738
self .nonlocal_decls .pop ()
6690
6739
self .block_depth .pop ()
6691
6740
self .loop_depth .pop ()
6692
6741
self .missing_names .pop ()
6693
6742
6694
6743
def is_func_scope (self ) -> bool :
6695
- return self .locals [- 1 ] is not None
6744
+ scope_type = self .scope_stack [- 1 ]
6745
+ if scope_type == SCOPE_ANNOTATION :
6746
+ scope_type = self .scope_stack [- 2 ]
6747
+ return scope_type in (SCOPE_FUNC , SCOPE_COMPREHENSION )
6696
6748
6697
6749
def is_nested_within_func_scope (self ) -> bool :
6698
6750
"""Are we underneath a function scope, even if we are in a nested class also?"""
6699
- return any (l is not None for l in self .locals )
6751
+ return any (s in ( SCOPE_FUNC , SCOPE_COMPREHENSION ) for s in self .scope_stack )
6700
6752
6701
6753
def is_class_scope (self ) -> bool :
6702
6754
return self .type is not None and not self .is_func_scope ()
@@ -6713,14 +6765,24 @@ def current_symbol_kind(self) -> int:
6713
6765
kind = GDEF
6714
6766
return kind
6715
6767
6716
- def current_symbol_table (self , escape_comprehensions : bool = False ) -> SymbolTable :
6717
- if self .is_func_scope ():
6718
- assert self .locals [- 1 ] is not None
6768
+ def current_symbol_table (
6769
+ self , escape_comprehensions : bool = False , type_param : bool = False
6770
+ ) -> SymbolTable :
6771
+ if type_param and self .scope_stack [- 1 ] == SCOPE_ANNOTATION :
6772
+ n = self .locals [- 1 ]
6773
+ assert n is not None
6774
+ return n
6775
+ elif self .is_func_scope ():
6776
+ if self .scope_stack [- 1 ] == SCOPE_ANNOTATION :
6777
+ n = self .locals [- 2 ]
6778
+ else :
6779
+ n = self .locals [- 1 ]
6780
+ assert n is not None
6719
6781
if escape_comprehensions :
6720
- assert len (self .locals ) == len (self .is_comprehension_stack )
6782
+ assert len (self .locals ) == len (self .scope_stack )
6721
6783
# Retrieve the symbol table from the enclosing non-comprehension scope.
6722
- for i , is_comprehension in enumerate (reversed (self .is_comprehension_stack )):
6723
- if not is_comprehension :
6784
+ for i , scope_type in enumerate (reversed (self .scope_stack )):
6785
+ if scope_type != SCOPE_COMPREHENSION :
6724
6786
if i == len (self .locals ) - 1 : # The last iteration.
6725
6787
# The caller of the comprehension is in the global space.
6726
6788
names = self .globals
@@ -6734,7 +6796,7 @@ def current_symbol_table(self, escape_comprehensions: bool = False) -> SymbolTab
6734
6796
else :
6735
6797
assert False , "Should have at least one non-comprehension scope"
6736
6798
else :
6737
- names = self . locals [ - 1 ]
6799
+ names = n
6738
6800
assert names is not None
6739
6801
elif self .type is not None :
6740
6802
names = self .type .names
0 commit comments