Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(177)

Side by Side Diff: lib/compiler/implementation/closure.dart

Issue 11092050: Revert "Allow closures inside lazy initializers." (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 8 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | lib/compiler/implementation/resolver.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 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 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. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 #library("closureToClassMapper"); 5 #library("closureToClassMapper");
6 6
7 #import("elements/elements.dart"); 7 #import("elements/elements.dart");
8 #import("leg.dart"); 8 #import("leg.dart");
9 #import("scanner/scannerlib.dart"); 9 #import("scanner/scannerlib.dart");
10 #import("tree/tree.dart"); 10 #import("tree/tree.dart");
11 #import("util/util.dart"); 11 #import("util/util.dart");
12 12
13 class ClosureTask extends CompilerTask { 13 class ClosureTask extends CompilerTask {
14 Map<Node, ClosureClassMap> closureMappingCache; 14 Map<Node, ClosureClassMap> closureMappingCache;
15 ClosureTask(Compiler compiler) 15 ClosureTask(Compiler compiler)
16 : closureMappingCache = new Map<Node, ClosureClassMap>(), 16 : closureMappingCache = new Map<Node, ClosureClassMap>(),
17 super(compiler); 17 super(compiler);
18 18
19 String get name => "Closure Simplifier"; 19 String get name => "Closure Simplifier";
20 20
21 ClosureClassMap computeClosureToClassMapping(Element element, 21 ClosureClassMap computeClosureToClassMapping(FunctionExpression node,
22 Expression node,
23 TreeElements elements) { 22 TreeElements elements) {
24 return measure(() { 23 return measure(() {
25 ClosureClassMap cached = closureMappingCache[node]; 24 ClosureClassMap cached = closureMappingCache[node];
26 if (cached !== null) return cached; 25 if (cached !== null) return cached;
27 26
28 ClosureTranslator translator = 27 ClosureTranslator translator =
29 new ClosureTranslator(compiler, elements, closureMappingCache); 28 new ClosureTranslator(compiler, elements, closureMappingCache);
30
31 // The translator will store the computed closure-mappings inside the 29 // The translator will store the computed closure-mappings inside the
32 // cache. One for given node and one for each nested closure. 30 // cache. One for given method and one for each nested closure.
33 if (node is FunctionExpression) { 31 translator.translate(node);
34 translator.translateFunction(element, node);
35 } else {
36 // Must be the lazy initializer of a static.
37 assert(node is SendSet);
38 translator.translateLazyInitializer(element, node);
39 }
40 assert(closureMappingCache[node] != null); 32 assert(closureMappingCache[node] != null);
41 return closureMappingCache[node]; 33 return closureMappingCache[node];
42 }); 34 });
43 } 35 }
44 36
45 ClosureClassMap getMappingForNestedFunction(FunctionExpression node) { 37 ClosureClassMap getMappingForNestedFunction(FunctionExpression node) {
46 return measure(() { 38 return measure(() {
47 ClosureClassMap nestedClosureData = closureMappingCache[node]; 39 ClosureClassMap nestedClosureData = closureMappingCache[node];
48 if (nestedClosureData === null) { 40 if (nestedClosureData === null) {
49 // TODO(floitsch): we can only assume that the reason for not having a 41 // TODO(floitsch): we can only assume that the reason for not having a
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after
114 List<Element> boxedLoopVariables; 106 List<Element> boxedLoopVariables;
115 107
116 ClosureScope(this.boxElement, this.capturedVariableMapping) 108 ClosureScope(this.boxElement, this.capturedVariableMapping)
117 : boxedLoopVariables = const <Element>[]; 109 : boxedLoopVariables = const <Element>[];
118 110
119 bool hasBoxedLoopVariables() => !boxedLoopVariables.isEmpty(); 111 bool hasBoxedLoopVariables() => !boxedLoopVariables.isEmpty();
120 } 112 }
121 113
122 class ClosureClassMap { 114 class ClosureClassMap {
123 // The closure's element before any translation. Will be null for methods. 115 // The closure's element before any translation. Will be null for methods.
124 final Element closureElement; 116 final FunctionElement closureElement;
125 // The closureClassElement will be null for methods that are not local 117 // The closureClassElement will be null for methods that are not local
126 // closures. 118 // closures.
127 final ClassElement closureClassElement; 119 final ClassElement closureClassElement;
128 // The callElement will be null for methods that are not local closures. 120 // The callElement will be null for methods that are not local closures.
129 final FunctionElement callElement; 121 final FunctionElement callElement;
130 // The [thisElement] makes handling 'this' easier by treating it like any 122 // The [thisElement] makes handling 'this' easier by treating it like any
131 // other argument. It is only set for instance-members. 123 // other argument. It is only set for instance-members.
132 final ThisElement thisElement; 124 final ThisElement thisElement;
133 125
134 // Maps free locals, arguments and function elements to their captured 126 // Maps free locals, arguments and function elements to their captured
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
171 final TreeElements elements; 163 final TreeElements elements;
172 int closureFieldCounter = 0; 164 int closureFieldCounter = 0;
173 bool inTryStatement = false; 165 bool inTryStatement = false;
174 final Map<Node, ClosureClassMap> closureMappingCache; 166 final Map<Node, ClosureClassMap> closureMappingCache;
175 167
176 // Map of captured variables. Initially they will map to themselves. If 168 // Map of captured variables. Initially they will map to themselves. If
177 // a variable needs to be boxed then the scope declaring the variable 169 // a variable needs to be boxed then the scope declaring the variable
178 // will update this mapping. 170 // will update this mapping.
179 Map<Element, Element> capturedVariableMapping; 171 Map<Element, Element> capturedVariableMapping;
180 // List of encountered closures. 172 // List of encountered closures.
181 List<Expression> closures; 173 List<FunctionExpression> closures;
182 174
183 // The variables that have been declared in the current scope. 175 // The variables that have been declared in the current scope.
184 List<Element> scopeVariables; 176 List<Element> scopeVariables;
185 177
186 // Keep track of the mutated variables so that we don't need to box 178 // Keep track of the mutated variables so that we don't need to box
187 // non-mutated variables. 179 // non-mutated variables.
188 Set<Element> mutatedVariables; 180 Set<Element> mutatedVariables;
189 181
190 Element outermostElement; 182 FunctionElement outermostFunctionElement;
191 Element currentElement; 183 FunctionElement currentFunctionElement;
192 184
193 // The closureData of the currentFunctionElement. 185 // The closureData of the currentFunctionElement.
194 ClosureClassMap closureData; 186 ClosureClassMap closureData;
195 187
196 bool insideClosure = false; 188 bool insideClosure = false;
197 189
198 ClosureTranslator(this.compiler, this.elements, this.closureMappingCache) 190 ClosureTranslator(this.compiler, this.elements, this.closureMappingCache)
199 : capturedVariableMapping = new Map<Element, Element>(), 191 : capturedVariableMapping = new Map<Element, Element>(),
200 closures = <Expression>[], 192 closures = <FunctionExpression>[],
201 mutatedVariables = new Set<Element>(); 193 mutatedVariables = new Set<Element>();
202 194
203 void translateFunction(Element element, FunctionExpression node) { 195 void translate(Node node) {
204 // For constructors the [element] and the [:elements[node]:] may differ. 196 visit(node);
205 // The [:elements[node]:] always points to the generative-constructor
206 // element, whereas the [element] might be the constructor-body element.
207 visit(node); // [visitFunctionExpression] will call [visitInvokable].
208 // When variables need to be boxed their [capturedVariableMapping] is 197 // When variables need to be boxed their [capturedVariableMapping] is
209 // updated, but we delay updating the similar freeVariableMapping in the 198 // updated, but we delay updating the similar freeVariableMapping in the
210 // closure datas that capture these variables. 199 // closure datas that capture these variables.
211 // The closures don't have their fields (in the closure class) set, either. 200 // The closures don't have their fields (in the closure class) set, either.
212 updateClosures(); 201 updateClosures();
213 } 202 }
214 203
215 void translateLazyInitializer(Element element, SendSet node) {
216 assert(node.assignmentOperator.source == const SourceString("="));
217 Expression initialValue = node.argumentsNode.nodes.head;
218 visitInvokable(element, node, () { visit(initialValue); });
219 updateClosures();
220 }
221
222 // This function runs through all of the existing closures and updates their 204 // This function runs through all of the existing closures and updates their
223 // free variables to the boxed value. It also adds the field-elements to the 205 // free variables to the boxed value. It also adds the field-elements to the
224 // class representing the closure. At the same time it fills the 206 // class representing the closure. At the same time it fills the
225 // [capturedFieldMapping]. 207 // [capturedFieldMapping].
226 void updateClosures() { 208 void updateClosures() {
227 for (Expression closure in closures) { 209 for (FunctionExpression closure in closures) {
228 // The captured variables that need to be stored in a field of the closure 210 // The captured variables that need to be stored in a field of the closure
229 // class. 211 // class.
230 Set<Element> fieldCaptures = new Set<Element>(); 212 Set<Element> fieldCaptures = new Set<Element>();
231 ClosureClassMap data = closureMappingCache[closure]; 213 ClosureClassMap data = closureMappingCache[closure];
232 Map<Element, Element> freeVariableMapping = data.freeVariableMapping; 214 Map<Element, Element> freeVariableMapping = data.freeVariableMapping;
233 // We get a copy of the keys and iterate over it, to avoid modifications 215 // We get a copy of the keys and iterate over it, to avoid modifications
234 // to the map while iterating over it. 216 // to the map while iterating over it.
235 freeVariableMapping.getKeys().forEach((Element fromElement) { 217 freeVariableMapping.getKeys().forEach((Element fromElement) {
236 assert(fromElement == freeVariableMapping[fromElement]); 218 assert(fromElement == freeVariableMapping[fromElement]);
237 Element updatedElement = capturedVariableMapping[fromElement]; 219 Element updatedElement = capturedVariableMapping[fromElement];
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
271 } 253 }
272 254
273 void useLocal(Element element) { 255 void useLocal(Element element) {
274 // If the element is not declared in the current function and the element 256 // If the element is not declared in the current function and the element
275 // is not the closure itself we need to mark the element as free variable. 257 // is not the closure itself we need to mark the element as free variable.
276 // Note that the check on [insideClosure] is not just an 258 // Note that the check on [insideClosure] is not just an
277 // optimization: factories have type parameters as function 259 // optimization: factories have type parameters as function
278 // parameters, and type parameters are declared in the class, not 260 // parameters, and type parameters are declared in the class, not
279 // the factory. 261 // the factory.
280 if (insideClosure && 262 if (insideClosure &&
281 element.enclosingElement != currentElement && 263 element.enclosingElement != currentFunctionElement &&
282 element != currentElement) { 264 element != currentFunctionElement) {
283 assert(closureData.freeVariableMapping[element] == null || 265 assert(closureData.freeVariableMapping[element] == null ||
284 closureData.freeVariableMapping[element] == element); 266 closureData.freeVariableMapping[element] == element);
285 closureData.freeVariableMapping[element] = element; 267 closureData.freeVariableMapping[element] = element;
286 } else if (inTryStatement) { 268 } else if (inTryStatement) {
287 // Don't mark the this-element. This would complicate things in the 269 // Don't mark the this-element. This would complicate things in the
288 // builder. 270 // builder.
289 if (element != closureData.thisElement) { 271 if (element != closureData.thisElement) {
290 // TODO(ngeoffray): only do this if the variable is mutated. 272 // TODO(ngeoffray): only do this if the variable is mutated.
291 closureData.usedVariablesInTry.add(element); 273 closureData.usedVariablesInTry.add(element);
292 } 274 }
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after
384 void analyzeTypeVariables(DartType type) { 366 void analyzeTypeVariables(DartType type) {
385 if (type is TypeVariableType) { 367 if (type is TypeVariableType) {
386 useLocal(type.element); 368 useLocal(type.element);
387 } else if (type is InterfaceType) { 369 } else if (type is InterfaceType) {
388 InterfaceType ifcType = type; 370 InterfaceType ifcType = type;
389 for (DartType argument in ifcType.arguments) { 371 for (DartType argument in ifcType.arguments) {
390 analyzeTypeVariables(argument); 372 analyzeTypeVariables(argument);
391 } 373 }
392 } 374 }
393 } 375 }
394 if (outermostElement.isMember() && 376 if (outermostFunctionElement.isMember() &&
395 compiler.world.needsRti(outermostElement.getEnclosingClass())) { 377 compiler.world.needsRti(outermostFunctionElement.getEnclosingClass())) {
396 if (outermostElement.isInstanceMember() 378 if (outermostFunctionElement.isInstanceMember()
397 || outermostElement.isGenerativeConstructor()) { 379 || outermostFunctionElement.isGenerativeConstructor()) {
398 if (hasTypeVariable(type)) useLocal(closureData.thisElement); 380 if (hasTypeVariable(type)) useLocal(closureData.thisElement);
399 } else if (outermostElement.isFactoryConstructor()) { 381 } else if (outermostFunctionElement.isFactoryConstructor()) {
400 analyzeTypeVariables(type); 382 analyzeTypeVariables(type);
401 } 383 }
402 } 384 }
403 385
404 node.visitChildren(this); 386 node.visitChildren(this);
405 } 387 }
406 388
407 // If variables that are declared in the [node] scope are captured and need 389 // If variables that are declared in the [node] scope are captured and need
408 // to be boxed create a box-element and update the [capturingScopes] in the 390 // to be boxed create a box-element and update the [capturingScopes] in the
409 // current [closureData]. 391 // current [closureData].
410 // The boxed variables are updated in the [capturedVariableMapping]. 392 // The boxed variables are updated in the [capturedVariableMapping].
411 void attachCapturedScopeVariables(Node node) { 393 void attachCapturedScopeVariables(Node node) {
412 Element box = null; 394 Element box = null;
413 Map<Element, Element> scopeMapping = new Map<Element, Element>(); 395 Map<Element, Element> scopeMapping = new Map<Element, Element>();
414 for (Element element in scopeVariables) { 396 for (Element element in scopeVariables) {
415 // No need to box non-assignable elements. 397 // No need to box non-assignable elements.
416 if (!element.isAssignable()) continue; 398 if (!element.isAssignable()) continue;
417 if (!mutatedVariables.contains(element)) continue; 399 if (!mutatedVariables.contains(element)) continue;
418 if (capturedVariableMapping.containsKey(element)) { 400 if (capturedVariableMapping.containsKey(element)) {
419 if (box == null) { 401 if (box == null) {
420 // TODO(floitsch): construct better box names. 402 // TODO(floitsch): construct better box names.
421 SourceString boxName = 403 SourceString boxName =
422 new SourceString("box_${closureFieldCounter++}"); 404 new SourceString("box_${closureFieldCounter++}");
423 box = new BoxElement(boxName, currentElement); 405 box = new BoxElement(boxName, currentFunctionElement);
424 } 406 }
425 // TODO(floitsch): construct better boxed names. 407 // TODO(floitsch): construct better boxed names.
426 String elementName = element.name.slowToString(); 408 String elementName = element.name.slowToString();
427 // We are currently using the name in an HForeign which could replace 409 // We are currently using the name in an HForeign which could replace
428 // "$X" with something else. 410 // "$X" with something else.
429 String escaped = elementName.replaceAll("\$", "_"); 411 String escaped = elementName.replaceAll("\$", "_");
430 SourceString boxedName = 412 SourceString boxedName =
431 new SourceString("${escaped}_${closureFieldCounter++}"); 413 new SourceString("${escaped}_${closureFieldCounter++}");
432 Element boxed = new Element(boxedName, ElementKind.FIELD, box); 414 Element boxed = new Element(boxedName, ElementKind.FIELD, box);
433 scopeMapping[element] = boxed; 415 scopeMapping[element] = boxed;
(...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after
520 globalizedElement); 502 globalizedElement);
521 globalizedElement.backendMembers = 503 globalizedElement.backendMembers =
522 const EmptyLink<Element>().prepend(callElement); 504 const EmptyLink<Element>().prepend(callElement);
523 // The nested function's 'this' is the same as the one for the outer 505 // The nested function's 'this' is the same as the one for the outer
524 // function. It could be [null] if we are inside a static method. 506 // function. It could be [null] if we are inside a static method.
525 Element thisElement = closureData.thisElement; 507 Element thisElement = closureData.thisElement;
526 return new ClosureClassMap(element, globalizedElement, 508 return new ClosureClassMap(element, globalizedElement,
527 callElement, thisElement); 509 callElement, thisElement);
528 } 510 }
529 511
530 void visitInvokable(Element element, Expression node, void visitChildren()) { 512 visitFunctionExpression(FunctionExpression node) {
513 Element element = elements[node];
514 if (element.isParameter()) {
515 // TODO(ahe): This is a hack. This method should *not* call
516 // visitChildren.
517 return node.name.accept(this);
518 }
519
531 bool oldInsideClosure = insideClosure; 520 bool oldInsideClosure = insideClosure;
532 Element oldFunctionElement = currentElement; 521 FunctionElement oldFunctionElement = currentFunctionElement;
533 ClosureClassMap oldClosureData = closureData; 522 ClosureClassMap oldClosureData = closureData;
534 523
535 insideClosure = outermostElement != null; 524 insideClosure = outermostFunctionElement != null;
536 currentElement = element; 525 currentFunctionElement = element;
537 if (insideClosure) { 526 if (insideClosure) {
538 closures.add(node); 527 closures.add(node);
539 closureData = globalizeClosure(node, element); 528 closureData = globalizeClosure(node, element);
540 } else { 529 } else {
541 outermostElement = element; 530 outermostFunctionElement = element;
542 Element thisElement = null; 531 Element thisElement = null;
543 if (element.isInstanceMember() || element.isGenerativeConstructor()) { 532 if (element.isInstanceMember() || element.isGenerativeConstructor()) {
544 thisElement = new ThisElement(element); 533 thisElement = new ThisElement(element);
545 } 534 }
546 closureData = new ClosureClassMap(null, null, null, thisElement); 535 closureData = new ClosureClassMap(null, null, null, thisElement);
547 } 536 }
548 closureMappingCache[node] = closureData; 537 closureMappingCache[node] = closureData;
549 538
550 inNewScope(node, () { 539 inNewScope(node, () {
551 // We have to declare the implicit 'this' parameter. 540 // We have to declare the implicit 'this' parameter.
552 if (!insideClosure && closureData.thisElement !== null) { 541 if (!insideClosure && closureData.thisElement !== null) {
553 declareLocal(closureData.thisElement); 542 declareLocal(closureData.thisElement);
554 } 543 }
555 // If we are inside a named closure we have to declare ourselve. For 544 // If we are inside a named closure we have to declare ourselve. For
556 // simplicity we declare the local even if the closure does not have a 545 // simplicity we declare the local even if the closure does not have a
557 // name. 546 // name.
558 // It will simply not be used. 547 // It will simply not be used.
559 if (insideClosure) { 548 if (insideClosure) {
560 declareLocal(element); 549 declareLocal(element);
561 } 550 }
562 551
563 if (currentElement.isFactoryConstructor() 552 if (currentFunctionElement.isFactoryConstructor()
564 && compiler.world.needsRti(currentElement.enclosingElement)) { 553 && compiler.world.needsRti(currentFunctionElement.enclosingElement)) {
565 // Declare the type parameters in the scope. Generative 554 // Declare the type parameters in the scope. Generative
566 // constructors just use 'this'. 555 // constructors just use 'this'.
567 ClassElement cls = currentElement.enclosingElement; 556 ClassElement cls = currentFunctionElement.enclosingElement;
568 cls.typeVariables.forEach((TypeVariableType typeVariable) { 557 cls.typeVariables.forEach((TypeVariableType typeVariable) {
569 declareLocal(typeVariable.element); 558 declareLocal(typeVariable.element);
570 }); 559 });
571 } 560 }
572 561
573 visitChildren(); 562 // TODO(ahe): This is problematic. The backend should not repeat
563 // the work of the resolver. It is the resolver's job to create
564 // parameters, etc. Other phases should only visit statements.
565 // TODO(floitsch): we avoid visiting the initializers on purpose so that
566 // we get an error-message later in the builder.
567 if (node.parameters !== null) node.parameters.accept(this);
568 if (node.body !== null) node.body.accept(this);
574 }); 569 });
575 570
576 571
577 ClosureClassMap savedClosureData = closureData; 572 ClosureClassMap savedClosureData = closureData;
578 bool savedInsideClosure = insideClosure; 573 bool savedInsideClosure = insideClosure;
579 574
580 // Restore old values. 575 // Restore old values.
581 insideClosure = oldInsideClosure; 576 insideClosure = oldInsideClosure;
582 closureData = oldClosureData; 577 closureData = oldClosureData;
583 currentElement = oldFunctionElement; 578 currentFunctionElement = oldFunctionElement;
584 579
585 // Mark all free variables as captured and use them in the outer function. 580 // Mark all free variables as captured and use them in the outer function.
586 List<Element> freeVariables = 581 List<Element> freeVariables =
587 savedClosureData.freeVariableMapping.getKeys(); 582 savedClosureData.freeVariableMapping.getKeys();
588 assert(freeVariables.isEmpty() || savedInsideClosure); 583 assert(freeVariables.isEmpty() || savedInsideClosure);
589 for (Element freeElement in freeVariables) { 584 for (Element freeElement in freeVariables) {
590 if (capturedVariableMapping[freeElement] != null && 585 if (capturedVariableMapping[freeElement] != null &&
591 capturedVariableMapping[freeElement] != freeElement) { 586 capturedVariableMapping[freeElement] != freeElement) {
592 compiler.internalError('In closure analyzer', node: node); 587 compiler.internalError('In closure analyzer', node: node);
593 } 588 }
594 capturedVariableMapping[freeElement] = freeElement; 589 capturedVariableMapping[freeElement] = freeElement;
595 useLocal(freeElement); 590 useLocal(freeElement);
596 } 591 }
597 } 592 }
598 593
599 visitFunctionExpression(FunctionExpression node) {
600 Element element = elements[node];
601
602 if (element.isParameter()) {
603 // TODO(ahe): This is a hack. This method should *not* call
604 // visitChildren.
605 return node.name.accept(this);
606 }
607
608 visitInvokable(element, node, () {
609 // TODO(ahe): This is problematic. The backend should not repeat
610 // the work of the resolver. It is the resolver's job to create
611 // parameters, etc. Other phases should only visit statements.
612 // TODO(floitsch): we avoid visiting the initializers on purpose so that
613 // we get an error-message later in the builder.
614 if (node.parameters !== null) node.parameters.accept(this);
615 if (node.body !== null) node.body.accept(this);
616 });
617 }
618
619 visitFunctionDeclaration(FunctionDeclaration node) { 594 visitFunctionDeclaration(FunctionDeclaration node) {
620 node.visitChildren(this); 595 node.visitChildren(this);
621 declareLocal(elements[node]); 596 declareLocal(elements[node]);
622 } 597 }
623 598
624 visitTryStatement(TryStatement node) { 599 visitTryStatement(TryStatement node) {
625 // TODO(ngeoffray): implement finer grain state. 600 // TODO(ngeoffray): implement finer grain state.
626 bool oldInTryStatement = inTryStatement; 601 bool oldInTryStatement = inTryStatement;
627 inTryStatement = true; 602 inTryStatement = true;
628 node.visitChildren(this); 603 node.visitChildren(this);
629 inTryStatement = oldInTryStatement; 604 inTryStatement = oldInTryStatement;
630 } 605 }
631 } 606 }
OLDNEW
« no previous file with comments | « no previous file | lib/compiler/implementation/resolver.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698