4
4
"""
5
5
6
6
import contextlib
7
- from typing import Union , Iterator , Optional , List , Callable
7
+ from typing import Union , Iterator , Optional , List , Callable , Dict , Tuple
8
8
9
9
from mypy .nodes import (
10
10
FuncDef , NameExpr , MemberExpr , RefExpr , MypyFile , ClassDef , AssignmentStmt ,
18
18
from mypy .server .aststrip import nothing
19
19
20
20
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 :
23
26
"""Reset a fine-grained incremental target to state before main pass of semantic analysis.
24
27
25
28
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]
28
31
defined as attributes on self. This is done by patches (callbacks) returned from this function
29
32
that re-add these variables when called.
30
33
"""
31
- visitor = NodeStripVisitor ()
34
+ visitor = NodeStripVisitor (saved_attrs )
32
35
if isinstance (node , MypyFile ):
33
36
visitor .strip_file_top_level (node )
34
37
else :
35
38
node .accept (visitor )
36
- return visitor .patches
37
39
38
40
39
41
class NodeStripVisitor (TraverserVisitor ):
40
- def __init__ (self ) -> None :
42
+ def __init__ (self , saved_class_attrs : SavedAttributes ) -> None :
41
43
# The current active class.
42
44
self .type = None # type: Optional[TypeInfo]
43
45
# This is True at class scope, but not in methods.
44
46
self .is_class_body = False
45
47
# By default, process function definitions. If False, don't -- this is used for
46
48
# processing module top levels.
47
49
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
49
56
50
57
def strip_file_top_level (self , file_node : MypyFile ) -> None :
51
58
"""Strip a module top-level (don't recursive into functions)."""
@@ -66,7 +73,7 @@ def visit_class_def(self, node: ClassDef) -> None:
66
73
# be lost if we only reprocess top-levels (this kills TypeInfos)
67
74
# but not the methods that defined those variables.
68
75
if not self .recurse_into_functions :
69
- self .prepare_implicit_var_patches (node )
76
+ self .save_implicit_attributes (node )
70
77
# We need to delete any entries that were generated by plugins,
71
78
# since they will get regenerated.
72
79
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:
81
88
# Kill the TypeInfo, since there is none before semantic analysis.
82
89
node .info = CLASSDEF_NO_INFO
83
90
84
- def prepare_implicit_var_patches (self , node : ClassDef ) -> None :
91
+ def save_implicit_attributes (self , node : ClassDef ) -> None :
85
92
"""Produce callbacks that re-add attributes defined on self."""
86
93
for name , sym in node .info .names .items ():
87
94
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
108
96
109
97
def visit_func_def (self , node : FuncDef ) -> None :
110
98
if not self .recurse_into_functions :
@@ -199,6 +187,9 @@ def process_lvalue_in_method(self, lvalue: Node) -> None:
199
187
# self, since only those can define new attributes.
200
188
assert self .type is not None
201
189
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 ]
202
193
elif isinstance (lvalue , (TupleExpr , ListExpr )):
203
194
for item in lvalue .items :
204
195
self .process_lvalue_in_method (item )
0 commit comments