@@ -589,6 +589,14 @@ def genhash(self, code, package_level=-1):
589589 temp_var_counts = None
590590 operators = None
591591
592+ def init_parsing_context (self ):
593+ """Initialize parsing context."""
594+ self .parsing_context = defaultdict (list )
595+ # initialize module-level scopes
596+ self .parsing_context ["final_vars" ].append ({})
597+ self .parsing_context ["pure_vars" ].append ({})
598+ return self .parsing_context
599+
592600 def reset (self , keep_state = False , filename = None ):
593601 """Reset references.
594602
@@ -607,8 +615,6 @@ def reset(self, keep_state=False, filename=None):
607615 self .temp_var_counts = defaultdict (int )
608616 # but always overwrite temp_vars_by_key since they store locs that will be invalidated
609617 self .temp_vars_by_key = {}
610- self .parsing_context = defaultdict (list )
611- self .parsing_context ["final_vars" ].append ({}) # initialize module-level final scope
612618 self .name_info = defaultdict (lambda : {"imported" : set (), "referenced" : set (), "assigned" : set ()})
613619 self .star_import = False
614620 self .kept_lines = []
@@ -625,6 +631,7 @@ def reset(self, keep_state=False, filename=None):
625631 self .shown_warnings = set ()
626632 if not keep_state :
627633 self .computation_graph_caches = defaultdict (staledict )
634+ self .init_parsing_context ()
628635
629636 @contextmanager
630637 def inner_environment (self , ln = None ):
@@ -638,12 +645,12 @@ def inner_environment(self, ln=None):
638645 wrapped_type_ignore , self .wrapped_type_ignore = self .wrapped_type_ignore , None
639646 skips , self .skips = self .skips , []
640647 docstring , self .docstring = self .docstring , ""
641- parsing_context , self .parsing_context = self .parsing_context , defaultdict (list )
642- self .parsing_context ["final_vars" ].append ({}) # initialize module-level final scope
643648 kept_lines , self .kept_lines = self .kept_lines , []
644649 num_lines , self .num_lines = self .num_lines , 0
645650 remaining_original , self .remaining_original = self .remaining_original , None
646651 shown_warnings , self .shown_warnings = self .shown_warnings , set ()
652+ parsing_context = self .parsing_context
653+ self .init_parsing_context ()
647654 try :
648655 with ComputationNode .using_overrides ():
649656 yield
@@ -655,11 +662,11 @@ def inner_environment(self, ln=None):
655662 self .wrapped_type_ignore = wrapped_type_ignore
656663 self .skips = skips
657664 self .docstring = docstring
658- self .parsing_context = parsing_context
659665 self .kept_lines = kept_lines
660666 self .num_lines = num_lines
661667 self .remaining_original = remaining_original
662668 self .shown_warnings = shown_warnings
669+ self .parsing_context = parsing_context
663670
664671 @contextmanager
665672 def disable_checks (self ):
@@ -1084,10 +1091,10 @@ def strict_err_or_warn(self, msg, original, loc, noqa_able=False, **kwargs):
10841091 else :
10851092 self .syntax_warning (msg , original , loc , ** kwargs )
10861093
1087- def pure_error (self , msg , original , loc , noqa_able = True , ** kwargs ):
1094+ def pure_error (self , msg , original , loc , ** kwargs ):
10881095 """If in pure mode, raise an error or warn depending on strict mode."""
10891096 if self .pure :
1090- return self .strict_err_or_warn (msg , original , loc , noqa_able = noqa_able , pure_err = True , ** kwargs )
1097+ return self .strict_err_or_warn (msg , original , loc , pure_err = True , ** kwargs )
10911098
10921099 @contextmanager
10931100 def complain_on_err (self ):
@@ -5047,16 +5054,20 @@ def current_parsing_context(self, name, default=None):
50475054 return default
50485055
50495056 @contextmanager
5050- def add_to_parsing_context (self , name , obj , callbacks_key = None ):
5051- """Put the given object on the parsing context stack for the given name."""
5052- self .parsing_context [name ].append (obj )
5057+ def add_to_parsing_context (self , name_obj_dict , callbacks_keys_dict = None ):
5058+ """Put all the given objects on their respective parsing context stacks."""
5059+ for name , obj in name_obj_dict .items ():
5060+ self .parsing_context [name ].append (obj )
50535061 try :
50545062 yield
50555063 finally :
5056- popped_ctx = self .parsing_context [name ].pop ()
5057- if callbacks_key is not None :
5058- for callback in popped_ctx [callbacks_key ]:
5059- callback ()
5064+ for name in name_obj_dict :
5065+ popped_ctx = self .parsing_context [name ].pop ()
5066+ if callbacks_keys_dict is not None :
5067+ callbacks_key = callbacks_keys_dict .get (name )
5068+ if callbacks_key is not None :
5069+ for callback in popped_ctx [callbacks_key ]:
5070+ callback ()
50605071
50615072 def funcname_typeparams_handle (self , tokens ):
50625073 """Handle function names with type parameters."""
@@ -5183,10 +5194,12 @@ def get_generic_for_typevars(self):
51835194 def type_alias_stmt_manage (self , original = None , loc = None , item = None ):
51845195 """Manage the typevars parsing context."""
51855196 prev_typevar_info = self .current_parsing_context ("typevars" )
5186- with self .add_to_parsing_context ("typevars" , {
5187- "all_typevars" : {} if prev_typevar_info is None else prev_typevar_info ["all_typevars" ].copy (),
5188- "new_typevars" : [],
5189- "typevar_locs" : {},
5197+ with self .add_to_parsing_context ({
5198+ "typevars" : {
5199+ "all_typevars" : {} if prev_typevar_info is None else prev_typevar_info ["all_typevars" ].copy (),
5200+ "new_typevars" : [],
5201+ "typevar_locs" : {},
5202+ },
51905203 }):
51915204 yield
51925205
@@ -5220,8 +5233,10 @@ def where_item_handle(self, tokens):
52205233 @contextmanager
52215234 def where_stmt_manage (self , original , loc , item ):
52225235 """Manage where statements."""
5223- with self .add_to_parsing_context ("where" , {
5224- "assigns" : None ,
5236+ with self .add_to_parsing_context ({
5237+ "where" : {
5238+ "assigns" : None ,
5239+ },
52255240 }):
52265241 yield
52275242
@@ -5277,7 +5292,10 @@ def class_manage(self, original, loc, item):
52775292 try :
52785293 # handles support for class type variables
52795294 with self .type_alias_stmt_manage ():
5280- with self .add_to_parsing_context ("final_vars" , {}):
5295+ with self .add_to_parsing_context ({
5296+ "final_vars" : {},
5297+ "pure_vars" : {},
5298+ }):
52815299 yield
52825300 finally :
52835301 cls_stack .pop ()
@@ -5291,7 +5309,10 @@ def func_manage(self, original, loc, item):
52915309 try :
52925310 # handles support for function type variables
52935311 with self .type_alias_stmt_manage ():
5294- with self .add_to_parsing_context ("final_vars" , {}):
5312+ with self .add_to_parsing_context ({
5313+ "final_vars" : {},
5314+ "pure_vars" : {},
5315+ }):
52955316 yield
52965317 finally :
52975318 if cls_context is not None :
@@ -5307,14 +5328,13 @@ def in_method(self):
53075328 def has_expr_setname_manage (self , original , loc , item ):
53085329 """Handle parses that can assign expr_setname."""
53095330 with self .add_to_parsing_context (
5310- "expr_setnames" ,
5311- {
5331+ {"expr_setnames" : {
53125332 "parent" : self .current_parsing_context ("expr_setnames" ),
53135333 "new_names" : set (),
53145334 "callbacks" : [],
53155335 "loc" : loc ,
5316- },
5317- callbacks_key = " callbacks" ,
5336+ }} ,
5337+ callbacks_keys_dict = { "expr_setnames" : " callbacks"} ,
53185338 ):
53195339 yield
53205340
@@ -5391,6 +5411,55 @@ def name_handle(self, original, loc, tokens, assign=False, classname=False, expr
53915411 is_new = loc not in self .name_info [name ]["referenced" ]
53925412 self .name_info [name ]["referenced" ].add (loc )
53935413
5414+ # final variable checking (setting final_vars happens at the end)
5415+ final_vars = self .current_parsing_context ("final_vars" )
5416+ self .internal_assert (final_vars is not None , original , loc , "no final_vars context" )
5417+ if (
5418+ assign
5419+ and not escaped
5420+ # at least on py3, expr_setnames shadow rather than overwrite, so we allow them
5421+ and not expr_setname
5422+ and name in final_vars
5423+ and final_vars [name ] != loc # allow reassign in same loc (speculative parsing duplicate)
5424+ ):
5425+ return local_raise_or_wrap_error (
5426+ CoconutSyntaxError ,
5427+ "disallowed reassignment of final variable '{name}'" .format (name = name ),
5428+ original ,
5429+ loc ,
5430+ extra = "use explicit '\\ {name}' syntax to bypass final checking" .format (name = name ),
5431+ )
5432+
5433+ safe_to_show_warnings = (
5434+ # in strict mode, errors are wrapped and we should always do that
5435+ self .strict
5436+ # in non-strict mode, only check when using computation graph
5437+ # and not for greedy handlers (to avoid spurious warnings)
5438+ # and only if it's a new assignment (to avoid duplicate warnings)
5439+ or (is_new and not is_greedy and USE_COMPUTATION_GRAPH )
5440+ )
5441+
5442+ # pure variable checking (setting pure_vars happens at the end)
5443+ if self .pure :
5444+ pure_vars = self .current_parsing_context ("pure_vars" )
5445+ self .internal_assert (pure_vars is not None , original , loc , "no pure_vars context" )
5446+ if (
5447+ assign
5448+ and not escaped
5449+ and not expr_setname
5450+ and safe_to_show_warnings
5451+ and name in pure_vars
5452+ and pure_vars [name ] != loc # allow reassign in same loc (speculative parsing duplicate)
5453+ ):
5454+ err = self .pure_error (
5455+ "disallowed reassignment of variable '{name}' (all variable reassignment is prohibited in --pure mode; try reworking to be more functional or bypass with explicit '\\ {name}' syntax if necessary)" .format (name = name ),
5456+ original ,
5457+ loc ,
5458+ raise_err_func = local_raise_or_wrap_error ,
5459+ )
5460+ if err is not None :
5461+ return err
5462+
53945463 is_class_attr = (
53955464 self .current_parsing_context ("class" )
53965465 and not self .in_method
@@ -5418,14 +5487,7 @@ def name_handle(self, original, loc, tokens, assign=False, classname=False, expr
54185487 # case we can't be sure this is actually shadowing a builtin;
54195488 # BUT if we're on strict mode, then it's an actual error, rather
54205489 # than a warning, which means we can just wrap it
5421- and (
5422- # in strict mode, errors are wrapped and we should always do that
5423- self .strict
5424- # in non-strict mode, only check when using computation graph
5425- # and not for greedy handlers (to avoid spurious warnings)
5426- # and only if it's a new assignment (to avoid duplicate warnings)
5427- or (is_new and not is_greedy and USE_COMPUTATION_GRAPH )
5428- )
5490+ and safe_to_show_warnings
54295491 ):
54305492 if name in all_builtins :
54315493 err = self .strict_err_or_warn (
@@ -5449,26 +5511,11 @@ def name_handle(self, original, loc, tokens, assign=False, classname=False, expr
54495511 if err is not None :
54505512 return err
54515513
5452- # final variable checking
5453- final_vars = self .current_parsing_context ("final_vars" )
5454- self .internal_assert (final_vars is not None , original , loc , "no final_vars context" )
5455- if (
5456- assign
5457- and not escaped
5458- and not expr_setname
5459- and name in final_vars
5460- and final_vars [name ] != loc # allow reassign in same loc (speculative parsing duplicate)
5461- ):
5462- return local_raise_or_wrap_error (
5463- CoconutSyntaxError ,
5464- "cannot reassign final variable '{name}'" .format (name = name ),
5465- original ,
5466- loc ,
5467- extra = "use explicit '\\ {name}' syntax to bypass final checking" .format (name = name ),
5468- )
54695514 # only mark as final after all checks pass
54705515 if is_final :
54715516 final_vars [name ] = loc
5517+ if self .pure :
5518+ pure_vars [name ] = loc
54725519
54735520 if name == "exec" :
54745521 if self .target .startswith ("3" ):
@@ -5575,13 +5622,13 @@ def check_py(self, version, name, original, loc, tokens):
55755622
55765623 def global_check (self , original , loc , tokens ):
55775624 """Check for global statement in --pure mode."""
5578- self .pure_error ("global statements are disabled in --pure mode" , original , loc )
5625+ self .pure_error ("global statements are disabled in --pure mode" , original , loc , noqa_able = True )
55795626 global_stmt , = tokens
55805627 return global_stmt
55815628
55825629 def nonlocal_check (self , original , loc , tokens ):
55835630 """Check for Python 3 nonlocal statement."""
5584- self .pure_error ("nonlocal statements are disabled in --pure mode" , original , loc )
5631+ self .pure_error ("nonlocal statements are disabled in --pure mode" , original , loc , noqa_able = True )
55855632 return self .check_py ("3" , "nonlocal statement" , original , loc , tokens )
55865633
55875634 def star_assign_item_check (self , original , loc , tokens ):
0 commit comments