| 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 |