OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 | |
6 class BlockScope { | |
7 MethodGenerator enclosingMethod; | |
8 BlockScope parent; | |
9 | |
10 // TODO(jimhug): Using a list or tree-based map may improve perf; the list | |
11 // is normally small. | |
12 CopyOnWriteMap<String, VariableValue> _vars; | |
13 | |
14 /** Used JS names, if different from the Dart name. */ | |
15 Set<String> _jsNames; | |
16 | |
17 /** | |
18 * Variables in this method that have been captured by lambdas. | |
19 * Don't reuse the names in child blocks. | |
20 */ | |
21 Set<String> _closedOver; | |
22 | |
23 /** If we are in a catch block, this is the exception variable to rethrow. */ | |
24 String rethrow; | |
25 | |
26 /** | |
27 * True if the block is reentrant while the current method is executing. | |
28 * This is only used for the blocks within loops. | |
29 */ | |
30 bool reentrant; | |
31 | |
32 /** Tracks the node that this scope is associated with, for debugging */ | |
33 Node node; | |
34 | |
35 /** True if we should try to infer types for this block. */ | |
36 bool inferTypes; | |
37 | |
38 BlockScope(this.enclosingMethod, this.parent, this.node, | |
39 [bool reentrant = false]) | |
40 : this.reentrant = reentrant, | |
41 _vars = new CopyOnWriteMap<String, VariableValue>(), | |
42 _jsNames = new Set<String>() { | |
43 | |
44 if (isMethodScope) { | |
45 _closedOver = new Set<String>(); | |
46 } else { | |
47 // Blocks within a reentrant block are also reentrant. | |
48 this.reentrant = reentrant || parent.reentrant; | |
49 } | |
50 inferTypes = options.inferTypes && (parent == null || parent.inferTypes); | |
51 } | |
52 | |
53 /** See the [snapshot] method for a description. */ | |
54 BlockScope._snapshot(BlockScope original) | |
55 : enclosingMethod = original.enclosingMethod, | |
56 parent = original.parent == null ? null : original.parent.snapshot(), | |
57 _vars = original._vars.clone(), | |
58 node = original.node, | |
59 inferTypes = original.inferTypes, | |
60 rethrow = original.rethrow, | |
61 // TODO(jmesserly): are these this right? | |
62 _jsNames = original._jsNames, | |
63 _closedOver = original._closedOver; | |
64 | |
65 /** True if this is the top level scope of the method. */ | |
66 bool get isMethodScope() { | |
67 return parent == null || parent.enclosingMethod != enclosingMethod; | |
68 } | |
69 | |
70 /** | |
71 * Gets the method scope associated with this block scope (possibly itself). | |
72 */ | |
73 BlockScope get methodScope() { | |
74 var s = this; | |
75 while (!s.isMethodScope) s = s.parent; | |
76 return s; | |
77 } | |
78 | |
79 VariableValue lookup(String name) { | |
80 for (var s = this; s != null; s = s.parent) { | |
81 VariableValue ret = s._vars[name]; | |
82 if (ret != null) return _capture(s, ret); | |
83 } | |
84 return null; | |
85 } | |
86 | |
87 void inferAssign(String name, Value value) { | |
88 if (inferTypes) assign(name, value); | |
89 } | |
90 | |
91 void assign(String name, Value value) { | |
92 for (var s = this; s != null; s = s.parent) { | |
93 var existing = s._vars[name]; | |
94 if (existing != null) { | |
95 s._vars[name] = existing.replaceValue(value); | |
96 return; | |
97 } | |
98 } | |
99 world.internalError("assigning variable '${name}' that doesn't exist."); | |
100 } | |
101 | |
102 Value _capture(BlockScope other, Value value) { | |
103 // If this variable is from a different method, it means we closed over | |
104 // it in the child lambda. Time for some bookeeping! | |
105 if (other.enclosingMethod != enclosingMethod) { | |
106 // Make sure the parent method doesn't reuse this variable to mean | |
107 // something else. | |
108 other.methodScope._closedOver.add(value.code); | |
109 | |
110 // If the scope we found this variable in is reentrant, remember the | |
111 // variable. The lambda we're in will capture it with Function.bind. | |
112 if (enclosingMethod.captures != null && other.reentrant) { | |
113 enclosingMethod.captures.add(value.code); | |
114 } | |
115 } | |
116 return value; | |
117 } | |
118 | |
119 /** | |
120 * Returns true if we can't use this name because we would be shadowing | |
121 * another name in the JS that we might need to access later. | |
122 */ | |
123 bool _isDefinedInParent(String name) { | |
124 if (isMethodScope && _closedOver.contains(name)) return true; | |
125 | |
126 for (var s = parent; s != null; s = s.parent) { | |
127 if (s._vars.containsKey(name)) return true; | |
128 if (s._jsNames.contains(name)) return true; | |
129 // Don't reuse a name that's been closed over | |
130 if (s.isMethodScope && s._closedOver.contains(name)) return true; | |
131 } | |
132 | |
133 // Ensure that we don't shadow another name that would've been accessible, | |
134 // like top level names. | |
135 // (This lookup might report errors, which is a bit strange. | |
136 // But probably harmless since we have to pay for the lookup anyway.) | |
137 // TODO(jmesserly): does this work right if JS name of the top-level thing | |
138 // is different from Dart name? | |
139 final type = enclosingMethod.method.declaringType; | |
140 if (type.library.lookup(name, null) != null) return true; | |
141 | |
142 // Nobody else needs this name. It's safe to reuse. | |
143 return false; | |
144 } | |
145 | |
146 | |
147 VariableValue create(String name, Type type, SourceSpan span, | |
148 [bool isFinal = false, bool isParameter = false]) { | |
149 | |
150 var jsName = world.toJsIdentifier(name); | |
151 if (_vars.containsKey(name)) { | |
152 world.error('duplicate name "$name"', span); | |
153 } | |
154 | |
155 // Make sure variables don't shadow any names we might need to access. | |
156 if (!isParameter) { | |
157 int index = 0; | |
158 while (_isDefinedInParent(jsName)) { | |
159 jsName = '$name${index++}'; | |
160 } | |
161 } | |
162 | |
163 var ret = new VariableValue(type, jsName, span, isFinal); | |
164 _vars[name] = ret; | |
165 if (name != jsName) _jsNames.add(jsName); | |
166 return ret; | |
167 } | |
168 | |
169 Value declareParameter(Parameter p) { | |
170 return create(p.name, p.type, p.definition.span, isParameter:true); | |
171 } | |
172 | |
173 /** Declares a variable in the current scope for this identifier. */ | |
174 Value declare(DeclaredIdentifier id) { | |
175 var type = enclosingMethod.method.resolveType(id.type, false, true); | |
176 return create(id.name.name, type, id.span); | |
177 } | |
178 | |
179 /** | |
180 * Finds the first lexically enclosing catch block, if any, and returns its | |
181 * exception variable. | |
182 */ | |
183 String getRethrow() { | |
184 var scope = this; | |
185 while (scope.rethrow == null && scope.parent != null) { | |
186 scope = scope.parent; | |
187 } | |
188 return scope.rethrow; | |
189 } | |
190 | |
191 /** | |
192 * Creates a snapshot of an existing BlockScope. Both the original and the | |
193 * returned copy are writable. Clones all the way to the root node. | |
194 */ | |
195 // TODO(jmesserly): this might need to be optimized. | |
196 BlockScope snapshot() => new BlockScope._snapshot(this); | |
197 | |
198 /** | |
199 * Unifies variable values with the ones in [other]. Returns `true` if | |
200 * anything changed, `false` otherwise. | |
201 */ | |
202 bool unionWith(BlockScope other) { | |
203 bool changed = false; | |
204 if (parent != null) { | |
205 changed = parent.unionWith(other.parent); | |
206 } | |
207 | |
208 // Optimization: check if the copy-on-write maps are the same | |
209 if (_vars._map !== other._vars._map) { | |
210 other._vars.forEach((String key, VariableValue otherVar) { | |
211 VariableValue myVar = _vars[key]; | |
212 Value v = Value.union(myVar.value, otherVar.value); | |
213 if (myVar.value !== v) { | |
214 _vars[key] = myVar.replaceValue(v); | |
215 changed = true; | |
216 } | |
217 }); | |
218 } | |
219 | |
220 return changed; | |
221 } | |
222 } | |
OLD | NEW |