Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 part of js_backend; | 5 part of js_backend; |
| 6 | 6 |
| 7 /** | 7 /** |
| 8 * A function element that represents a closure call. The signature is copied | 8 * A function element that represents a closure call. The signature is copied |
| 9 * from the given element. | 9 * from the given element. |
| 10 */ | 10 */ |
| (...skipping 272 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 283 // Example: | 283 // Example: |
| 284 // defineClass("A", ["x", "y"], { | 284 // defineClass("A", ["x", "y"], { |
| 285 // foo$1: function(y) { | 285 // foo$1: function(y) { |
| 286 // print(this.x + y); | 286 // print(this.x + y); |
| 287 // }, | 287 // }, |
| 288 // bar$2: function(t, v) { | 288 // bar$2: function(t, v) { |
| 289 // this.x = t - v; | 289 // this.x = t - v; |
| 290 // }, | 290 // }, |
| 291 // }); | 291 // }); |
| 292 | 292 |
| 293 // function(cls, fields, prototype) { | 293 var defineClass = js.fun(['name', 'cls', 'fields', 'prototype'], [ |
| 294 var defineClass = js.fun(['cls', 'fields', 'prototype'], [ | |
| 295 js('var constructor'), | 294 js('var constructor'), |
| 296 | 295 |
| 297 // if (typeof fields == "function") { | |
| 298 js.if_(js('typeof fields == "function"'), [ | 296 js.if_(js('typeof fields == "function"'), [ |
| 299 js('constructor = fields') | 297 js('constructor = fields') |
| 300 ], /* else */ [ | 298 ], /* else */ [ |
| 301 js('var str = "function " + cls + "("'), | 299 js('var str = "function " + cls + "("'), |
| 302 js('var body = ""'), | 300 js('var body = ""'), |
| 303 | 301 |
| 304 // for (var i = 0; i < fields.length; i++) { | |
| 305 js.for_('var i = 0', 'i < fields.length', 'i++', [ | 302 js.for_('var i = 0', 'i < fields.length', 'i++', [ |
| 306 // if (i != 0) str += ", "; | |
| 307 js.if_('i != 0', js('str += ", "')), | 303 js.if_('i != 0', js('str += ", "')), |
| 308 | 304 |
| 309 js('var field = fields[i]'), | 305 js('var field = fields[i]'), |
| 310 js('field = generateAccessor(field, prototype)'), | 306 js('field = generateAccessor(field, prototype)'), |
| 311 js('str += field'), | 307 js('str += field'), |
| 312 js('body += ("this." + field + " = " + field + ";\\n")') | 308 js('body += ("this." + field + " = " + field + ";\\n")') |
| 313 ]), | 309 ]), |
| 314 | 310 |
| 315 js('str += (") {" + body + "}\\nreturn " + cls)'), | 311 js('str += (") {" + body + "}\\nreturn " + cls)'), |
| 316 | 312 |
| 317 js('constructor = new Function(str)()') | 313 js('constructor = new Function(str)()') |
| 318 ]), | 314 ]), |
| 319 | 315 |
| 320 js('constructor.prototype = prototype'), | 316 js('constructor.prototype = prototype'), |
| 321 js('constructor.builtin\$cls = cls'), | 317 js(r'constructor.builtin$cls = name'), |
| 322 | 318 |
| 323 // return constructor; | |
| 324 js.return_('constructor') | 319 js.return_('constructor') |
| 325 ]); | 320 ]); |
| 326 // Declare a function called "generateAccessor". This is used in | 321 // Declare a function called "generateAccessor". This is used in |
| 327 // defineClassFunction (it's a local declaration in init()). | 322 // defineClassFunction (it's a local declaration in init()). |
| 328 return [ | 323 return [ |
| 329 generateAccessorFunction, | 324 generateAccessorFunction, |
| 330 js('$generateAccessorHolder = generateAccessor'), | 325 js('$generateAccessorHolder = generateAccessor'), |
| 331 new jsAst.FunctionDeclaration( | 326 new jsAst.FunctionDeclaration( |
| 332 new jsAst.VariableDeclaration('defineClass'), defineClass) ]; | 327 new jsAst.VariableDeclaration('defineClass'), defineClass) ]; |
| 333 } | 328 } |
| 334 | 329 |
| 335 /** Needs defineClass to be defined. */ | 330 /** Needs defineClass to be defined. */ |
| 336 List buildProtoSupportCheck() { | 331 List buildProtoSupportCheck() { |
| 337 // On Firefox and Webkit browsers we can manipulate the __proto__ | 332 // On Firefox and Webkit browsers we can manipulate the __proto__ |
| 338 // directly. Opera claims to have __proto__ support, but it is buggy. | 333 // directly. Opera claims to have __proto__ support, but it is buggy. |
| 339 // So we have to do more checks. | 334 // So we have to do more checks. |
| 340 // Opera bug was filed as DSK-370158, and fixed as CORE-47615 | 335 // Opera bug was filed as DSK-370158, and fixed as CORE-47615 |
| 341 // (http://my.opera.com/desktopteam/blog/2012/07/20/more-12-01-fixes). | 336 // (http://my.opera.com/desktopteam/blog/2012/07/20/more-12-01-fixes). |
| 342 // If the browser does not support __proto__ we need to instantiate an | 337 // If the browser does not support __proto__ we need to instantiate an |
| 343 // object with the correct (internal) prototype set up correctly, and then | 338 // object with the correct (internal) prototype set up correctly, and then |
| 344 // copy the members. | 339 // copy the members. |
| 345 // TODO(8541): Remove this work around. | 340 // TODO(8541): Remove this work around. |
| 346 | 341 |
| 347 return [ | 342 return [ |
| 348 js('var $supportsProtoName = false'), | 343 js('var $supportsProtoName = false'), |
| 349 js('var tmp = defineClass("c", ["f?"], {}).prototype'), | 344 js('var tmp = defineClass("c", "c", ["f?"], {}).prototype'), |
| 350 | 345 |
| 351 js.if_(js('tmp.__proto__'), [ | 346 js.if_(js('tmp.__proto__'), [ |
| 352 js('tmp.__proto__ = {}'), | 347 js('tmp.__proto__ = {}'), |
| 353 js.if_(js(r'typeof tmp.get$f != "undefined"'), | 348 js.if_(js(r'typeof tmp.get$f != "undefined"'), |
| 354 js('$supportsProtoName = true')) | 349 js('$supportsProtoName = true')) |
| 355 | 350 |
| 356 ]) | 351 ]) |
| 357 ]; | 352 ]; |
| 358 } | 353 } |
| 359 | 354 |
| (...skipping 227 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 587 // constructor. | 582 // constructor. |
| 588 // For engines where we have access to the '__proto__' we can manipulate | 583 // For engines where we have access to the '__proto__' we can manipulate |
| 589 // the object literal directly. For other engines we have to create a new | 584 // the object literal directly. For other engines we have to create a new |
| 590 // object and copy over the members. | 585 // object and copy over the members. |
| 591 | 586 |
| 592 List<jsAst.Node> statements = [ | 587 List<jsAst.Node> statements = [ |
| 593 js('var pendingClasses = {}'), | 588 js('var pendingClasses = {}'), |
| 594 | 589 |
| 595 js('var hasOwnProperty = Object.prototype.hasOwnProperty'), | 590 js('var hasOwnProperty = Object.prototype.hasOwnProperty'), |
| 596 | 591 |
| 597 // for (var cls in collectedClasses) | |
| 598 js.forIn('cls', 'collectedClasses', [ | 592 js.forIn('cls', 'collectedClasses', [ |
| 599 // if (hasOwnProperty.call(collectedClasses, cls)) | |
| 600 js.if_('hasOwnProperty.call(collectedClasses, cls)', [ | 593 js.if_('hasOwnProperty.call(collectedClasses, cls)', [ |
| 601 js('var desc = collectedClasses[cls]'), | 594 js('var desc = collectedClasses[cls]'), |
| 602 | 595 |
| 603 /* The 'fields' are either a constructor function or a | 596 /* The 'fields' are either a constructor function or a |
| 604 * string encoding fields, constructor and superclass. Get | 597 * string encoding fields, constructor and superclass. Get |
| 605 * the superclass and the fields in the format | 598 * the superclass and the fields in the format |
| 606 * Super;field1,field2 from the null-string property on the | 599 * name/Super;field1,field2 from the null-string property on the |
| 607 * descriptor. | 600 * descriptor. |
| 608 */ | 601 */ |
| 609 // var fields = desc[""], supr; | 602 js('var classData = desc[""], supr, name, fields'), |
| 610 js('var fields = desc[""], supr'), | 603 js('var split = classData.split("/")'), |
| 604 js.if_('split.length == 2', [ | |
| 605 js('name = split[0]'), | |
| 606 js('fields = split[1]') | |
| 607 ], /* else */ [ | |
| 608 js('name = cls'), | |
| 609 js('fields = classData') | |
| 610 ]), | |
| 611 | 611 |
| 612 js.if_('typeof fields == "string"', [ | 612 js.if_('typeof fields == "string"', [ |
| 613 js('var s = fields.split(";")'), | 613 js('var s = fields.split(";")'), |
| 614 js('supr = s[0]'), | 614 js('supr = s[0]'), |
| 615 js('fields = s[1] == "" ? [] : s[1].split(",")'), | 615 js('fields = s[1] == "" ? [] : s[1].split(",")'), |
| 616 ], /* else */ [ | 616 ], /* else */ [ |
| 617 js('supr = desc.super') | 617 js('supr = desc.super') |
| 618 ]), | 618 ]), |
| 619 | 619 |
| 620 js('isolateProperties[cls] = defineClass(cls, fields, desc)'), | 620 js('isolateProperties[cls] = defineClass(name, cls, fields, desc)'), |
| 621 | 621 |
| 622 // if (supr) pendingClasses[cls] = supr; | |
| 623 js.if_('supr', js('pendingClasses[cls] = supr')) | 622 js.if_('supr', js('pendingClasses[cls] = supr')) |
| 624 ]) | 623 ]) |
| 625 ]), | 624 ]), |
| 626 | 625 |
| 627 js('var finishedClasses = {}'), | 626 js('var finishedClasses = {}'), |
| 628 | 627 |
| 629 // function finishClass(cls) { ... } | 628 // function finishClass(cls) { ... } |
| 630 buildFinishClass(), | 629 buildFinishClass(), |
| 631 ]; | 630 ]; |
| 632 | 631 |
| (...skipping 539 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1172 classElement.implementation.forEachMember( | 1171 classElement.implementation.forEachMember( |
| 1173 visitMember, | 1172 visitMember, |
| 1174 includeBackendMembers: true, | 1173 includeBackendMembers: true, |
| 1175 includeSuperMembers: false); | 1174 includeSuperMembers: false); |
| 1176 | 1175 |
| 1177 void generateIsTest(Element other) { | 1176 void generateIsTest(Element other) { |
| 1178 if (other == compiler.objectClass && other != classElement) { | 1177 if (other == compiler.objectClass && other != classElement) { |
| 1179 // Avoid emitting [:$isObject:] on all classes but [Object]. | 1178 // Avoid emitting [:$isObject:] on all classes but [Object]. |
| 1180 return; | 1179 return; |
| 1181 } | 1180 } |
| 1181 other = backend.getImplementationClass(other); | |
| 1182 builder.addProperty(namer.operatorIs(other), js('true')); | 1182 builder.addProperty(namer.operatorIs(other), js('true')); |
| 1183 } | 1183 } |
| 1184 | 1184 |
| 1185 void generateSubstitution(Element other, {bool emitNull: false}) { | 1185 void generateSubstitution(Element other, {bool emitNull: false}) { |
| 1186 RuntimeTypes rti = backend.rti; | 1186 RuntimeTypes rti = backend.rti; |
| 1187 // TODO(karlklose): support typedefs with variables. | 1187 // TODO(karlklose): support typedefs with variables. |
| 1188 jsAst.Expression expression; | 1188 jsAst.Expression expression; |
| 1189 bool needsNativeCheck = nativeEmitter.requiresNativeIsCheck(other); | 1189 bool needsNativeCheck = nativeEmitter.requiresNativeIsCheck(other); |
| 1190 if (other.kind == ElementKind.CLASS) { | 1190 if (other.kind == ElementKind.CLASS) { |
| 1191 String substitution = rti.getSupertypeSubstitution(classElement, other, | 1191 String substitution = rti.getSupertypeSubstitution(classElement, other, |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 1210 // them. Note that this helper is invoked before analyzing the | 1210 // them. Note that this helper is invoked before analyzing the |
| 1211 // full JS script. | 1211 // full JS script. |
| 1212 if (!nativeEmitter.handleNoSuchMethod) { | 1212 if (!nativeEmitter.handleNoSuchMethod) { |
| 1213 emitNoSuchMethodHandlers(builder.addProperty); | 1213 emitNoSuchMethodHandlers(builder.addProperty); |
| 1214 } | 1214 } |
| 1215 } | 1215 } |
| 1216 } | 1216 } |
| 1217 | 1217 |
| 1218 void emitRuntimeTypeSupport(CodeBuffer buffer) { | 1218 void emitRuntimeTypeSupport(CodeBuffer buffer) { |
| 1219 RuntimeTypes rti = backend.rti; | 1219 RuntimeTypes rti = backend.rti; |
| 1220 TypeChecks typeChecks = rti.getRequiredChecks(); | 1220 TypeChecks typeChecks = rti.requiredChecks; |
| 1221 | 1221 |
| 1222 /// Classes that are not instantiated and native classes need a holder | 1222 // Add checks to the constructors of instantiated classes. |
| 1223 /// object for their checks, because there will be no class defined for | |
| 1224 /// them. | |
| 1225 | |
| 1226 // TODO(9556): Get rid of holders. | |
| 1227 // | |
| 1228 // - For primitive classes, use the interceptors (e.g JSInt). | |
| 1229 // | |
| 1230 // - For uninstantiated classes, define the class anyway. It will not need | |
| 1231 // fields or a constructor, or any methods, so the class definition will | |
| 1232 // be smaller than a holder. | |
| 1233 | |
| 1234 bool needsHolder(ClassElement cls) { | |
| 1235 return !neededClasses.contains(cls) || | |
| 1236 rti.isJsNative(cls); | |
| 1237 } | |
| 1238 | |
| 1239 /** | |
| 1240 * Generates a holder object if it is needed. A holder is a JavaScript | |
| 1241 * object literal with a field [builtin$cls] that contains the name of the | |
| 1242 * class as a string (just like object constructors do). The is-checks for | |
| 1243 * the class are are added to the holder object later. | |
| 1244 */ | |
| 1245 void maybeGenerateHolder(ClassElement cls) { | |
| 1246 if (!needsHolder(cls)) return; | |
| 1247 String holder = namer.isolateAccess(cls); | |
| 1248 String name = namer.getRuntimeTypeName(cls); | |
| 1249 buffer.write('$holder$_=$_{builtin\$cls:$_"$name"'); | |
| 1250 buffer.write('}$N'); | |
| 1251 } | |
| 1252 | |
| 1253 // Create representation objects for classes that we do not have a class | |
| 1254 // definition for (because they are uninstantiated or native). | |
| 1255 for (ClassElement cls in rti.allArguments) { | |
| 1256 maybeGenerateHolder(cls); | |
| 1257 } | |
| 1258 | |
| 1259 // Add checks to the constructors of instantiated classes or to the created | |
| 1260 // holder object. | |
| 1261 for (ClassElement cls in typeChecks) { | 1223 for (ClassElement cls in typeChecks) { |
| 1262 String holder = namer.isolateAccess(cls); | 1224 String holder = namer.isolateAccess(backend.getImplementationClass(cls)); |
| 1263 for (ClassElement check in typeChecks[cls]) { | 1225 for (ClassElement check in typeChecks[cls]) { |
| 1264 buffer.write('$holder.${namer.operatorIs(check)}$_=${_}true$N'); | 1226 buffer.write('$holder.${namer.operatorIs(check)}$_=${_}true$N'); |
| 1265 String body = rti.getSupertypeSubstitution(cls, check); | 1227 String body = rti.getSupertypeSubstitution(cls, check); |
| 1266 if (body != null) { | 1228 if (body != null) { |
| 1267 buffer.write('$holder.${namer.substitutionName(check)}$_=${_}$body$N') ; | 1229 buffer.write('$holder.${namer.substitutionName(check)}$_=${_}$body$N') ; |
| 1268 } | 1230 } |
| 1269 }; | 1231 }; |
| 1270 } | 1232 } |
| 1271 } | 1233 } |
| 1272 | 1234 |
| (...skipping 154 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1427 /* Do nothing. */ | 1389 /* Do nothing. */ |
| 1428 } | 1390 } |
| 1429 | 1391 |
| 1430 /// Returns `true` if fields added. | 1392 /// Returns `true` if fields added. |
| 1431 bool emitClassFields(ClassElement classElement, | 1393 bool emitClassFields(ClassElement classElement, |
| 1432 ClassBuilder builder, | 1394 ClassBuilder builder, |
| 1433 { String superClass, | 1395 { String superClass, |
| 1434 bool classIsNative: false }) { | 1396 bool classIsNative: false }) { |
| 1435 String separator = ''; | 1397 String separator = ''; |
| 1436 StringBuffer buffer = new StringBuffer(); | 1398 StringBuffer buffer = new StringBuffer(); |
| 1399 String nativeName = namer.getNativeName(classElement); | |
| 1400 if (nativeName != null) { | |
| 1401 buffer.write('$nativeName/'); | |
| 1402 } | |
| 1437 if (superClass != null) { | 1403 if (superClass != null) { |
| 1438 buffer.write('$superClass;'); | 1404 buffer.write('$superClass;'); |
| 1439 } | 1405 } |
| 1440 int bufferClassLength = buffer.length; | 1406 int bufferClassLength = buffer.length; |
| 1441 | 1407 |
| 1442 visitClassFields(classElement, (Element member, | 1408 visitClassFields(classElement, (Element member, |
| 1443 String name, | 1409 String name, |
| 1444 String accessorName, | 1410 String accessorName, |
| 1445 bool needsGetter, | 1411 bool needsGetter, |
| 1446 bool needsSetter, | 1412 bool needsSetter, |
| (...skipping 1135 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2582 } | 2548 } |
| 2583 | 2549 |
| 2584 /** | 2550 /** |
| 2585 * Compute all the classes that must be emitted. | 2551 * Compute all the classes that must be emitted. |
| 2586 */ | 2552 */ |
| 2587 void computeNeededClasses() { | 2553 void computeNeededClasses() { |
| 2588 instantiatedClasses = | 2554 instantiatedClasses = |
| 2589 compiler.codegenWorld.instantiatedClasses.where(computeClassFilter()) | 2555 compiler.codegenWorld.instantiatedClasses.where(computeClassFilter()) |
| 2590 .toSet(); | 2556 .toSet(); |
| 2591 | 2557 |
| 2558 RuntimeTypes rti = backend.rti; | |
| 2559 | |
| 2592 // The set of classes that must be emitted are based on instantiated | 2560 // The set of classes that must be emitted are based on instantiated |
| 2593 // classes. | 2561 // classes. |
| 2594 neededClasses.addAll(instantiatedClasses); | 2562 neededClasses.addAll(instantiatedClasses); |
| 2563 rti.allArguments.forEach((ClassElement c) { | |
|
ngeoffray
2013/04/29 14:14:22
Please add a comment on why you're iterating over
karlklose
2013/04/30 09:29:41
Done.
| |
| 2564 if (!rti.isJsNative(c)) { | |
| 2565 neededClasses.add(c); | |
| 2566 } | |
| 2567 }); | |
| 2595 | 2568 |
| 2596 // Then add all superclasses of these classes. | 2569 // Then add all superclasses of these classes. |
| 2597 for (ClassElement element in neededClasses.toList() /* copy */) { | 2570 for (ClassElement element in neededClasses.toList() /* copy */) { |
| 2598 for (ClassElement superclass = element.superclass; | 2571 for (ClassElement superclass = element.superclass; |
| 2599 superclass != null; | 2572 superclass != null; |
| 2600 superclass = superclass.superclass) { | 2573 superclass = superclass.superclass) { |
| 2601 if (neededClasses.contains(superclass)) break; | 2574 if (neededClasses.contains(superclass)) break; |
| 2602 neededClasses.add(superclass); | 2575 neededClasses.add(superclass); |
| 2603 } | 2576 } |
| 2604 } | 2577 } |
| (...skipping 241 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2846 ..addAll(buildLazyInitializerFunctionIfNecessary()) | 2819 ..addAll(buildLazyInitializerFunctionIfNecessary()) |
| 2847 ..addAll(buildFinishIsolateConstructor()) | 2820 ..addAll(buildFinishIsolateConstructor()) |
| 2848 ); | 2821 ); |
| 2849 jsAst.FunctionDeclaration decl = new jsAst.FunctionDeclaration( | 2822 jsAst.FunctionDeclaration decl = new jsAst.FunctionDeclaration( |
| 2850 new jsAst.VariableDeclaration('init'), fun); | 2823 new jsAst.VariableDeclaration('init'), fun); |
| 2851 buffer.write(jsAst.prettyPrint(decl, compiler).getText()); | 2824 buffer.write(jsAst.prettyPrint(decl, compiler).getText()); |
| 2852 } | 2825 } |
| 2853 | 2826 |
| 2854 String assembleProgram() { | 2827 String assembleProgram() { |
| 2855 measure(() { | 2828 measure(() { |
| 2856 computeNeededClasses(); | |
| 2857 | |
| 2858 // Compute the required type checks to know which classes need a | 2829 // Compute the required type checks to know which classes need a |
| 2859 // 'is$' method. | 2830 // 'is$' method. |
| 2860 computeRequiredTypeChecks(); | 2831 computeRequiredTypeChecks(); |
| 2861 | 2832 |
| 2833 computeNeededClasses(); | |
| 2834 | |
| 2862 mainBuffer.add(GENERATED_BY); | 2835 mainBuffer.add(GENERATED_BY); |
| 2863 addComment(HOOKS_API_USAGE, mainBuffer); | 2836 addComment(HOOKS_API_USAGE, mainBuffer); |
| 2864 mainBuffer.add('function ${namer.isolateName}()$_{}\n'); | 2837 mainBuffer.add('function ${namer.isolateName}()$_{}\n'); |
| 2865 mainBuffer.add('init()$N$n'); | 2838 mainBuffer.add('init()$N$n'); |
| 2866 // Shorten the code by using [namer.CURRENT_ISOLATE] as temporary. | 2839 // Shorten the code by using [namer.CURRENT_ISOLATE] as temporary. |
| 2867 isolateProperties = namer.CURRENT_ISOLATE; | 2840 isolateProperties = namer.CURRENT_ISOLATE; |
| 2868 mainBuffer.add( | 2841 mainBuffer.add( |
| 2869 'var $isolateProperties$_=$_$isolatePropertiesName$N'); | 2842 'var $isolateProperties$_=$_$isolatePropertiesName$N'); |
| 2870 | 2843 |
| 2871 if (!regularClasses.isEmpty || | 2844 if (!regularClasses.isEmpty || |
| (...skipping 166 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 3038 """; | 3011 """; |
| 3039 const String HOOKS_API_USAGE = """ | 3012 const String HOOKS_API_USAGE = """ |
| 3040 // The code supports the following hooks: | 3013 // The code supports the following hooks: |
| 3041 // dartPrint(message) - if this function is defined it is called | 3014 // dartPrint(message) - if this function is defined it is called |
| 3042 // instead of the Dart [print] method. | 3015 // instead of the Dart [print] method. |
| 3043 // dartMainRunner(main) - if this function is defined, the Dart [main] | 3016 // dartMainRunner(main) - if this function is defined, the Dart [main] |
| 3044 // method will not be invoked directly. | 3017 // method will not be invoked directly. |
| 3045 // Instead, a closure that will invoke [main] is | 3018 // Instead, a closure that will invoke [main] is |
| 3046 // passed to [dartMainRunner]. | 3019 // passed to [dartMainRunner]. |
| 3047 """; | 3020 """; |
| OLD | NEW |