44"""
55
66import contextlib
7- from typing import Union , Iterator , Optional , List , Callable
7+ from typing import Union , Iterator , Optional , List , Callable , Dict , Tuple
88
99from mypy .nodes import (
1010 FuncDef , NameExpr , MemberExpr , RefExpr , MypyFile , ClassDef , AssignmentStmt ,
1818from mypy .server .aststrip import nothing
1919
2020
21- def strip_target_new (node : Union [MypyFile , FuncDef , OverloadedFuncDef ]
22- ) -> List [Callable [[], None ]]:
21+ SavedAttributes = Dict [Tuple [ClassDef , str ], SymbolTableNode ]
22+
23+
24+ def strip_target_new (node : Union [MypyFile , FuncDef , OverloadedFuncDef ],
25+ saved_attrs : SavedAttributes ) -> None :
2326 """Reset a fine-grained incremental target to state before main pass of semantic analysis.
2427
2528 The most notable difference from the old version of strip_target() is that new semantic
@@ -28,24 +31,28 @@ def strip_target_new(node: Union[MypyFile, FuncDef, OverloadedFuncDef]
2831 defined as attributes on self. This is done by patches (callbacks) returned from this function
2932 that re-add these variables when called.
3033 """
31- visitor = NodeStripVisitor ()
34+ visitor = NodeStripVisitor (saved_attrs )
3235 if isinstance (node , MypyFile ):
3336 visitor .strip_file_top_level (node )
3437 else :
3538 node .accept (visitor )
36- return visitor .patches
3739
3840
3941class NodeStripVisitor (TraverserVisitor ):
40- def __init__ (self ) -> None :
42+ def __init__ (self , saved_class_attrs : SavedAttributes ) -> None :
4143 # The current active class.
4244 self .type = None # type: Optional[TypeInfo]
4345 # This is True at class scope, but not in methods.
4446 self .is_class_body = False
4547 # By default, process function definitions. If False, don't -- this is used for
4648 # processing module top levels.
4749 self .recurse_into_functions = True
48- self .patches = [] # type: List[Callable[[], None]]
50+ # These attributes were removed from top-level classes during strip and
51+ # will be added afterwards (if no existing definition is found). These
52+ # must be added back before semantically analyzing any methods. These
53+ # allow moving attribute definition from a method (through self.x) to a
54+ # definition inside class body (x = ...).
55+ self .saved_class_attrs = saved_class_attrs
4956
5057 def strip_file_top_level (self , file_node : MypyFile ) -> None :
5158 """Strip a module top-level (don't recursive into functions)."""
@@ -66,7 +73,7 @@ def visit_class_def(self, node: ClassDef) -> None:
6673 # be lost if we only reprocess top-levels (this kills TypeInfos)
6774 # but not the methods that defined those variables.
6875 if not self .recurse_into_functions :
69- self .prepare_implicit_var_patches (node )
76+ self .save_implicit_attributes (node )
7077 # We need to delete any entries that were generated by plugins,
7178 # since they will get regenerated.
7279 to_delete = {v .node for v in node .info .names .values () if v .plugin_generated }
@@ -81,30 +88,11 @@ def visit_class_def(self, node: ClassDef) -> None:
8188 # Kill the TypeInfo, since there is none before semantic analysis.
8289 node .info = CLASSDEF_NO_INFO
8390
84- def prepare_implicit_var_patches (self , node : ClassDef ) -> None :
91+ def save_implicit_attributes (self , node : ClassDef ) -> None :
8592 """Produce callbacks that re-add attributes defined on self."""
8693 for name , sym in node .info .names .items ():
8794 if isinstance (sym .node , Var ) and sym .implicit :
88- explicit_self_type = sym .node .explicit_self_type
89-
90- def patch (name : str , sym : SymbolTableNode ) -> None :
91- existing = node .info .get (name )
92- defined_in_this_class = name in node .info .names
93- # This needs to mimic the logic in SemanticAnalyzer.analyze_member_lvalue()
94- # regarding the existing variable in class body or in a superclass:
95- # If the attribute of self is not defined in superclasses, create a new Var.
96- if (existing is None or
97- # (An abstract Var is considered as not defined.)
98- (isinstance (existing .node , Var ) and existing .node .is_abstract_var ) or
99- # Also an explicit declaration on self creates a new Var unless
100- # there is already one defined in the class body.
101- explicit_self_type and not defined_in_this_class ):
102- node .info .names [name ] = sym
103-
104- # Capture the current name, sym in a weird hacky way,
105- # because mypyc doesn't yet support capturing them in
106- # the usual hacky way (as default arguments).
107- self .patches .append ((lambda name , sym : lambda : patch (name , sym ))(name , sym ))
95+ self .saved_class_attrs [node , name ] = sym
10896
10997 def visit_func_def (self , node : FuncDef ) -> None :
11098 if not self .recurse_into_functions :
@@ -199,6 +187,9 @@ def process_lvalue_in_method(self, lvalue: Node) -> None:
199187 # self, since only those can define new attributes.
200188 assert self .type is not None
201189 del self .type .names [lvalue .name ]
190+ key = (self .type .defn , lvalue .name )
191+ if key in self .saved_class_attrs :
192+ del self .saved_class_attrs [key ]
202193 elif isinstance (lvalue , (TupleExpr , ListExpr )):
203194 for item in lvalue .items :
204195 self .process_lvalue_in_method (item )
0 commit comments