| 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 * Top level generator object for writing code and keeping track of | |
| 7 * dependencies. | |
| 8 * | |
| 9 * Should have two compilation models, but only one implemented so far. | |
| 10 * | |
| 11 * 1. Do a top-level resolution of all types and their members. | |
| 12 * 2. Start from main and walk the call-graph compiling members as needed. | |
| 13 * 2a. That includes compiling overriding methods and calling methods by | |
| 14 * selector when invoked on var. | |
| 15 * 3. Spit out all required code. | |
| 16 */ | |
| 17 class WorldGenerator { | |
| 18 MethodMember main; | |
| 19 CodeWriter writer; | |
| 20 CodeWriter _mixins; | |
| 21 | |
| 22 final CallingContext mainContext; | |
| 23 | |
| 24 /** | |
| 25 * Whether the app has any static fields used. Note this could still be true | |
| 26 * and [globals] be empty if no static field has a default initialization. | |
| 27 */ | |
| 28 bool hasStatics = false; | |
| 29 | |
| 30 /** Global const and static field initializations. */ | |
| 31 Map<String, GlobalValue> globals; | |
| 32 CoreJs corejs; | |
| 33 | |
| 34 /** */ | |
| 35 Set<Type> typesWithDynamicDispatch; | |
| 36 | |
| 37 /** | |
| 38 * For a type, which type-checks are on the prototype chain, and to they match | |
| 39 * or not? Type checks are in-predicates (x is T) and type assertions. | |
| 40 */ | |
| 41 Map<Type, Map<String, bool>> typeEmittedTests; | |
| 42 | |
| 43 WorldGenerator(main, this.writer) | |
| 44 : this.main = main, | |
| 45 mainContext = new MethodGenerator(main, null), | |
| 46 globals = {}, | |
| 47 corejs = new CoreJs(); | |
| 48 | |
| 49 analyze() { | |
| 50 // Walk all code and find all NewExpressions - to determine possible types | |
| 51 int nlibs=0, ntypes=0, nmems=0, nnews=0; | |
| 52 for (var lib in world.libraries.getValues()) { | |
| 53 nlibs += 1; | |
| 54 for (var type in lib.types.getValues()) { | |
| 55 // TODO(jmesserly): we can't accurately track if DOM types are | |
| 56 // created or not, so we need to prepare to handle them. | |
| 57 // This should be fixed by tightening up the return types in DOM. | |
| 58 // Until then, this 'analysis' just marks all the DOM types as used. | |
| 59 // TODO(jimhug): Do we still need this? Or do/can we handle this by | |
| 60 // using return values? | |
| 61 if (type.library.isDomOrHtml || type.isHiddenNativeType) { | |
| 62 if (type.isClass) type.markUsed(); | |
| 63 } | |
| 64 | |
| 65 ntypes += 1; | |
| 66 var allMembers = []; | |
| 67 allMembers.addAll(type.constructors.getValues()); | |
| 68 allMembers.addAll(type.members.getValues()); | |
| 69 type.factories.forEach((f) => allMembers.add(f)); | |
| 70 for (var m in allMembers) { | |
| 71 if (m.isAbstract || !(m.isMethod || m.isConstructor)) continue; | |
| 72 m.methodData.analyze(); | |
| 73 } | |
| 74 } | |
| 75 } | |
| 76 } | |
| 77 | |
| 78 run() { | |
| 79 var mainTarget = new TypeValue(main.declaringType, main.span); | |
| 80 var mainCall = main.invoke(mainContext, null, mainTarget, Arguments.EMPTY); | |
| 81 main.declaringType.markUsed(); | |
| 82 | |
| 83 if (options.compileAll) { | |
| 84 markLibrariesUsed( | |
| 85 [world.coreimpl, world.corelib, main.declaringType.library]); | |
| 86 } | |
| 87 | |
| 88 // These are essentially always used through literals - just include them | |
| 89 world.numImplType.markUsed(); | |
| 90 world.stringImplType.markUsed(); | |
| 91 | |
| 92 if (corejs.useIndex || corejs.useSetIndex) { | |
| 93 if (!options.disableBoundsChecks) { | |
| 94 // These exceptions might be thrown by array bounds checks. | |
| 95 markTypeUsed(world.corelib.types['IndexOutOfRangeException']); | |
| 96 markTypeUsed(world.corelib.types['IllegalArgumentException']); | |
| 97 } | |
| 98 } | |
| 99 | |
| 100 // Only wrap the app as an isolate if the isolate library was imported. | |
| 101 if (world.isolatelib != null) { | |
| 102 corejs.useIsolates = true; | |
| 103 MethodMember isolateMain = | |
| 104 world.isolatelib.lookup('startRootIsolate', main.span); | |
| 105 mainCall = isolateMain.invoke(mainContext, null, | |
| 106 new TypeValue(world.isolatelib.topType, main.span), | |
| 107 new Arguments(null, [main._get(mainContext, main.definition, null)])); | |
| 108 } | |
| 109 | |
| 110 typeEmittedTests = new Map<Type, Map<String, bool>>(); | |
| 111 | |
| 112 writeTypes(world.coreimpl); | |
| 113 writeTypes(world.corelib); | |
| 114 | |
| 115 // Write the main library. This will cause all libraries to be written in | |
| 116 // the topological sort order. | |
| 117 writeTypes(main.declaringType.library); | |
| 118 | |
| 119 // Write out any inherited concrete members. | |
| 120 // TODO(jmesserly): this won't need to come last once we are sorting types | |
| 121 // correctly. | |
| 122 if (_mixins != null) writer.write(_mixins.text); | |
| 123 | |
| 124 writeDynamicDispatchMetadata(); | |
| 125 | |
| 126 writeGlobals(); | |
| 127 writer.writeln("if (typeof window != 'undefined' && typeof document != 'unde
fined' &&"); | |
| 128 writer.writeln(" window.addEventListener && document.readyState == 'loadi
ng') {"); | |
| 129 writer.writeln(" window.addEventListener('DOMContentLoaded', function(e) {"
); | |
| 130 writer.writeln(" ${mainCall.code};"); | |
| 131 writer.writeln(" });"); | |
| 132 writer.writeln("} else {"); | |
| 133 writer.writeln(" ${mainCall.code};"); | |
| 134 writer.writeln("}"); | |
| 135 } | |
| 136 | |
| 137 void markLibrariesUsed(List<Library> libs) => | |
| 138 getAllTypes(libs).forEach(markTypeUsed); | |
| 139 | |
| 140 void markTypeUsed(Type type) { | |
| 141 if (!type.isClass) return; | |
| 142 | |
| 143 type.markUsed(); | |
| 144 type.isTested = true; | |
| 145 // (e.g. Math, console, process) | |
| 146 type.isTested = !type.isTop && !(type.isNative && | |
| 147 type.members.getValues().every((m) => m.isStatic && !m.isFactory)); | |
| 148 final members = new List.from(type.members.getValues()); | |
| 149 members.addAll(type.constructors.getValues()); | |
| 150 type.factories.forEach((f) => members.add(f)); | |
| 151 for (var member in members) { | |
| 152 if (member is PropertyMember) { | |
| 153 if (member.getter != null) genMethod(member.getter); | |
| 154 if (member.setter != null) genMethod(member.setter); | |
| 155 } | |
| 156 | |
| 157 if (member is MethodMember) genMethod(member); | |
| 158 } | |
| 159 } | |
| 160 | |
| 161 void writeAllDynamicStubs(List<Library> libs) => | |
| 162 getAllTypes(libs).forEach((Type type) { | |
| 163 if (type.isClass || type.isFunction) _writeDynamicStubs(type); | |
| 164 }); | |
| 165 | |
| 166 List<Type> getAllTypes(List<Library> libs) { | |
| 167 List<Type> types = <Type>[]; | |
| 168 Set<Library> seen = new Set<Library>(); | |
| 169 for (var mainLib in libs) { | |
| 170 Queue<Library> toCheck = new Queue.from([mainLib]); | |
| 171 while (!toCheck.isEmpty()) { | |
| 172 var lib = toCheck.removeFirst(); | |
| 173 if (seen.contains(lib)) continue; | |
| 174 seen.add(lib); | |
| 175 lib.imports.forEach((i) => toCheck.addLast(lib)); | |
| 176 lib.types.getValues().forEach((t) => types.add(t)); | |
| 177 } | |
| 178 } | |
| 179 return types; | |
| 180 } | |
| 181 | |
| 182 GlobalValue globalForStaticField(FieldMember field, Value exp, | |
| 183 List<Value> dependencies) { | |
| 184 hasStatics = true; | |
| 185 var key = "${field.declaringType.jsname}.${field.jsname}"; | |
| 186 var ret = globals[key]; | |
| 187 if (ret === null) { | |
| 188 ret = new GlobalValue(exp.type, exp.code, field.isFinal, field, null, | |
| 189 exp, exp.span, dependencies); | |
| 190 globals[key] = ret; | |
| 191 } | |
| 192 return ret; | |
| 193 } | |
| 194 | |
| 195 GlobalValue globalForConst(Value exp, List<Value> dependencies) { | |
| 196 // Include type name to ensure unique constants - this matches | |
| 197 // the code above that includes the type name for static fields. | |
| 198 var key = '${exp.type.jsname}:${exp.code}'; | |
| 199 var ret = globals[key]; | |
| 200 if (ret === null) { | |
| 201 // another egregious hack!!! | |
| 202 var ns = globals.length.toString(); | |
| 203 while (ns.length < 4) ns = '0$ns'; | |
| 204 var name = "const\$${ns}"; | |
| 205 ret = new GlobalValue(exp.type, name, true, null, name, exp, | |
| 206 exp.span, dependencies); | |
| 207 globals[key] = ret; | |
| 208 } | |
| 209 assert(ret.type == exp.type); | |
| 210 return ret; | |
| 211 } | |
| 212 | |
| 213 writeTypes(Library lib) { | |
| 214 if (lib.isWritten) return; | |
| 215 | |
| 216 // Do this first to be safe in the face of circular refs. | |
| 217 lib.isWritten = true; | |
| 218 | |
| 219 // Ensure all imports have been written. | |
| 220 for (var import in lib.imports) { | |
| 221 writeTypes(import.library); | |
| 222 } | |
| 223 | |
| 224 // Ensure that our source files have a notion of "order" so we can emit | |
| 225 // types in the same order source files are imported. | |
| 226 for (int i = 0; i < lib.sources.length; i++) { | |
| 227 lib.sources[i].orderInLibrary = i; | |
| 228 } | |
| 229 | |
| 230 writer.comment('// ********** Library ${lib.name} **************'); | |
| 231 if (lib.isCore) { | |
| 232 // Generates the JS natives for dart:core. | |
| 233 writer.comment('// ********** Natives dart:core **************'); | |
| 234 corejs.generate(writer); | |
| 235 } | |
| 236 for (var file in lib.natives) { | |
| 237 var filename = basename(file.filename); | |
| 238 writer.comment('// ********** Natives $filename **************'); | |
| 239 writer.writeln(file.text); | |
| 240 } | |
| 241 lib.topType.markUsed(); // TODO(jimhug): EGREGIOUS HACK | |
| 242 | |
| 243 var orderedTypes = _orderValues(lib.types); | |
| 244 | |
| 245 for (var type in orderedTypes) { | |
| 246 if (type.isUsed && type.isClass) { | |
| 247 writeType(type); | |
| 248 // TODO(jimhug): Performance is terrible if we use current | |
| 249 // reified generics approach for reified generic Arrays. | |
| 250 if (type.isGeneric && type !== world.listFactoryType) { | |
| 251 for (var ct in _orderValues(type._concreteTypes)) { | |
| 252 if (ct.isUsed) writeType(ct); | |
| 253 } | |
| 254 } | |
| 255 } else if (type.isFunction && type.varStubs.length > 0) { | |
| 256 // Emit stubs on "Function" if needed | |
| 257 writer.comment('// ********** Code for ${type.jsname} **************'); | |
| 258 _writeDynamicStubs(type); | |
| 259 } | |
| 260 // Type check functions for builtin JS types | |
| 261 if (type.typeCheckCode != null) { | |
| 262 writer.writeln(type.typeCheckCode); | |
| 263 } | |
| 264 } | |
| 265 } | |
| 266 | |
| 267 genMethod(MethodMember meth) { | |
| 268 meth.methodData.run(meth); | |
| 269 } | |
| 270 | |
| 271 String _prototypeOf(Type type, String name) { | |
| 272 if (type.isSingletonNative) { | |
| 273 // e.g. window.console.log$1 | |
| 274 return '${type.jsname}.$name'; | |
| 275 } else if (type.isHiddenNativeType) { | |
| 276 corejs.ensureDynamicProto(); | |
| 277 _usedDynamicDispatchOnType(type); | |
| 278 return '\$dynamic("$name").${type.definition.nativeType.name}'; | |
| 279 } else { | |
| 280 return '${type.jsname}.prototype.$name'; | |
| 281 } | |
| 282 } | |
| 283 | |
| 284 /** | |
| 285 * Make sure the methods that we add to Array and Object are | |
| 286 * non-enumerable, so that we don't mess up any other third-party JS | |
| 287 * libraries we might be using. | |
| 288 * We return the necessary suffix (if any) we need to complete the patching. | |
| 289 */ | |
| 290 String _writePrototypePatch(Type type, String name, String functionBody, | |
| 291 CodeWriter writer, [bool isOneLiner=true]) { | |
| 292 var writeFunction = writer.writeln; | |
| 293 String ending = ';'; | |
| 294 if (!isOneLiner) { | |
| 295 writeFunction = writer.enterBlock; | |
| 296 ending = ''; | |
| 297 } | |
| 298 if (type.isObject) { | |
| 299 world.counters.objectProtoMembers++; | |
| 300 } | |
| 301 if (type.isObject || type.genericType == world.listFactoryType) { | |
| 302 // We special case these two so that by default we can use "= function()" | |
| 303 // syntax for better readability of the others. | |
| 304 if (isOneLiner) { | |
| 305 ending = ')$ending'; | |
| 306 } | |
| 307 corejs.ensureDefProp(); | |
| 308 writeFunction( | |
| 309 '\$defProp(${type.jsname}.prototype, "$name", $functionBody$ending'); | |
| 310 if (isOneLiner) return '}'; | |
| 311 return '});'; | |
| 312 } else { | |
| 313 writeFunction('${_prototypeOf(type, name)} = ${functionBody}${ending}'); | |
| 314 return isOneLiner? '': '}'; | |
| 315 } | |
| 316 } | |
| 317 | |
| 318 _maybeIsTest(Type onType, Type checkType) { | |
| 319 bool isSubtype = onType.isSubtypeOf(checkType); | |
| 320 | |
| 321 var onTypeMap = typeEmittedTests[onType]; | |
| 322 if (onTypeMap == null) typeEmittedTests[onType] = onTypeMap = {}; | |
| 323 | |
| 324 Type protoParent = onType.genericType == onType | |
| 325 ? onType.parent | |
| 326 : onType.genericType; | |
| 327 | |
| 328 needToOverride(checkName) { | |
| 329 if (protoParent != null) { | |
| 330 var map = typeEmittedTests[protoParent]; | |
| 331 if (map != null) { | |
| 332 bool protoParentIsSubtype = map[checkName]; | |
| 333 if (protoParentIsSubtype != null && | |
| 334 protoParentIsSubtype == isSubtype) { | |
| 335 return false; | |
| 336 } | |
| 337 } | |
| 338 } | |
| 339 return true; | |
| 340 } | |
| 341 | |
| 342 if (checkType.isTested) { | |
| 343 String checkName = 'is\$${checkType.jsname}'; | |
| 344 onTypeMap[checkName] = isSubtype; | |
| 345 if (needToOverride(checkName)) { | |
| 346 // TODO(jmesserly): cache these functions? they just return true or | |
| 347 // false. | |
| 348 _writePrototypePatch(onType, checkName, | |
| 349 'function(){return $isSubtype}', writer); | |
| 350 } | |
| 351 } | |
| 352 | |
| 353 if (checkType.isChecked) { | |
| 354 String checkName = 'assert\$${checkType.jsname}'; | |
| 355 onTypeMap[checkName] = isSubtype; | |
| 356 if (needToOverride(checkName)) { | |
| 357 String body = 'return this'; | |
| 358 if (!isSubtype) { | |
| 359 // Get the code to throw a TypeError. | |
| 360 // TODO(jmesserly): it'd be nice not to duplicate this code, and | |
| 361 // instead be able to refer to the JS function. | |
| 362 body = world.objectType.varStubs[checkName].body; | |
| 363 } else if (onType == world.stringImplType | |
| 364 || onType == world.numImplType) { | |
| 365 body = 'return ${onType.nativeType.name}(this)'; | |
| 366 } | |
| 367 _writePrototypePatch(onType, checkName, 'function(){$body}', writer); | |
| 368 } | |
| 369 } | |
| 370 } | |
| 371 | |
| 372 writeType(Type type) { | |
| 373 if (type.isWritten) return; | |
| 374 | |
| 375 type.isWritten = true; | |
| 376 writeType(type.genericType); | |
| 377 // Ensure parent has been written before the child. Important ordering for | |
| 378 // IE when we're using $inherits, since we don't have __proto__ available. | |
| 379 if (type.parent != null) { | |
| 380 writeType(type.parent); | |
| 381 } | |
| 382 | |
| 383 var typeName = type.jsname != null ? type.jsname : 'top level'; | |
| 384 writer.comment('// ********** Code for ${typeName} **************'); | |
| 385 if (type.isNative && !type.isTop && !type.isConcreteGeneric) { | |
| 386 var nativeName = type.definition.nativeType.name; | |
| 387 if (nativeName == '') { | |
| 388 writer.writeln('function ${type.jsname}() {}'); | |
| 389 } else if (type.jsname != nativeName) { | |
| 390 if (type.isHiddenNativeType) { | |
| 391 if (_hasStaticOrFactoryMethods(type)) { | |
| 392 writer.writeln('var ${type.jsname} = {};'); | |
| 393 } | |
| 394 } else { | |
| 395 writer.writeln('var ${type.jsname} = ${nativeName};'); | |
| 396 } | |
| 397 } | |
| 398 } | |
| 399 | |
| 400 // We need the $inherits call to immediately follow the standard constructor | |
| 401 // declaration. In particular, it needs to be called before factory | |
| 402 // constructors are declared, otherwise $inherits will clear out the | |
| 403 // prototype on IE (which does not have writable __proto__). | |
| 404 if (!type.isTop) { | |
| 405 if (type.genericType !== type) { | |
| 406 corejs.ensureInheritsHelper(); | |
| 407 writer.writeln('\$inherits(${type.jsname}, ${type.genericType.jsname});'
); | |
| 408 } else if (!type.isNative) { | |
| 409 if (type.parent != null && !type.parent.isObject) { | |
| 410 corejs.ensureInheritsHelper(); | |
| 411 writer.writeln('\$inherits(${type.jsname}, ${type.parent.jsname});'); | |
| 412 } | |
| 413 } | |
| 414 } | |
| 415 | |
| 416 if (type.isTop) { | |
| 417 // no preludes for top type | |
| 418 } else if (type.constructors.length == 0) { | |
| 419 if (!type.isNative || type.isConcreteGeneric) { | |
| 420 // TODO(jimhug): More guards to guarantee staticness | |
| 421 writer.writeln('function ${type.jsname}() {}'); | |
| 422 } | |
| 423 } else { | |
| 424 bool wroteStandard = false; | |
| 425 for (var c in type.constructors.getValues()) { | |
| 426 if (c.methodData.writeDefinition(c, writer)) { | |
| 427 if (c.isConstructor && c.constructorName == '') wroteStandard = true; | |
| 428 } | |
| 429 } | |
| 430 | |
| 431 if (!wroteStandard && (!type.isNative || type.genericType !== type)) { | |
| 432 writer.writeln('function ${type.jsname}() {}'); | |
| 433 } | |
| 434 } | |
| 435 | |
| 436 // Concrete types (like List<String>) will have this already defined on | |
| 437 // their prototype from the generic type (like List) | |
| 438 if (!type.isConcreteGeneric) { | |
| 439 _maybeIsTest(type, type); | |
| 440 } | |
| 441 if (type.genericType._concreteTypes != null) { | |
| 442 for (var ct in _orderValues(type.genericType._concreteTypes)) { | |
| 443 _maybeIsTest(type, ct); | |
| 444 } | |
| 445 } | |
| 446 | |
| 447 if (type.interfaces != null) { | |
| 448 final seen = new Set(); | |
| 449 final worklist = []; | |
| 450 worklist.addAll(type.interfaces); | |
| 451 seen.addAll(type.interfaces); | |
| 452 while (!worklist.isEmpty()) { | |
| 453 var interface_ = worklist.removeLast(); | |
| 454 _maybeIsTest(type, interface_.genericType); | |
| 455 if (interface_.genericType._concreteTypes != null) { | |
| 456 for (var ct in _orderValues(interface_.genericType._concreteTypes)) { | |
| 457 _maybeIsTest(type, ct); | |
| 458 } | |
| 459 } | |
| 460 for (var other in interface_.interfaces) { | |
| 461 if (!seen.contains(other)) { | |
| 462 worklist.addLast(other); | |
| 463 seen.add(other); | |
| 464 } | |
| 465 } | |
| 466 } | |
| 467 } | |
| 468 | |
| 469 type.factories.forEach(_writeMethod); | |
| 470 | |
| 471 for (var member in _orderValues(type.members)) { | |
| 472 if (member is FieldMember) { | |
| 473 _writeField(member); | |
| 474 } | |
| 475 | |
| 476 if (member is PropertyMember) { | |
| 477 _writeProperty(member); | |
| 478 } | |
| 479 | |
| 480 if (member.isMethod) { | |
| 481 _writeMethod(member); | |
| 482 } | |
| 483 } | |
| 484 | |
| 485 _writeDynamicStubs(type); | |
| 486 } | |
| 487 | |
| 488 /** | |
| 489 * Returns [:true:] if the hidden native type has any static or factory | |
| 490 * methods. | |
| 491 * | |
| 492 * class Float32Array native '*Float32Array' { | |
| 493 * factory Float32Array(int len) => _construct(len); | |
| 494 * static _construct(len) native 'return createFloat32Array(len);'; | |
| 495 * } | |
| 496 * | |
| 497 * The factory method and static member are generated something like this: | |
| 498 * var lib_Float32Array = {}; | |
| 499 * lib_Float32Array.Float32Array$factory = ... ; | |
| 500 * lib_Float32Array._construct = ... ; | |
| 501 * | |
| 502 * This predicate determines when we need to define lib_Float32Array. | |
| 503 */ | |
| 504 bool _hasStaticOrFactoryMethods(Type type) { | |
| 505 // TODO(jmesserly): better tracking if the methods are actually called. | |
| 506 // For now we assume that if the type is used, the method is used. | |
| 507 return type.members.getValues().some((m) => m.isMethod && m.isStatic) | |
| 508 || !type.factories.isEmpty(); | |
| 509 } | |
| 510 | |
| 511 _writeDynamicStubs(Type type) { | |
| 512 for (var stub in orderValuesByKeys(type.varStubs)) { | |
| 513 if (!stub.isGenerated) stub.generate(writer); | |
| 514 } | |
| 515 } | |
| 516 | |
| 517 _writeStaticField(FieldMember field) { | |
| 518 // Final static fields must be constants which will be folded and inlined. | |
| 519 if (field.isFinal) return; | |
| 520 | |
| 521 var fullname = "${field.declaringType.jsname}.${field.jsname}"; | |
| 522 if (globals.containsKey(fullname)) { | |
| 523 var value = globals[fullname]; | |
| 524 if (field.declaringType.isTop && !field.isNative) { | |
| 525 writer.writeln('\$globals.${field.jsname} = ${value.exp.code};'); | |
| 526 } else { | |
| 527 writer.writeln('\$globals.${field.declaringType.jsname}_${field.jsname}' | |
| 528 + ' = ${value.exp.code};'); | |
| 529 } | |
| 530 } | |
| 531 // No need to write code for a static class field with no initial value. | |
| 532 } | |
| 533 | |
| 534 _writeField(FieldMember field) { | |
| 535 // Generate declarations for static top-level fields with no value. | |
| 536 if (field.declaringType.isTop && !field.isNative && field.value == null) { | |
| 537 writer.writeln('var ${field.jsname};'); | |
| 538 } | |
| 539 | |
| 540 // generate code for instance fields | |
| 541 if (field._provideGetter && | |
| 542 !field.declaringType.isConcreteGeneric) { | |
| 543 _writePrototypePatch(field.declaringType, field.jsnameOfGetter, | |
| 544 'function() { return this.${field.jsname}; }', writer); | |
| 545 } | |
| 546 if (field._provideSetter && | |
| 547 !field.declaringType.isConcreteGeneric) { | |
| 548 _writePrototypePatch(field.declaringType, field.jsnameOfSetter, | |
| 549 'function(value) { return this.${field.jsname} = value; }', writer); | |
| 550 } | |
| 551 | |
| 552 // TODO(jimhug): Currently choose not to initialize fields on objects, but | |
| 553 // instead to rely on uninitialized === null in our generated code. | |
| 554 // Investigate the perf pros and cons of this. | |
| 555 } | |
| 556 | |
| 557 _writeProperty(PropertyMember property) { | |
| 558 if (property.getter != null) _writeMethod(property.getter); | |
| 559 if (property.setter != null) _writeMethod(property.setter); | |
| 560 | |
| 561 // TODO(jmesserly): make sure we don't do this on hidden native types! | |
| 562 if (property.needsFieldSyntax) { | |
| 563 writer.enterBlock('Object.defineProperty(' | |
| 564 '${property.declaringType.jsname}.prototype, "${property.jsname}", {'); | |
| 565 if (property.getter != null) { | |
| 566 writer.write( | |
| 567 'get: ${property.declaringType.jsname}.prototype.${property.getter.jsn
ame}'); | |
| 568 // The shenanigan below is to make IE happy -- IE 9 doesn't like a | |
| 569 // trailing comma on the last element in a list. | |
| 570 writer.writeln(property.setter == null ? '' : ','); | |
| 571 } | |
| 572 if (property.setter != null) { | |
| 573 writer.writeln( | |
| 574 'set: ${property.declaringType.jsname}.prototype.${property.setter.jsn
ame}'); | |
| 575 } | |
| 576 writer.exitBlock('});'); | |
| 577 } | |
| 578 } | |
| 579 | |
| 580 _writeMethod(MethodMember m) { | |
| 581 m.methodData.writeDefinition(m, writer); | |
| 582 | |
| 583 if (m.isNative && m._provideGetter) { | |
| 584 if (MethodGenerator._maybeGenerateBoundGetter(m, writer)) { | |
| 585 world.gen.corejs.ensureBind(); | |
| 586 } | |
| 587 } | |
| 588 } | |
| 589 | |
| 590 writeGlobals() { | |
| 591 if (globals.length > 0) { | |
| 592 writer.comment('// ********** Globals **************'); | |
| 593 var list = globals.getValues(); | |
| 594 list.sort((a, b) => a.compareTo(b)); | |
| 595 | |
| 596 // put all static field initializations in a method | |
| 597 writer.enterBlock('function \$static_init(){'); | |
| 598 for (var global in list) { | |
| 599 if (global.field != null) { | |
| 600 _writeStaticField(global.field); | |
| 601 } | |
| 602 } | |
| 603 writer.exitBlock('}'); | |
| 604 | |
| 605 // Keep const expressions shared across isolates. Note that the frog | |
| 606 // isolate library needs this because we wrote it's bootstrap and | |
| 607 // book-keeping directly in Dart. Specifically, that code uses | |
| 608 // [HashMapImplementation] which internally uses a constant expression. | |
| 609 for (var global in list) { | |
| 610 if (global.field == null) { | |
| 611 writer.writeln('var ${global.name} = ${global.exp.code};'); | |
| 612 } | |
| 613 } | |
| 614 } | |
| 615 | |
| 616 if (!corejs.useIsolates) { | |
| 617 if (hasStatics) { | |
| 618 writer.writeln('var \$globals = {};'); | |
| 619 } | |
| 620 if (globals.length > 0) { | |
| 621 writer.writeln('\$static_init();'); | |
| 622 } | |
| 623 } | |
| 624 } | |
| 625 | |
| 626 _usedDynamicDispatchOnType(Type type) { | |
| 627 if (typesWithDynamicDispatch == null) typesWithDynamicDispatch = new Set(); | |
| 628 typesWithDynamicDispatch.add(type); | |
| 629 } | |
| 630 | |
| 631 writeDynamicDispatchMetadata() { | |
| 632 if (typesWithDynamicDispatch == null) return; | |
| 633 writer.comment('// ${typesWithDynamicDispatch.length} dynamic types.'); | |
| 634 | |
| 635 // Build a pre-order traversal over all the types and their subtypes. | |
| 636 var seen = new Set(); | |
| 637 var types = []; | |
| 638 visit(type) { | |
| 639 if (seen.contains(type)) return; | |
| 640 seen.add(type); | |
| 641 for (final subtype in _orderCollectionValues(type.directSubtypes)) { | |
| 642 visit(subtype); | |
| 643 } | |
| 644 types.add(type); | |
| 645 } | |
| 646 for (final type in _orderCollectionValues(typesWithDynamicDispatch)) { | |
| 647 visit(type); | |
| 648 } | |
| 649 | |
| 650 var dispatchTypes = types.filter( | |
| 651 (type) => !type.directSubtypes.isEmpty() && | |
| 652 typesWithDynamicDispatch.contains(type)); | |
| 653 | |
| 654 writer.comment('// ${types.length} types'); | |
| 655 writer.comment( | |
| 656 '// ${types.filter((t) => !t.directSubtypes.isEmpty()).length} !leaf'); | |
| 657 | |
| 658 // Generate code that builds the map from type tags used in dynamic dispatch | |
| 659 // to the set of type tags of types that extend (TODO: or implement) those | |
| 660 // types. The set is represented as a string of tags joined with '|'. This | |
| 661 // is easily split into an array of tags, or converted into a regexp. | |
| 662 // | |
| 663 // To reduce the size of the sets, subsets are CSE-ed out into variables. | |
| 664 // The sets could be much smaller if we could make assumptions about the | |
| 665 // type tags of other types (which are constructor names or part of the | |
| 666 // result of Object.prototype.toString). For example, if objects that are | |
| 667 // Dart objects could be easily excluded, then we might be able to simplify | |
| 668 // the test, replacing dozens of HTMLxxxElement types with the regexp | |
| 669 // /HTML.*Element/. | |
| 670 | |
| 671 var varNames = []; // temporary variables for common substrings. | |
| 672 var varDefns = {}; // var -> expression | |
| 673 var tagDefns = {}; // tag -> expression (a string or a variable) | |
| 674 | |
| 675 makeExpression(type) { | |
| 676 var expressions = []; // expression fragments for this set of type keys. | |
| 677 var subtags = [type.nativeName]; // TODO: Remove if type is abstract. | |
| 678 walk(type) { | |
| 679 for (final subtype in _orderCollectionValues(type.directSubtypes)) { | |
| 680 var tag = subtype.nativeName; | |
| 681 var existing = tagDefns[tag]; | |
| 682 if (existing == null) { | |
| 683 subtags.add(tag); | |
| 684 walk(subtype); | |
| 685 } else { | |
| 686 if (varDefns.containsKey(existing)) { | |
| 687 expressions.add(existing); | |
| 688 } else { | |
| 689 var varName = 'v${varNames.length}/*${tag}*/'; | |
| 690 varNames.add(varName); | |
| 691 varDefns[varName] = existing; | |
| 692 tagDefns[tag] = varName; | |
| 693 expressions.add(varName); | |
| 694 } | |
| 695 } | |
| 696 } | |
| 697 } | |
| 698 walk(type); | |
| 699 var constantPart = "'${Strings.join(subtags, '|')}'"; | |
| 700 if (constantPart != "''") expressions.add(constantPart); | |
| 701 var expression; | |
| 702 if (expressions.length == 1) { | |
| 703 expression = expressions[0]; | |
| 704 } else { | |
| 705 expression = "[${Strings.join(expressions, ',')}].join('|')"; | |
| 706 } | |
| 707 return expression; | |
| 708 } | |
| 709 | |
| 710 for (final type in dispatchTypes) { | |
| 711 tagDefns[type.nativeName] = makeExpression(type); | |
| 712 } | |
| 713 | |
| 714 // Write out a thunk that builds the metadata. | |
| 715 | |
| 716 if (!tagDefns.isEmpty()) { | |
| 717 corejs.ensureDynamicSetMetadata(); | |
| 718 writer.enterBlock('(function(){'); | |
| 719 | |
| 720 for (final varName in varNames) { | |
| 721 writer.writeln('var ${varName} = ${varDefns[varName]};'); | |
| 722 } | |
| 723 | |
| 724 writer.enterBlock('var table = ['); | |
| 725 writer.comment( | |
| 726 '// [dynamic-dispatch-tag, ' | |
| 727 + 'tags of classes implementing dynamic-dispatch-tag]'); | |
| 728 bool needsComma = false; | |
| 729 for (final type in dispatchTypes) { | |
| 730 if (needsComma) { | |
| 731 writer.write(', '); | |
| 732 } | |
| 733 writer.writeln("['${type.nativeName}', ${tagDefns[type.nativeName]}]"); | |
| 734 needsComma = true; | |
| 735 } | |
| 736 writer.exitBlock('];'); | |
| 737 writer.writeln('\$dynamicSetMetadata(table);'); | |
| 738 | |
| 739 writer.exitBlock('})();'); | |
| 740 } | |
| 741 } | |
| 742 | |
| 743 /** Order a list of values in a Map by SourceSpan, then by name. */ | |
| 744 List _orderValues(Map map) { | |
| 745 // TODO(jmesserly): should we copy the list? | |
| 746 // Right now, the Maps are returning a copy already. | |
| 747 List values = map.getValues(); | |
| 748 values.sort(_compareMembers); | |
| 749 return values; | |
| 750 } | |
| 751 | |
| 752 /** Order a list of values in a Collection by SourceSpan, then by name. */ | |
| 753 List _orderCollectionValues(Collection collection) { | |
| 754 List values = new List.from(collection); | |
| 755 values.sort(_compareMembers); | |
| 756 return values; | |
| 757 } | |
| 758 | |
| 759 int _compareMembers(x, y) { | |
| 760 if (x.span != null && y.span != null) { | |
| 761 // First compare by source span. | |
| 762 int spans = x.span.compareTo(y.span); | |
| 763 if (spans != 0) return spans; | |
| 764 } else { | |
| 765 // With-spans before sans-spans. | |
| 766 if (x.span != null) return -1; | |
| 767 if (y.span != null) return 1; | |
| 768 } | |
| 769 // If that fails, compare by name, null comes first. | |
| 770 if (x.name == y.name) return 0; | |
| 771 if (x.name == null) return -1; | |
| 772 if (y.name == null) return 1; | |
| 773 return x.name.compareTo(y.name); | |
| 774 } | |
| 775 } | |
| 776 | |
| 777 | |
| 778 /** | |
| 779 * A naive code generator for Dart. | |
| 780 */ | |
| 781 class MethodGenerator implements TreeVisitor, CallingContext { | |
| 782 Member method; | |
| 783 CodeWriter writer; | |
| 784 BlockScope _scope; | |
| 785 MethodGenerator enclosingMethod; | |
| 786 bool needsThis; | |
| 787 List<String> _paramCode; | |
| 788 | |
| 789 // TODO(jmesserly): if we knew temps were always used like a stack, we could | |
| 790 // reduce the overhead here. | |
| 791 List<String> _freeTemps; | |
| 792 Set<String> _usedTemps; | |
| 793 | |
| 794 /** | |
| 795 * The set of variables that this lambda closes that need to capture | |
| 796 * with Function.prototype.bind. This is any variable that lives inside a | |
| 797 * reentrant block scope (e.g. loop bodies). | |
| 798 * | |
| 799 * This field is null if we don't need to track this. | |
| 800 */ | |
| 801 Set<String> captures; | |
| 802 | |
| 803 CounterLog counters; | |
| 804 | |
| 805 MethodGenerator(this.method, this.enclosingMethod) | |
| 806 : writer = new CodeWriter(), needsThis = false { | |
| 807 if (enclosingMethod != null) { | |
| 808 _scope = new BlockScope(this, enclosingMethod._scope, method.definition); | |
| 809 captures = new Set(); | |
| 810 } else { | |
| 811 _scope = new BlockScope(this, null, method.definition); | |
| 812 } | |
| 813 _usedTemps = new Set(); | |
| 814 _freeTemps = []; | |
| 815 counters = world.counters; | |
| 816 } | |
| 817 | |
| 818 Library get library() => method.library; | |
| 819 | |
| 820 // TODO(jimhug): Where does this really belong? | |
| 821 MemberSet findMembers(String name) { | |
| 822 return library._findMembers(name); | |
| 823 } | |
| 824 | |
| 825 bool get needsCode() => true; | |
| 826 bool get showWarnings() => false; | |
| 827 | |
| 828 bool get isClosure() => (enclosingMethod != null); | |
| 829 | |
| 830 bool get isStatic() => method.isStatic; | |
| 831 | |
| 832 Value getTemp(Value value) { | |
| 833 return value.needsTemp ? forceTemp(value) : value; | |
| 834 } | |
| 835 | |
| 836 VariableValue forceTemp(Value value) { | |
| 837 String name; | |
| 838 if (_freeTemps.length > 0) { | |
| 839 name = _freeTemps.removeLast(); | |
| 840 } else { | |
| 841 name = '\$${_usedTemps.length}'; | |
| 842 } | |
| 843 _usedTemps.add(name); | |
| 844 return new VariableValue(value.staticType, name, value.span, false, value); | |
| 845 } | |
| 846 | |
| 847 Value assignTemp(Value tmp, Value v) { | |
| 848 if (tmp == v) { | |
| 849 return v; | |
| 850 } else { | |
| 851 // TODO(jmesserly): we should mark this returned value with the temp | |
| 852 // somehow, so getTemp will reuse it instead of allocating a new one. | |
| 853 // (we could do this now if we had a "TempValue" or something like that) | |
| 854 return new Value(v.type, '(${tmp.code} = ${v.code})', v.span); | |
| 855 } | |
| 856 } | |
| 857 | |
| 858 void freeTemp(VariableValue value) { | |
| 859 // TODO(jimhug): Need to do this right - for now we can just skip freeing. | |
| 860 /* | |
| 861 if (_usedTemps.remove(value.code)) { | |
| 862 _freeTemps.add(value.code); | |
| 863 } else { | |
| 864 world.internalError( | |
| 865 'tried to free unused value or non-temp "${value.code}"'); | |
| 866 } | |
| 867 */ | |
| 868 } | |
| 869 | |
| 870 run() { | |
| 871 // Create most generic possible call for this method. | |
| 872 var thisObject; | |
| 873 if (method.isConstructor) { | |
| 874 thisObject = new ObjectValue(false, method.declaringType, method.span); | |
| 875 thisObject.initFields(); | |
| 876 } else { | |
| 877 thisObject = new Value(method.declaringType, 'this', null); | |
| 878 } | |
| 879 var values = []; | |
| 880 for (var p in method.parameters) { | |
| 881 values.add(new Value(p.type, p.name, null)); | |
| 882 } | |
| 883 var args = new Arguments(null, values); | |
| 884 | |
| 885 evalBody(thisObject, args); | |
| 886 } | |
| 887 | |
| 888 | |
| 889 writeDefinition(CodeWriter defWriter, LambdaExpression lambda/*=null*/) { | |
| 890 // To implement block scope: capture any variables we need to. | |
| 891 var paramCode = _paramCode; | |
| 892 var names = null; | |
| 893 if (captures != null && captures.length > 0) { | |
| 894 names = new List.from(captures); | |
| 895 names.sort((x, y) => x.compareTo(y)); | |
| 896 // Prepend these as extra parameters. We'll bind them below. | |
| 897 paramCode = new List.from(names); | |
| 898 paramCode.addAll(_paramCode); | |
| 899 } | |
| 900 | |
| 901 String _params = '(${Strings.join(_paramCode, ", ")})'; | |
| 902 String params = '(${Strings.join(paramCode, ", ")})'; | |
| 903 String suffix = '}'; | |
| 904 // TODO(jmesserly): many of these are similar, it'd be nice to clean up. | |
| 905 if (method.declaringType.isTop && !isClosure) { | |
| 906 defWriter.enterBlock('function ${method.jsname}$params {'); | |
| 907 } else if (isClosure) { | |
| 908 if (method.name == '') { | |
| 909 defWriter.enterBlock('(function $params {'); | |
| 910 } else if (names != null) { | |
| 911 if (lambda == null) { | |
| 912 defWriter.enterBlock('var ${method.jsname} = (function$params {'); | |
| 913 } else { | |
| 914 defWriter.enterBlock('(function ${method.jsname}$params {'); | |
| 915 } | |
| 916 } else { | |
| 917 defWriter.enterBlock('function ${method.jsname}$params {'); | |
| 918 } | |
| 919 } else if (method.isConstructor) { | |
| 920 if (method.constructorName == '') { | |
| 921 defWriter.enterBlock('function ${method.declaringType.jsname}$params {')
; | |
| 922 } else { | |
| 923 defWriter.enterBlock('${method.declaringType.jsname}.${method.constructo
rName}\$ctor = function$params {'); | |
| 924 } | |
| 925 } else if (method.isFactory) { | |
| 926 defWriter.enterBlock('${method.generatedFactoryName} = function$_params {'
); | |
| 927 } else if (method.isStatic) { | |
| 928 defWriter.enterBlock('${method.declaringType.jsname}.${method.jsname} = fu
nction$_params {'); | |
| 929 } else { | |
| 930 suffix = world.gen._writePrototypePatch(method.declaringType, | |
| 931 method.jsname, 'function$_params {', defWriter, false); | |
| 932 } | |
| 933 | |
| 934 if (needsThis) { | |
| 935 defWriter.writeln('var \$this = this;'); | |
| 936 } | |
| 937 | |
| 938 if (_usedTemps.length > 0 || _freeTemps.length > 0) { | |
| 939 //TODO(jimhug): assert(_usedTemps.length == 0); // all temps should be fre
ed. | |
| 940 _freeTemps.addAll(_usedTemps); | |
| 941 _freeTemps.sort((x, y) => x.compareTo(y)); | |
| 942 defWriter.writeln('var ${Strings.join(_freeTemps, ", ")};'); | |
| 943 } | |
| 944 | |
| 945 // TODO(jimhug): Lots of string translation here - perf bottleneck? | |
| 946 defWriter.writeln(writer.text); | |
| 947 | |
| 948 bool usesBind = false; | |
| 949 if (names != null) { | |
| 950 usesBind = true; | |
| 951 defWriter.exitBlock('}).bind(null, ${Strings.join(names, ", ")})'); | |
| 952 } else if (isClosure && method.name == '') { | |
| 953 defWriter.exitBlock('})'); | |
| 954 } else { | |
| 955 defWriter.exitBlock(suffix); | |
| 956 } | |
| 957 if (method.isConstructor && method.constructorName != '') { | |
| 958 defWriter.writeln( | |
| 959 '${method.declaringType.jsname}.${method.constructorName}\$ctor.prototyp
e = ' | |
| 960 '${method.declaringType.jsname}.prototype;'); | |
| 961 } | |
| 962 | |
| 963 _provideOptionalParamInfo(defWriter); | |
| 964 | |
| 965 if (method is MethodMember) { | |
| 966 if (_maybeGenerateBoundGetter(method, defWriter)) { | |
| 967 usesBind = true; | |
| 968 } | |
| 969 } | |
| 970 | |
| 971 if (usesBind) world.gen.corejs.ensureBind(); | |
| 972 } | |
| 973 | |
| 974 static bool _maybeGenerateBoundGetter(MethodMember m, CodeWriter defWriter) { | |
| 975 if (m._provideGetter) { | |
| 976 String suffix = world.gen._writePrototypePatch(m.declaringType, | |
| 977 m.jsnameOfGetter, 'function() {', defWriter, false); | |
| 978 if (m.parameters.some((p) => p.isOptional)) { | |
| 979 defWriter.writeln('var f = this.${m.jsname}.bind(this);'); | |
| 980 defWriter.writeln('f.\$optional = this.${m.jsname}.\$optional;'); | |
| 981 defWriter.writeln('return f;'); | |
| 982 } else { | |
| 983 defWriter.writeln('return this.${m.jsname}.bind(this);'); | |
| 984 } | |
| 985 defWriter.exitBlock(suffix); | |
| 986 return true; | |
| 987 } | |
| 988 return false; | |
| 989 } | |
| 990 | |
| 991 /** | |
| 992 * Generates information about the default/named arguments into the JS code. | |
| 993 * Only methods that are passed as bound methods to "var" need this. It is | |
| 994 * generated to support run time stub creation. | |
| 995 */ | |
| 996 _provideOptionalParamInfo(CodeWriter defWriter) { | |
| 997 if (method is MethodMember) { | |
| 998 MethodMember meth = method; | |
| 999 if (meth._provideOptionalParamInfo) { | |
| 1000 var optNames = []; | |
| 1001 var optValues = []; | |
| 1002 meth.genParameterValues(this); | |
| 1003 for (var param in meth.parameters) { | |
| 1004 if (param.isOptional) { | |
| 1005 optNames.add(param.name); | |
| 1006 // TODO(jimhug): Remove this last usage of escapeString. | |
| 1007 optValues.add(_escapeString(param.value.code)); | |
| 1008 } | |
| 1009 } | |
| 1010 if (optNames.length > 0) { | |
| 1011 // TODO(jmesserly): the logic for how to refer to | |
| 1012 // static/instance/top-level members is duplicated all over the place. | |
| 1013 // Badly needs cleanup. | |
| 1014 var start = ''; | |
| 1015 if (meth.isStatic) { | |
| 1016 if (!meth.declaringType.isTop) { | |
| 1017 start = meth.declaringType.jsname + '.'; | |
| 1018 } | |
| 1019 } else { | |
| 1020 start = meth.declaringType.jsname + '.prototype.'; | |
| 1021 } | |
| 1022 | |
| 1023 optNames.addAll(optValues); | |
| 1024 var optional = "['${Strings.join(optNames, "', '")}']"; | |
| 1025 defWriter.writeln('${start}${meth.jsname}.\$optional = $optional'); | |
| 1026 } | |
| 1027 } | |
| 1028 } | |
| 1029 } | |
| 1030 | |
| 1031 _initField(ObjectValue newObject, String name, Value value, SourceSpan span) { | |
| 1032 var field = method.declaringType.getMember(name); | |
| 1033 if (field == null) { | |
| 1034 world.error('bad initializer - no matching field', span); | |
| 1035 } | |
| 1036 if (!field.isField) { | |
| 1037 world.error('"this.${name}" does not refer to a field', span); | |
| 1038 } | |
| 1039 return newObject.setField(field, value, duringInit: true); | |
| 1040 } | |
| 1041 | |
| 1042 evalBody(Value newObject, Arguments args) { | |
| 1043 bool fieldsSet = false; | |
| 1044 if (method.isNative && method.isConstructor && newObject is ObjectValue) { | |
| 1045 newObject.dynamic.seenNativeInitializer = true; | |
| 1046 } | |
| 1047 // Collects parameters for writing signature in the future. | |
| 1048 _paramCode = []; | |
| 1049 for (int i = 0; i < method.parameters.length; i++) { | |
| 1050 var p = method.parameters[i]; | |
| 1051 Value currentArg = null; | |
| 1052 if (i < args.bareCount) { | |
| 1053 currentArg = args.values[i]; | |
| 1054 } else { | |
| 1055 // Handle named or missing arguments | |
| 1056 currentArg = args.getValue(p.name); | |
| 1057 if (currentArg === null) { | |
| 1058 // Ensure default value for param has been generated | |
| 1059 p.genValue(method, this); | |
| 1060 currentArg = p.value; | |
| 1061 if (currentArg == null) { | |
| 1062 // Not enough arguments, we'll get an error later. | |
| 1063 return; | |
| 1064 } | |
| 1065 } | |
| 1066 } | |
| 1067 | |
| 1068 if (p.isInitializer) { | |
| 1069 _paramCode.add(p.name); | |
| 1070 fieldsSet = true; | |
| 1071 _initField(newObject, p.name, currentArg, p.definition.span); | |
| 1072 } else { | |
| 1073 var paramValue = _scope.declareParameter(p); | |
| 1074 _paramCode.add(paramValue.code); | |
| 1075 if (newObject != null && newObject.isConst) { | |
| 1076 _scope.assign(p.name, currentArg.convertTo(this, p.type)); | |
| 1077 } | |
| 1078 } | |
| 1079 } | |
| 1080 | |
| 1081 var initializerCall = null; | |
| 1082 final declaredInitializers = method.definition.dynamic.initializers; | |
| 1083 if (declaredInitializers != null) { | |
| 1084 for (var init in declaredInitializers) { | |
| 1085 if (init is CallExpression) { | |
| 1086 if (initializerCall != null) { | |
| 1087 world.error('only one initializer redirecting call is allowed', | |
| 1088 init.span); | |
| 1089 } | |
| 1090 initializerCall = init; | |
| 1091 } else if (init is BinaryExpression | |
| 1092 && TokenKind.kindFromAssign(init.op.kind) == 0) { | |
| 1093 var left = init.x; | |
| 1094 if (!(left is DotExpression && left.self is ThisExpression | |
| 1095 || left is VarExpression)) { | |
| 1096 world.error('invalid left side of initializer', left.span); | |
| 1097 continue; | |
| 1098 } | |
| 1099 // TODO(jmesserly): eval right side of initializers in static | |
| 1100 // context, so "this." is not in scope | |
| 1101 var initValue = visitValue(init.y); | |
| 1102 fieldsSet = true; | |
| 1103 _initField(newObject, left.name.name, initValue, left.span); | |
| 1104 } else { | |
| 1105 world.error('invalid initializer', init.span); | |
| 1106 } | |
| 1107 } | |
| 1108 } | |
| 1109 | |
| 1110 if (method.isConstructor && initializerCall == null && !method.isNative) { | |
| 1111 var parentType = method.declaringType.parent; | |
| 1112 if (parentType != null && !parentType.isObject) { | |
| 1113 // TODO(jmesserly): we could omit this if all supertypes are using | |
| 1114 // default constructors. | |
| 1115 initializerCall = new CallExpression( | |
| 1116 new SuperExpression(method.span), [], method.span); | |
| 1117 } | |
| 1118 } | |
| 1119 | |
| 1120 if (method.isConstructor && newObject is ObjectValue) { | |
| 1121 var fields = newObject.dynamic.fields; | |
| 1122 for (var field in newObject.dynamic.fieldsInInitOrder) { | |
| 1123 if (field !== null) { | |
| 1124 var value = fields[field]; | |
| 1125 if (value !== null) { | |
| 1126 writer.writeln('this.${field.jsname} = ${value.code};'); | |
| 1127 } | |
| 1128 } | |
| 1129 } | |
| 1130 } | |
| 1131 | |
| 1132 // TODO(jimhug): Doing this call last does not match spec. | |
| 1133 if (initializerCall != null) { | |
| 1134 evalInitializerCall(newObject, initializerCall, fieldsSet); | |
| 1135 } | |
| 1136 | |
| 1137 if (method.isConstructor && newObject !== null && newObject.isConst) { | |
| 1138 newObject.validateInitialized(method.span); | |
| 1139 } else if (method.isConstructor) { | |
| 1140 var fields = newObject.dynamic.fields; | |
| 1141 for (var field in fields.getKeys()) { | |
| 1142 var value = fields[field]; | |
| 1143 if (value === null && field.isFinal && | |
| 1144 field.declaringType == method.declaringType && | |
| 1145 !newObject.dynamic.seenNativeInitializer) { | |
| 1146 world.error('uninitialized final field "${field.name}"', | |
| 1147 field.span, method.span); | |
| 1148 } | |
| 1149 } | |
| 1150 } | |
| 1151 | |
| 1152 var body = method.definition.dynamic.body; | |
| 1153 | |
| 1154 if (body === null) { | |
| 1155 // TODO(jimhug): Move check into resolve on method. | |
| 1156 if (!method.isConstructor && !method.isNative) { | |
| 1157 world.error('unexpected empty body for ${method.name}', | |
| 1158 method.definition.span); | |
| 1159 } | |
| 1160 } else { | |
| 1161 visitStatementsInBlock(body); | |
| 1162 } | |
| 1163 } | |
| 1164 | |
| 1165 evalInitializerCall(ObjectValue newObject, CallExpression node, | |
| 1166 [bool fieldsSet = false]) { | |
| 1167 String contructorName = ''; | |
| 1168 var targetExp = node.target; | |
| 1169 if (targetExp is DotExpression) { | |
| 1170 DotExpression dot = targetExp; | |
| 1171 targetExp = dot.self; | |
| 1172 contructorName = dot.name.name; | |
| 1173 } | |
| 1174 | |
| 1175 Type targetType = null; | |
| 1176 var target = null; | |
| 1177 if (targetExp is SuperExpression) { | |
| 1178 targetType = method.declaringType.parent; | |
| 1179 target = _makeSuperValue(targetExp); | |
| 1180 } else if (targetExp is ThisExpression) { | |
| 1181 targetType = method.declaringType; | |
| 1182 target = _makeThisValue(targetExp); | |
| 1183 if (fieldsSet) { | |
| 1184 world.error('no initialization allowed with redirecting constructor', | |
| 1185 node.span); | |
| 1186 } | |
| 1187 } else { | |
| 1188 world.error('bad call in initializers', node.span); | |
| 1189 } | |
| 1190 | |
| 1191 var m = targetType.getConstructor(contructorName); | |
| 1192 if (m == null) { | |
| 1193 world.error('no matching constructor for ${targetType.name}', node.span); | |
| 1194 } | |
| 1195 | |
| 1196 // TODO(jimhug): Replace with more generic recursion detection | |
| 1197 method.initDelegate = m; | |
| 1198 // check no cycles in in initialization: | |
| 1199 var other = m; | |
| 1200 while (other != null) { | |
| 1201 if (other == method) { | |
| 1202 world.error('initialization cycle', node.span); | |
| 1203 break; | |
| 1204 } | |
| 1205 other = other.initDelegate; | |
| 1206 } | |
| 1207 | |
| 1208 var newArgs = _makeArgs(node.arguments); | |
| 1209 // ???? wacky stuff ???? | |
| 1210 world.gen.genMethod(m); | |
| 1211 | |
| 1212 m._evalConstConstructor(newObject, newArgs); | |
| 1213 | |
| 1214 if (!newObject.isConst) { | |
| 1215 var value = m.invoke(this, node, target, newArgs); | |
| 1216 if (target.type != world.objectType) { | |
| 1217 // No need to actually call Object's empty super constructor. | |
| 1218 writer.writeln('${value.code};'); | |
| 1219 } | |
| 1220 } | |
| 1221 } | |
| 1222 | |
| 1223 _makeArgs(List<ArgumentNode> arguments) { | |
| 1224 var args = []; | |
| 1225 bool seenLabel = false; | |
| 1226 for (var arg in arguments) { | |
| 1227 if (arg.label != null) { | |
| 1228 seenLabel = true; | |
| 1229 } else if (seenLabel) { | |
| 1230 // TODO(jimhug): Move this into parser? | |
| 1231 world.error('bare argument cannot follow named arguments', arg.span); | |
| 1232 } | |
| 1233 args.add(visitValue(arg.value)); | |
| 1234 } | |
| 1235 | |
| 1236 return new Arguments(arguments, args); | |
| 1237 } | |
| 1238 | |
| 1239 /** Invoke a top-level corelib native method. */ | |
| 1240 Value _invokeNative(String name, List<Value> arguments) { | |
| 1241 var args = Arguments.EMPTY; | |
| 1242 if (arguments.length > 0) { | |
| 1243 args = new Arguments(null, arguments); | |
| 1244 } | |
| 1245 | |
| 1246 var method = world.corelib.topType.members[name]; | |
| 1247 return method.invoke(this, method.definition, | |
| 1248 new Value(world.corelib.topType, null, null), args); | |
| 1249 } | |
| 1250 | |
| 1251 /** | |
| 1252 * Escapes a string so it can be inserted into JS code as a double-quoted | |
| 1253 * JS string. | |
| 1254 */ | |
| 1255 static String _escapeString(String text) { | |
| 1256 // TODO(jimhug): Use a regex for performance here. | |
| 1257 return text.replaceAll('\\', '\\\\').replaceAll('"', '\\"').replaceAll( | |
| 1258 '\n', '\\n').replaceAll('\r', '\\r'); | |
| 1259 } | |
| 1260 | |
| 1261 /** Visits [body] without creating a new block for a [BlockStatement]. */ | |
| 1262 bool visitStatementsInBlock(Statement body) { | |
| 1263 if (body is BlockStatement) { | |
| 1264 BlockStatement block = body; | |
| 1265 for (var stmt in block.body) { | |
| 1266 stmt.visit(this); | |
| 1267 } | |
| 1268 } else { | |
| 1269 if (body != null) body.visit(this); | |
| 1270 } | |
| 1271 return false; | |
| 1272 } | |
| 1273 | |
| 1274 _pushBlock(Node node, [bool reentrant = false]) { | |
| 1275 _scope = new BlockScope(this, _scope, node, reentrant); | |
| 1276 } | |
| 1277 | |
| 1278 _popBlock(Node node) { | |
| 1279 if (_scope.node !== node) { | |
| 1280 spanOf(n) => n != null ? n.span : null; | |
| 1281 world.internalError('scope mismatch. Trying to pop "${node}" but found ' | |
| 1282 + ' "${_scope.node}"', spanOf(node), spanOf(_scope.node)); | |
| 1283 } | |
| 1284 _scope = _scope.parent; | |
| 1285 } | |
| 1286 | |
| 1287 /** Visits a loop body and handles fixed point for type inference. */ | |
| 1288 _visitLoop(Node node, void visitBody()) { | |
| 1289 if (_scope.inferTypes) { | |
| 1290 _loopFixedPoint(node, visitBody); | |
| 1291 } else { | |
| 1292 _pushBlock(node, reentrant:true); | |
| 1293 visitBody(); | |
| 1294 _popBlock(node); | |
| 1295 } | |
| 1296 } | |
| 1297 | |
| 1298 // TODO(jmesserly): we're evaluating the body multiple times, how do we | |
| 1299 // prevent duplicate warnings/errors? | |
| 1300 // We either need a way to collect them before printing, or a check that | |
| 1301 // prevents multiple identical errors at the same source location. | |
| 1302 _loopFixedPoint(Node node, void visitBody()) { | |
| 1303 | |
| 1304 // TODO(jmesserly): should we move the writer/counters into the scope? | |
| 1305 // Also should we save the scope on the node, like how we save | |
| 1306 // MethodGenerator? That would reduce the required work for nested loops. | |
| 1307 var savedCounters = counters; | |
| 1308 var savedWriter = writer; | |
| 1309 int tries = 0; | |
| 1310 var startScope = _scope.snapshot(); | |
| 1311 var s = startScope; | |
| 1312 while (true) { | |
| 1313 // Create a nested writer so we can easily discard it. | |
| 1314 // TODO(jmesserly): does this belong on BlockScope? | |
| 1315 writer = new CodeWriter(); | |
| 1316 counters = new CounterLog(); | |
| 1317 | |
| 1318 _pushBlock(node, reentrant:true); | |
| 1319 | |
| 1320 // If we've tried too many times and haven't converged, disable inference | |
| 1321 if (tries++ >= options.maxInferenceIterations) { | |
| 1322 // TODO(jmesserly): needs more information to actually be useful | |
| 1323 _scope.inferTypes = false; | |
| 1324 } | |
| 1325 | |
| 1326 visitBody(); | |
| 1327 _popBlock(node); | |
| 1328 | |
| 1329 if (!_scope.inferTypes || !_scope.unionWith(s)) { | |
| 1330 // We've converged! | |
| 1331 break; | |
| 1332 } | |
| 1333 | |
| 1334 s = _scope.snapshot(); | |
| 1335 } | |
| 1336 | |
| 1337 // We're done! Write the final code. | |
| 1338 savedWriter.write(writer.text); | |
| 1339 writer = savedWriter; | |
| 1340 savedCounters.add(counters); | |
| 1341 counters = savedCounters; | |
| 1342 } | |
| 1343 | |
| 1344 MethodMember _makeLambdaMethod(String name, FunctionDefinition func) { | |
| 1345 var meth = new MethodMember.lambda(name, method.declaringType, func); | |
| 1346 meth.enclosingElement = method; | |
| 1347 meth._methodData = new MethodData(meth, this); | |
| 1348 meth.resolve(); | |
| 1349 return meth; | |
| 1350 } | |
| 1351 | |
| 1352 visitBool(Expression node) { | |
| 1353 // Boolean conversions in if/while/do/for/conditions require non-null bool. | |
| 1354 | |
| 1355 // TODO(jmesserly): why do we have this rule? It seems inconsistent with | |
| 1356 // the rest of the type system, and just causes bogus asserts unless all | |
| 1357 // bools are initialized to false. | |
| 1358 return visitValue(node).convertTo(this, world.nonNullBool); | |
| 1359 } | |
| 1360 | |
| 1361 visitValue(Expression node) { | |
| 1362 if (node == null) return null; | |
| 1363 | |
| 1364 var value = node.visit(this); | |
| 1365 value.checkFirstClass(node.span); | |
| 1366 return value; | |
| 1367 } | |
| 1368 | |
| 1369 /** | |
| 1370 * Visit [node] and ensure statically or with an runtime check that it has the | |
| 1371 * expected type (if specified). | |
| 1372 */ | |
| 1373 visitTypedValue(Expression node, Type expectedType) { | |
| 1374 final val = visitValue(node); | |
| 1375 return expectedType == null ? val : val.convertTo(this, expectedType); | |
| 1376 } | |
| 1377 | |
| 1378 visitVoid(Expression node) { | |
| 1379 // TODO(jmesserly): should we generalize this? | |
| 1380 if (node is PostfixExpression) { | |
| 1381 var value = visitPostfixExpression(node, isVoid: true); | |
| 1382 value.checkFirstClass(node.span); | |
| 1383 return value; | |
| 1384 } else if (node is BinaryExpression) { | |
| 1385 var value = visitBinaryExpression(node, isVoid: true); | |
| 1386 value.checkFirstClass(node.span); | |
| 1387 return value; | |
| 1388 } | |
| 1389 // TODO(jimhug): Some level of warnings for non-void things here? | |
| 1390 return visitValue(node); | |
| 1391 } | |
| 1392 | |
| 1393 // ******************* Statements ******************* | |
| 1394 | |
| 1395 bool visitDietStatement(DietStatement node) { | |
| 1396 var parser = new Parser(node.span.file, startOffset: node.span.start); | |
| 1397 visitStatementsInBlock(parser.block()); | |
| 1398 return false; | |
| 1399 } | |
| 1400 | |
| 1401 bool visitVariableDefinition(VariableDefinition node) { | |
| 1402 var isFinal = false; | |
| 1403 // TODO(jimhug): Clean this up and share modifier parsing somewhere. | |
| 1404 if (node.modifiers != null && node.modifiers[0].kind == TokenKind.FINAL) { | |
| 1405 isFinal = true; | |
| 1406 } | |
| 1407 writer.write('var '); | |
| 1408 var type = method.resolveType(node.type, false, true); | |
| 1409 for (int i=0; i < node.names.length; i++) { | |
| 1410 if (i > 0) { | |
| 1411 writer.write(', '); | |
| 1412 } | |
| 1413 final name = node.names[i].name; | |
| 1414 var value = visitValue(node.values[i]); | |
| 1415 if (isFinal && value == null) { | |
| 1416 world.error('no value specified for final variable', node.span); | |
| 1417 } | |
| 1418 | |
| 1419 var val = _scope.create(name, type, node.names[i].span, isFinal); | |
| 1420 | |
| 1421 if (value == null) { | |
| 1422 if (_scope.reentrant) { | |
| 1423 // To preserve block scoping, we need to ensure the variable is | |
| 1424 // reinitialized each time the block is entered. | |
| 1425 writer.write('${val.code} = null'); | |
| 1426 } else { | |
| 1427 writer.write('${val.code}'); | |
| 1428 } | |
| 1429 } else { | |
| 1430 value = value.convertTo(this, type); | |
| 1431 _scope.inferAssign(name, value); | |
| 1432 writer.write('${val.code} = ${value.code}'); | |
| 1433 } | |
| 1434 } | |
| 1435 writer.writeln(';'); | |
| 1436 return false; | |
| 1437 | |
| 1438 } | |
| 1439 | |
| 1440 bool visitFunctionDefinition(FunctionDefinition node) { | |
| 1441 var meth = _makeLambdaMethod(node.name.name, node); | |
| 1442 var funcValue = _scope.create(meth.name, meth.functionType, | |
| 1443 method.definition.span, isFinal:true); | |
| 1444 | |
| 1445 meth.methodData.createFunction(writer); | |
| 1446 return false; | |
| 1447 } | |
| 1448 | |
| 1449 /** | |
| 1450 * Returns true indicating that normal control-flow is interrupted by | |
| 1451 * this statement. (This could be a return, break, throw, or continue.) | |
| 1452 */ | |
| 1453 bool visitReturnStatement(ReturnStatement node) { | |
| 1454 if (node.value == null) { | |
| 1455 // This is essentially "return null". | |
| 1456 // It can't issue a warning because every type is nullable. | |
| 1457 writer.writeln('return;'); | |
| 1458 } else { | |
| 1459 if (method.isConstructor) { | |
| 1460 world.error('return of value not allowed from constructor', node.span); | |
| 1461 } | |
| 1462 var value = visitTypedValue(node.value, method.returnType); | |
| 1463 writer.writeln('return ${value.code};'); | |
| 1464 } | |
| 1465 return true; | |
| 1466 } | |
| 1467 | |
| 1468 bool visitThrowStatement(ThrowStatement node) { | |
| 1469 // Dart allows throwing anything, just like JS | |
| 1470 if (node.value != null) { | |
| 1471 var value = visitValue(node.value); | |
| 1472 // Ensure that we generate a toString() method for things that we throw | |
| 1473 value.invoke(this, 'toString', node, Arguments.EMPTY); | |
| 1474 writer.writeln('\$throw(${value.code});'); | |
| 1475 world.gen.corejs.useThrow = true; | |
| 1476 } else { | |
| 1477 var rethrow = _scope.getRethrow(); | |
| 1478 if (rethrow == null) { | |
| 1479 world.error('rethrow outside of catch', node.span); | |
| 1480 } else { | |
| 1481 // Use a normal throw instead of $throw so we don't capture a new stack | |
| 1482 writer.writeln('throw ${rethrow};'); | |
| 1483 } | |
| 1484 } | |
| 1485 return true; | |
| 1486 } | |
| 1487 | |
| 1488 bool visitAssertStatement(AssertStatement node) { | |
| 1489 // be sure to walk test for static checking even is asserts disabled | |
| 1490 var test = visitValue(node.test); // TODO(jimhug): check bool or callable. | |
| 1491 if (options.enableAsserts) { | |
| 1492 var span = node.test.span; | |
| 1493 | |
| 1494 // TODO(jmesserly): do we need to include path/line/column here? | |
| 1495 // It should be captured in the stack trace. | |
| 1496 var line = span.file.getLine(span.start) + 1; | |
| 1497 var column = span.file.getColumn(line - 1, span.start) + 1; | |
| 1498 | |
| 1499 // TODO(jimhug): Simplify code for creating const values. | |
| 1500 var args = [ | |
| 1501 test, | |
| 1502 Value.fromString(span.text, node.span), | |
| 1503 Value.fromString(span.file.filename, node.span), | |
| 1504 Value.fromInt(line, node.span), | |
| 1505 Value.fromInt(column, node.span) | |
| 1506 ]; | |
| 1507 | |
| 1508 var tp = world.corelib.topType; | |
| 1509 Member f = tp.getMember('_assert'); | |
| 1510 var value = f.invoke(this, node, new TypeValue(tp, null), | |
| 1511 new Arguments(null, args)); | |
| 1512 writer.writeln('${value.code};'); | |
| 1513 } | |
| 1514 return false; | |
| 1515 } | |
| 1516 | |
| 1517 bool visitBreakStatement(BreakStatement node) { | |
| 1518 // TODO(jimhug): Lots of flow error checking here and below. | |
| 1519 if (node.label == null) { | |
| 1520 writer.writeln('break;'); | |
| 1521 } else { | |
| 1522 writer.writeln('break ${node.label.name};'); | |
| 1523 } | |
| 1524 return true; | |
| 1525 } | |
| 1526 | |
| 1527 bool visitContinueStatement(ContinueStatement node) { | |
| 1528 if (node.label == null) { | |
| 1529 writer.writeln('continue;'); | |
| 1530 } else { | |
| 1531 writer.writeln('continue ${node.label.name};'); | |
| 1532 } | |
| 1533 return true; | |
| 1534 } | |
| 1535 | |
| 1536 bool visitIfStatement(IfStatement node) { | |
| 1537 var test = visitBool(node.test); | |
| 1538 writer.write('if (${test.code}) '); | |
| 1539 var exit1 = node.trueBranch.visit(this); | |
| 1540 if (node.falseBranch != null) { | |
| 1541 writer.write('else '); | |
| 1542 if (node.falseBranch.visit(this) && exit1) { | |
| 1543 return true; | |
| 1544 } | |
| 1545 } | |
| 1546 return false; | |
| 1547 } | |
| 1548 | |
| 1549 bool visitWhileStatement(WhileStatement node) { | |
| 1550 var test = visitBool(node.test); | |
| 1551 writer.write('while (${test.code}) '); | |
| 1552 _visitLoop(node, () { | |
| 1553 node.body.visit(this); | |
| 1554 }); | |
| 1555 return false; | |
| 1556 } | |
| 1557 | |
| 1558 bool visitDoStatement(DoStatement node) { | |
| 1559 writer.write('do '); | |
| 1560 _visitLoop(node, () { | |
| 1561 node.body.visit(this); | |
| 1562 }); | |
| 1563 var test = visitBool(node.test); | |
| 1564 writer.writeln('while (${test.code})'); | |
| 1565 return false; | |
| 1566 } | |
| 1567 | |
| 1568 bool visitForStatement(ForStatement node) { | |
| 1569 _pushBlock(node); | |
| 1570 writer.write('for ('); | |
| 1571 if (node.init != null) { | |
| 1572 node.init.visit(this); | |
| 1573 } else { | |
| 1574 writer.write(';'); | |
| 1575 } | |
| 1576 | |
| 1577 _visitLoop(node, () { | |
| 1578 if (node.test != null) { | |
| 1579 var test = visitBool(node.test); | |
| 1580 writer.write(' ${test.code}; '); | |
| 1581 } else { | |
| 1582 writer.write('; '); | |
| 1583 } | |
| 1584 | |
| 1585 bool needsComma = false; | |
| 1586 for (var s in node.step) { | |
| 1587 if (needsComma) writer.write(', '); | |
| 1588 var sv = visitVoid(s); | |
| 1589 writer.write(sv.code); | |
| 1590 needsComma = true; | |
| 1591 } | |
| 1592 writer.write(') '); | |
| 1593 | |
| 1594 _pushBlock(node.body); | |
| 1595 node.body.visit(this); | |
| 1596 _popBlock(node.body); | |
| 1597 }); | |
| 1598 _popBlock(node); | |
| 1599 return false; | |
| 1600 } | |
| 1601 | |
| 1602 bool _isFinal(typeRef) { | |
| 1603 if (typeRef is GenericTypeReference) { | |
| 1604 typeRef = typeRef.baseType; | |
| 1605 } else if (typeRef is SimpleTypeReference) { | |
| 1606 return false; | |
| 1607 } | |
| 1608 return typeRef != null && typeRef.isFinal; | |
| 1609 } | |
| 1610 | |
| 1611 bool visitForInStatement(ForInStatement node) { | |
| 1612 // TODO(jimhug): visitValue and other cleanups here. | |
| 1613 var itemType = method.resolveType(node.item.type, false, true); | |
| 1614 var list = node.list.visit(this); | |
| 1615 _visitLoop(node, () { | |
| 1616 _visitForInBody(node, itemType, list); | |
| 1617 }); | |
| 1618 return false; | |
| 1619 } | |
| 1620 | |
| 1621 void _visitForInBody(ForInStatement node, Type itemType, Value list) { | |
| 1622 // TODO(jimhug): Check that itemType matches list members... | |
| 1623 bool isFinal = node.item.isFinal; | |
| 1624 var itemName = node.item.name.name; | |
| 1625 var item = _scope.create(itemName, itemType, node.item.name.span, isFinal); | |
| 1626 if (list.needsTemp) { | |
| 1627 var listVar = _scope.create('\$list', list.type, null); | |
| 1628 writer.writeln('var ${listVar.code} = ${list.code};'); | |
| 1629 list = listVar; | |
| 1630 } | |
| 1631 | |
| 1632 // Special path for concrete Arrays for readability and perf optimization. | |
| 1633 if (list.type.genericType == world.listFactoryType) { | |
| 1634 var tmpi = _scope.create('\$i', world.numType, null); | |
| 1635 var listLength = list.get_(this, 'length', node.list); | |
| 1636 writer.enterBlock('for (var ${tmpi.code} = 0;' | |
| 1637 '${tmpi.code} < ${listLength.code}; ${tmpi.code}++) {'); | |
| 1638 var value = list.invoke(this, ':index', node.list, | |
| 1639 new Arguments(null, [tmpi])); | |
| 1640 writer.writeln('var ${item.code} = ${value.code};'); | |
| 1641 } else { | |
| 1642 var iterator = list.invoke(this, 'iterator', node.list, Arguments.EMPTY); | |
| 1643 var tmpi = _scope.create('\$i', iterator.type, null); | |
| 1644 | |
| 1645 var hasNext = tmpi.invoke(this, 'hasNext', node.list, Arguments.EMPTY); | |
| 1646 var next = tmpi.invoke(this, 'next', node.list, Arguments.EMPTY); | |
| 1647 | |
| 1648 writer.enterBlock( | |
| 1649 'for (var ${tmpi.code} = ${iterator.code}; ${hasNext.code}; ) {'); | |
| 1650 writer.writeln('var ${item.code} = ${next.code};'); | |
| 1651 } | |
| 1652 | |
| 1653 visitStatementsInBlock(node.body); | |
| 1654 writer.exitBlock('}'); | |
| 1655 } | |
| 1656 | |
| 1657 void _genToDartException(Value ex) { | |
| 1658 var result = _invokeNative("_toDartException", [ex]); | |
| 1659 writer.writeln('${ex.code} = ${result.code};'); | |
| 1660 } | |
| 1661 | |
| 1662 void _genStackTraceOf(Value trace, Value ex) { | |
| 1663 var result = _invokeNative("_stackTraceOf", [ex]); | |
| 1664 writer.writeln('var ${trace.code} = ${result.code};'); | |
| 1665 } | |
| 1666 | |
| 1667 bool visitTryStatement(TryStatement node) { | |
| 1668 writer.enterBlock('try {'); | |
| 1669 _pushBlock(node.body); | |
| 1670 visitStatementsInBlock(node.body); | |
| 1671 _popBlock(node.body); | |
| 1672 | |
| 1673 if (node.catches.length == 1) { | |
| 1674 // Handle a single catch. We can generate simple code here compared to the | |
| 1675 // multiple catch, such as no extra temp or if-else-if chain. | |
| 1676 var catch_ = node.catches[0]; | |
| 1677 _pushBlock(catch_); | |
| 1678 var exType = method.resolveType(catch_.exception.type, false, true); | |
| 1679 var ex = _scope.declare(catch_.exception); | |
| 1680 _scope.rethrow = ex.code; | |
| 1681 writer.nextBlock('} catch (${ex.code}) {'); | |
| 1682 if (catch_.trace != null) { | |
| 1683 var trace = _scope.declare(catch_.trace); | |
| 1684 _genStackTraceOf(trace, ex); | |
| 1685 } | |
| 1686 _genToDartException(ex); | |
| 1687 | |
| 1688 if (!exType.isVarOrObject) { | |
| 1689 var test = ex.instanceOf(this, exType, catch_.exception.span, | |
| 1690 isTrue:false, forceCheck:true); | |
| 1691 writer.writeln('if (${test.code}) throw ${ex.code};'); | |
| 1692 } | |
| 1693 visitStatementsInBlock(node.catches[0].body); | |
| 1694 _popBlock(catch_); | |
| 1695 } else if (node.catches.length > 0) { | |
| 1696 // Handle more than one catch | |
| 1697 _pushBlock(node); | |
| 1698 var ex = _scope.create('\$ex', world.varType, null); | |
| 1699 _scope.rethrow = ex.code; | |
| 1700 writer.nextBlock('} catch (${ex.code}) {'); | |
| 1701 var trace = null; | |
| 1702 if (node.catches.some((c) => c.trace != null)) { | |
| 1703 trace = _scope.create('\$trace', world.varType, null); | |
| 1704 _genStackTraceOf(trace, ex); | |
| 1705 } | |
| 1706 _genToDartException(ex); | |
| 1707 | |
| 1708 // We need a rethrow unless we encounter a "var" or "Object" catch | |
| 1709 bool needsRethrow = true; | |
| 1710 | |
| 1711 for (int i = 0; i < node.catches.length; i++) { | |
| 1712 var catch_ = node.catches[i]; | |
| 1713 | |
| 1714 _pushBlock(catch_); | |
| 1715 var tmpType = method.resolveType(catch_.exception.type, false, true); | |
| 1716 var tmp = _scope.declare(catch_.exception); | |
| 1717 if (!tmpType.isVarOrObject) { | |
| 1718 var test = ex.instanceOf(this, tmpType, catch_.exception.span, | |
| 1719 isTrue:true, forceCheck:true); | |
| 1720 if (i == 0) { | |
| 1721 writer.enterBlock('if (${test.code}) {'); | |
| 1722 } else { | |
| 1723 writer.nextBlock('} else if (${test.code}) {'); | |
| 1724 } | |
| 1725 } else if (i > 0) { | |
| 1726 writer.nextBlock('} else {'); | |
| 1727 } | |
| 1728 | |
| 1729 writer.writeln('var ${tmp.code} = ${ex.code};'); | |
| 1730 if (catch_.trace != null) { | |
| 1731 // TODO(jmesserly): ensure this is the right type | |
| 1732 var tmptrace = _scope.declare(catch_.trace); | |
| 1733 writer.writeln('var ${tmptrace.code} = ${trace.code};'); | |
| 1734 } | |
| 1735 | |
| 1736 visitStatementsInBlock(catch_.body); | |
| 1737 _popBlock(catch_); | |
| 1738 | |
| 1739 if (tmpType.isVarOrObject) { | |
| 1740 // We matched this for sure; no need to keep going | |
| 1741 if (i + 1 < node.catches.length) { | |
| 1742 world.error('Unreachable catch clause', node.catches[i + 1].span); | |
| 1743 } | |
| 1744 if (i > 0) { | |
| 1745 // Close the else block | |
| 1746 writer.exitBlock('}'); | |
| 1747 } | |
| 1748 needsRethrow = false; | |
| 1749 break; | |
| 1750 } | |
| 1751 } | |
| 1752 | |
| 1753 if (needsRethrow) { | |
| 1754 // If we didn't have a "catch (var e)", generate a rethrow | |
| 1755 writer.nextBlock('} else {'); | |
| 1756 writer.writeln('throw ${ex.code};'); | |
| 1757 writer.exitBlock('}'); | |
| 1758 } | |
| 1759 | |
| 1760 _popBlock(node); | |
| 1761 } | |
| 1762 | |
| 1763 if (node.finallyBlock != null) { | |
| 1764 writer.nextBlock('} finally {'); | |
| 1765 _pushBlock(node.finallyBlock); | |
| 1766 visitStatementsInBlock(node.finallyBlock); | |
| 1767 _popBlock(node.finallyBlock); | |
| 1768 } | |
| 1769 | |
| 1770 // Close the try-catch-finally | |
| 1771 writer.exitBlock('}'); | |
| 1772 // TODO(efortuna): This could be more precise by combining all the different | |
| 1773 // paths here. -i.e. if there is a finally block with a return at the end | |
| 1774 // then this can return true, similarly if all blocks have a return at the | |
| 1775 // end then the same holds. | |
| 1776 return false; | |
| 1777 } | |
| 1778 | |
| 1779 bool visitSwitchStatement(SwitchStatement node) { | |
| 1780 var test = visitValue(node.test); | |
| 1781 writer.enterBlock('switch (${test.code}) {'); | |
| 1782 for (var case_ in node.cases) { | |
| 1783 if (case_.label != null) { | |
| 1784 world.error('unimplemented: labeled case statement', case_.span); | |
| 1785 } | |
| 1786 _pushBlock(case_); | |
| 1787 for (int i=0; i < case_.cases.length; i++) { | |
| 1788 var expr = case_.cases[i]; | |
| 1789 if (expr == null) { | |
| 1790 // Default can only be the last case. | |
| 1791 if (i < case_.cases.length - 1) { | |
| 1792 world.error('default clause must be the last case', case_.span); | |
| 1793 } | |
| 1794 writer.writeln('default:'); | |
| 1795 } else { | |
| 1796 var value = visitValue(expr); | |
| 1797 writer.writeln('case ${value.code}:'); | |
| 1798 } | |
| 1799 } | |
| 1800 writer.enterBlock(''); | |
| 1801 bool caseExits = _visitAllStatements(case_.statements, false); | |
| 1802 | |
| 1803 if (case_ != node.cases[node.cases.length - 1] && !caseExits) { | |
| 1804 var span = case_.statements[case_.statements.length - 1].span; | |
| 1805 writer.writeln('\$throw(new FallThroughError());'); | |
| 1806 world.gen.corejs.useThrow = true; | |
| 1807 } | |
| 1808 writer.exitBlock(''); | |
| 1809 _popBlock(case_); | |
| 1810 } | |
| 1811 writer.exitBlock('}'); | |
| 1812 // TODO(efortuna): When we are passing more information back about | |
| 1813 // control flow by returning something other than bool, return true for the | |
| 1814 // cases where every branch of the switch statement ends with a return | |
| 1815 // statement. | |
| 1816 return false; | |
| 1817 } | |
| 1818 | |
| 1819 bool _visitAllStatements(statementList, exits) { | |
| 1820 for (int i = 0; i < statementList.length; i++) { | |
| 1821 var stmt = statementList[i]; | |
| 1822 exits = stmt.visit(this); | |
| 1823 //TODO(efortuna): fix this so you only get one error if you have "return; | |
| 1824 //a; b; c;" | |
| 1825 if (stmt != statementList[statementList.length - 1] && exits) { | |
| 1826 world.warning('unreachable code', statementList[i + 1].span); | |
| 1827 } | |
| 1828 } | |
| 1829 return exits; | |
| 1830 } | |
| 1831 | |
| 1832 bool visitBlockStatement(BlockStatement node) { | |
| 1833 _pushBlock(node); | |
| 1834 writer.enterBlock('{'); | |
| 1835 var exits = _visitAllStatements(node.body, false); | |
| 1836 writer.exitBlock('}'); | |
| 1837 _popBlock(node); | |
| 1838 return exits; | |
| 1839 } | |
| 1840 | |
| 1841 bool visitLabeledStatement(LabeledStatement node) { | |
| 1842 writer.writeln('${node.name.name}:'); | |
| 1843 node.body.visit(this); | |
| 1844 return false; | |
| 1845 } | |
| 1846 | |
| 1847 bool visitExpressionStatement(ExpressionStatement node) { | |
| 1848 if (node.body is VarExpression || node.body is ThisExpression) { | |
| 1849 // TODO(jmesserly): this is a "warning" but not a "type warning", | |
| 1850 // Is that okay? We have a similar issue around unreachable code warnings. | |
| 1851 world.warning('variable used as statement', node.span); | |
| 1852 } | |
| 1853 var value = visitVoid(node.body); | |
| 1854 writer.writeln('${value.code};'); | |
| 1855 return false; | |
| 1856 } | |
| 1857 | |
| 1858 bool visitEmptyStatement(EmptyStatement node) { | |
| 1859 writer.writeln(';'); | |
| 1860 return false; | |
| 1861 } | |
| 1862 | |
| 1863 _checkNonStatic(Node node) { | |
| 1864 if (isStatic) { | |
| 1865 world.warning('not allowed in static method', node.span); | |
| 1866 } | |
| 1867 } | |
| 1868 | |
| 1869 _makeSuperValue(Node node) { | |
| 1870 var parentType = method.declaringType.parent; | |
| 1871 _checkNonStatic(node); | |
| 1872 if (parentType == null) { | |
| 1873 world.error('no super class', node.span); | |
| 1874 } | |
| 1875 return new SuperValue(parentType, node.span); | |
| 1876 } | |
| 1877 | |
| 1878 _getOutermostMethod() { | |
| 1879 var result = this; | |
| 1880 while (result.enclosingMethod != null) { | |
| 1881 result = result.enclosingMethod; | |
| 1882 } | |
| 1883 return result; | |
| 1884 } | |
| 1885 | |
| 1886 | |
| 1887 // TODO(jimhug): Share code better with _makeThisValue. | |
| 1888 String _makeThisCode() { | |
| 1889 if (enclosingMethod != null) { | |
| 1890 _getOutermostMethod().needsThis = true; | |
| 1891 return '\$this'; | |
| 1892 } else { | |
| 1893 return 'this'; | |
| 1894 } | |
| 1895 } | |
| 1896 | |
| 1897 /** | |
| 1898 * Creates a reference to the enclosing type ('this') that can be used within | |
| 1899 * closures. | |
| 1900 */ | |
| 1901 Value _makeThisValue(Node node) { | |
| 1902 if (enclosingMethod != null) { | |
| 1903 var outermostMethod = _getOutermostMethod(); | |
| 1904 outermostMethod._checkNonStatic(node); | |
| 1905 outermostMethod.needsThis = true; | |
| 1906 return new ThisValue(outermostMethod.method.declaringType, '\$this', | |
| 1907 node != null ? node.span : null); | |
| 1908 } else { | |
| 1909 _checkNonStatic(node); | |
| 1910 return new ThisValue(method.declaringType, 'this', | |
| 1911 node != null ? node.span : null); | |
| 1912 } | |
| 1913 } | |
| 1914 | |
| 1915 // ******************* Expressions ******************* | |
| 1916 visitLambdaExpression(LambdaExpression node) { | |
| 1917 var name = (node.func.name != null) ? node.func.name.name : ''; | |
| 1918 | |
| 1919 MethodMember meth = _makeLambdaMethod(name, node.func); | |
| 1920 return meth.methodData.createLambda(node, this); | |
| 1921 } | |
| 1922 | |
| 1923 visitCallExpression(CallExpression node) { | |
| 1924 var target; | |
| 1925 var position = node.target; | |
| 1926 var name = ':call'; | |
| 1927 if (node.target is DotExpression) { | |
| 1928 DotExpression dot = node.target; | |
| 1929 target = dot.self.visit(this); | |
| 1930 name = dot.name.name; | |
| 1931 position = dot.name; | |
| 1932 } else if (node.target is VarExpression) { | |
| 1933 VarExpression varExpr = node.target; | |
| 1934 name = varExpr.name.name; | |
| 1935 // First check in block scopes. | |
| 1936 target = _scope.lookup(name); | |
| 1937 if (target != null) { | |
| 1938 return target.invoke(this, ':call', node, _makeArgs(node.arguments)); | |
| 1939 } | |
| 1940 | |
| 1941 target = _makeThisOrType(varExpr.span); | |
| 1942 return target.invoke(this, name, node, _makeArgs(node.arguments)); | |
| 1943 } else { | |
| 1944 target = node.target.visit(this); | |
| 1945 } | |
| 1946 | |
| 1947 return target.invoke(this, name, position, _makeArgs(node.arguments)); | |
| 1948 } | |
| 1949 | |
| 1950 visitIndexExpression(IndexExpression node) { | |
| 1951 var target = visitValue(node.target); | |
| 1952 var index = visitValue(node.index); | |
| 1953 return target.invoke(this, ':index', node, new Arguments(null, [index])); | |
| 1954 } | |
| 1955 | |
| 1956 bool _expressionNeedsParens(Expression e) { | |
| 1957 return (e is BinaryExpression || e is ConditionalExpression | |
| 1958 || e is PostfixExpression || _isUnaryIncrement(e)); | |
| 1959 } | |
| 1960 | |
| 1961 visitBinaryExpression(BinaryExpression node, [bool isVoid = false]) { | |
| 1962 final kind = node.op.kind; | |
| 1963 // TODO(jimhug): Ensure these have same semantics as JS! | |
| 1964 if (kind == TokenKind.AND || kind == TokenKind.OR) { | |
| 1965 var x = visitTypedValue(node.x, world.nonNullBool); | |
| 1966 var y = visitTypedValue(node.y, world.nonNullBool); | |
| 1967 return x.binop(kind, y, this, node); | |
| 1968 } else if (kind == TokenKind.EQ_STRICT || kind == TokenKind.NE_STRICT) { | |
| 1969 var x = visitValue(node.x); | |
| 1970 var y = visitValue(node.y); | |
| 1971 return x.binop(kind, y, this, node); | |
| 1972 } | |
| 1973 | |
| 1974 final assignKind = TokenKind.kindFromAssign(node.op.kind); | |
| 1975 if (assignKind == -1) { | |
| 1976 final x = visitValue(node.x); | |
| 1977 final y = visitValue(node.y); | |
| 1978 return x.binop(kind, y, this, node); | |
| 1979 } else if ((assignKind != 0) && _expressionNeedsParens(node.y)) { | |
| 1980 return _visitAssign(assignKind, node.x, | |
| 1981 new ParenExpression(node.y, node.y.span), node, | |
| 1982 isVoid ? ReturnKind.IGNORE : ReturnKind.POST); | |
| 1983 } else { | |
| 1984 return _visitAssign(assignKind, node.x, node.y, node, | |
| 1985 isVoid ? ReturnKind.IGNORE : ReturnKind.POST); | |
| 1986 } | |
| 1987 } | |
| 1988 | |
| 1989 /** | |
| 1990 * Visits an assignment expression. | |
| 1991 */ | |
| 1992 _visitAssign(int kind, Expression xn, Expression yn, Node position, | |
| 1993 int returnKind) { | |
| 1994 // TODO(jimhug): The usual battle with making assign impl not look ugly. | |
| 1995 if (xn is VarExpression) { | |
| 1996 return _visitVarAssign(kind, xn, yn, position, returnKind); | |
| 1997 } else if (xn is IndexExpression) { | |
| 1998 return _visitIndexAssign(kind, xn, yn, position, returnKind); | |
| 1999 } else if (xn is DotExpression) { | |
| 2000 return _visitDotAssign(kind, xn, yn, position, returnKind); | |
| 2001 } else { | |
| 2002 world.error('illegal lhs', xn.span); | |
| 2003 } | |
| 2004 } | |
| 2005 | |
| 2006 // TODO(jmesserly): it'd be nice if we didn't have to deal directly with | |
| 2007 // MemberSets here and in visitVarExpression. | |
| 2008 _visitVarAssign(int kind, VarExpression xn, Expression yn, Node position, | |
| 2009 int returnKind) { | |
| 2010 final name = xn.name.name; | |
| 2011 | |
| 2012 // First check in block scopes. | |
| 2013 var x = _scope.lookup(name); | |
| 2014 var y = visitValue(yn); | |
| 2015 | |
| 2016 if (x != null) { | |
| 2017 y = y.convertTo(this, x.staticType); | |
| 2018 // Update the inferred value | |
| 2019 // Note: for now we aren't very flow sensitive, so this is a "union" | |
| 2020 // rather than simply setting it to "y" | |
| 2021 _scope.inferAssign(name, Value.union(x, y)); | |
| 2022 | |
| 2023 // TODO(jimhug): This is "legacy" and should be cleaned ASAP | |
| 2024 if (x.isFinal) { | |
| 2025 world.error('final variable "${x.code}" is not assignable', | |
| 2026 position.span); | |
| 2027 } | |
| 2028 | |
| 2029 // Handle different ReturnKind values here... | |
| 2030 if (kind == 0) { | |
| 2031 return new Value(y.type, '${x.code} = ${y.code}', position.span); | |
| 2032 } else if (x.type.isNum && y.type.isNum && (kind != TokenKind.TRUNCDIV)) { | |
| 2033 // Process everything but ~/ , which has no equivalent JS operator | |
| 2034 // Very localized optimization for numbers! | |
| 2035 if (returnKind == ReturnKind.PRE) { | |
| 2036 world.internalError('should not be here', position.span); | |
| 2037 } | |
| 2038 final op = TokenKind.kindToString(kind); | |
| 2039 return new Value(y.type, '${x.code} $op= ${y.code}', position.span); | |
| 2040 } else { | |
| 2041 var right = x; | |
| 2042 y = right.binop(kind, y, this, position); | |
| 2043 if (returnKind == ReturnKind.PRE) { | |
| 2044 var tmp = forceTemp(x); | |
| 2045 var ret = new Value(x.type, | |
| 2046 '(${tmp.code} = ${x.code}, ${x.code} = ${y.code}, ${tmp.code})', | |
| 2047 position.span); | |
| 2048 freeTemp(tmp); | |
| 2049 return ret; | |
| 2050 } else { | |
| 2051 return new Value(x.type, '${x.code} = ${y.code}', position.span); | |
| 2052 } | |
| 2053 } | |
| 2054 } else { | |
| 2055 x = _makeThisOrType(position.span); | |
| 2056 return x.set_(this, name, position, y, kind: kind, | |
| 2057 returnKind: returnKind); | |
| 2058 } | |
| 2059 } | |
| 2060 | |
| 2061 _visitIndexAssign(int kind, IndexExpression xn, Expression yn, | |
| 2062 Node position, int returnKind) { | |
| 2063 var target = visitValue(xn.target); | |
| 2064 var index = visitValue(xn.index); | |
| 2065 var y = visitValue(yn); | |
| 2066 | |
| 2067 return target.setIndex(this, index, position, y, kind: kind, | |
| 2068 returnKind: returnKind); | |
| 2069 } | |
| 2070 | |
| 2071 _visitDotAssign(int kind, DotExpression xn, Expression yn, Node position, | |
| 2072 int returnKind) { | |
| 2073 // This is not visitValue because types members are assignable. | |
| 2074 var target = xn.self.visit(this); | |
| 2075 var y = visitValue(yn); | |
| 2076 | |
| 2077 return target.set_(this, xn.name.name, xn.name, y, kind: kind, | |
| 2078 returnKind: returnKind); | |
| 2079 } | |
| 2080 | |
| 2081 visitUnaryExpression(UnaryExpression node) { | |
| 2082 var value = visitValue(node.self); | |
| 2083 switch (node.op.kind) { | |
| 2084 case TokenKind.INCR: | |
| 2085 case TokenKind.DECR: | |
| 2086 // TODO(jimhug): Hackish optimization not always correct | |
| 2087 if (value.type.isNum && !value.isFinal && node.self is VarExpression) { | |
| 2088 return new Value(value.type, '${node.op}${value.code}', node.span); | |
| 2089 } else { | |
| 2090 // ++x becomes x += 1 | |
| 2091 // --x becomes x -= 1 | |
| 2092 var kind = (TokenKind.INCR == node.op.kind ? | |
| 2093 TokenKind.ADD : TokenKind.SUB); | |
| 2094 // TODO(jimhug): Shouldn't need a full-expression here. | |
| 2095 var operand = new LiteralExpression(Value.fromInt(1, node.span), | |
| 2096 node.span); | |
| 2097 | |
| 2098 var assignValue = _visitAssign(kind, node.self, operand, node, | |
| 2099 ReturnKind.POST); | |
| 2100 return new Value(assignValue.type, '(${assignValue.code})', | |
| 2101 node.span); | |
| 2102 } | |
| 2103 } | |
| 2104 return value.unop(node.op.kind, this, node); | |
| 2105 } | |
| 2106 | |
| 2107 visitDeclaredIdentifier(DeclaredIdentifier node) { | |
| 2108 world.error('Expected expression', node.span); | |
| 2109 } | |
| 2110 | |
| 2111 visitAwaitExpression(AwaitExpression node) { | |
| 2112 world.internalError( | |
| 2113 'Await expressions should have been eliminated before code generation', | |
| 2114 node.span); | |
| 2115 } | |
| 2116 | |
| 2117 visitPostfixExpression(PostfixExpression node, [bool isVoid = false]) { | |
| 2118 // TODO(jimhug): Hackish optimization here to revisit in many ways... | |
| 2119 var value = visitValue(node.body); | |
| 2120 if (value.type.isNum && !value.isFinal && node.body is VarExpression) { | |
| 2121 // Would like to also do on "pure" fields - check to see if possible... | |
| 2122 return new Value(value.type, '${value.code}${node.op}', node.span); | |
| 2123 } | |
| 2124 | |
| 2125 // x++ is equivalent to (t = x, x = t + 1, t), where we capture all temps | |
| 2126 // needed to evaluate x so we're not evaluating multiple times. Likewise, | |
| 2127 // x-- is equivalent to (t = x, x = t - 1, t). | |
| 2128 var kind = (TokenKind.INCR == node.op.kind) ? | |
| 2129 TokenKind.ADD : TokenKind.SUB; | |
| 2130 // TODO(jimhug): Shouldn't need a full-expression here. | |
| 2131 var operand = new LiteralExpression(Value.fromInt(1, node.span), | |
| 2132 node.span); | |
| 2133 var ret = _visitAssign(kind, node.body, operand, node, | |
| 2134 isVoid ? ReturnKind.IGNORE : ReturnKind.PRE); | |
| 2135 return ret; | |
| 2136 } | |
| 2137 | |
| 2138 visitNewExpression(NewExpression node) { | |
| 2139 var typeRef = node.type; | |
| 2140 | |
| 2141 var constructorName = ''; | |
| 2142 if (node.name != null) { | |
| 2143 constructorName = node.name.name; | |
| 2144 } | |
| 2145 | |
| 2146 // Named constructors and library prefixes, oh my! | |
| 2147 // At last, we can collapse the ambiguous wave function... | |
| 2148 if (constructorName == '' && typeRef is NameTypeReference && | |
| 2149 typeRef.names != null) { | |
| 2150 | |
| 2151 // Pull off the last name from the type, guess it's the constructor name. | |
| 2152 var names = new List.from(typeRef.names); | |
| 2153 constructorName = names.removeLast().name; | |
| 2154 if (names.length == 0) names = null; | |
| 2155 | |
| 2156 typeRef = new NameTypeReference( | |
| 2157 typeRef.isFinal, typeRef.name, names, typeRef.span); | |
| 2158 } | |
| 2159 | |
| 2160 var type = method.resolveType(typeRef, true, true); | |
| 2161 if (type.isTop) { | |
| 2162 type = type.library.findTypeByName(constructorName); | |
| 2163 constructorName = ''; | |
| 2164 } | |
| 2165 | |
| 2166 if (type is ParameterType) { | |
| 2167 world.error('cannot instantiate a type parameter', node.span); | |
| 2168 return _makeMissingValue(constructorName); | |
| 2169 } | |
| 2170 | |
| 2171 var m = type.getConstructor(constructorName); | |
| 2172 if (m == null) { | |
| 2173 var name = type.jsname; | |
| 2174 if (type.isVar) { | |
| 2175 name = typeRef.name.name; | |
| 2176 } | |
| 2177 world.error('no matching constructor for $name', node.span); | |
| 2178 return _makeMissingValue(name); | |
| 2179 } | |
| 2180 | |
| 2181 if (node.isConst) { | |
| 2182 if (!m.isConst) { | |
| 2183 world.error('can\'t use const on a non-const constructor', node.span); | |
| 2184 } | |
| 2185 for (var arg in node.arguments) { | |
| 2186 if (!visitValue(arg.value).isConst) { | |
| 2187 world.error('const constructor expects const arguments', arg.span); | |
| 2188 } | |
| 2189 } | |
| 2190 } | |
| 2191 | |
| 2192 // Call the constructor on the type we want to construct. | |
| 2193 // NOTE: this is important for correct type checking of factories. | |
| 2194 // If the user calls "new Interface()" we want the result type to be the | |
| 2195 // interface, not the class. | |
| 2196 var target = new TypeValue(type, typeRef.span); | |
| 2197 return m.invoke(this, node, target, _makeArgs(node.arguments)); | |
| 2198 } | |
| 2199 | |
| 2200 visitListExpression(ListExpression node) { | |
| 2201 var argValues = []; | |
| 2202 var listType = world.listType; | |
| 2203 var type = world.varType; | |
| 2204 if (node.itemType != null) { | |
| 2205 type = method.resolveType(node.itemType, true, !node.isConst); | |
| 2206 if (node.isConst && (type is ParameterType || type.hasTypeParams)) { | |
| 2207 world.error('type parameter cannot be used in const list literals'); | |
| 2208 } | |
| 2209 listType = listType.getOrMakeConcreteType([type]); | |
| 2210 } | |
| 2211 for (var item in node.values) { | |
| 2212 var arg = visitTypedValue(item, type); | |
| 2213 argValues.add(arg); | |
| 2214 if (node.isConst && !arg.isConst) { | |
| 2215 world.error('const list can only contain const values', arg.span); | |
| 2216 } | |
| 2217 } | |
| 2218 | |
| 2219 world.listFactoryType.markUsed(); | |
| 2220 | |
| 2221 var ret = new ListValue(argValues, node.isConst, listType, node.span); | |
| 2222 if (ret.isConst) return ret.getGlobalValue(); | |
| 2223 return ret; | |
| 2224 } | |
| 2225 | |
| 2226 | |
| 2227 visitMapExpression(MapExpression node) { | |
| 2228 // Special case the empty non-const map. | |
| 2229 if (node.items.length == 0 && !node.isConst) { | |
| 2230 return world.mapType.getConstructor('').invoke(this, node, | |
| 2231 new TypeValue(world.mapType, node.span), Arguments.EMPTY); | |
| 2232 } | |
| 2233 | |
| 2234 var values = <Value>[]; | |
| 2235 var valueType = world.varType, keyType = world.stringType; | |
| 2236 var mapType = world.mapType; // TODO(jimhug): immutable type? | |
| 2237 if (node.valueType !== null) { | |
| 2238 if (node.keyType !== null) { | |
| 2239 keyType = method.resolveType(node.keyType, true, !node.isConst); | |
| 2240 // TODO(jimhug): Would be nice to allow arbitrary keys here (this is | |
| 2241 // currently not allowed by the spec). | |
| 2242 if (!keyType.isString) { | |
| 2243 world.error('the key type of a map literal must be "String"', | |
| 2244 keyType.span); | |
| 2245 } | |
| 2246 if (node.isConst && | |
| 2247 (keyType is ParameterType || keyType.hasTypeParams)) { | |
| 2248 world.error('type parameter cannot be used in const map literals'); | |
| 2249 } | |
| 2250 } | |
| 2251 | |
| 2252 valueType = method.resolveType(node.valueType, true, !node.isConst); | |
| 2253 if (node.isConst && | |
| 2254 (valueType is ParameterType || valueType.hasTypeParams)) { | |
| 2255 world.error('type parameter cannot be used in const map literals'); | |
| 2256 } | |
| 2257 | |
| 2258 mapType = mapType.getOrMakeConcreteType([keyType, valueType]); | |
| 2259 } | |
| 2260 | |
| 2261 for (int i = 0; i < node.items.length; i += 2) { | |
| 2262 var key = visitTypedValue(node.items[i], keyType); | |
| 2263 if (node.isConst && !key.isConst) { | |
| 2264 world.error('const map can only contain const keys', key.span); | |
| 2265 } | |
| 2266 values.add(key); | |
| 2267 | |
| 2268 var value = visitTypedValue(node.items[i + 1], valueType); | |
| 2269 if (node.isConst && !value.isConst) { | |
| 2270 world.error('const map can only contain const values', value.span); | |
| 2271 } | |
| 2272 values.add(value); | |
| 2273 } | |
| 2274 | |
| 2275 var ret = new MapValue(values, node.isConst, mapType, node.span); | |
| 2276 if (ret.isConst) return ret.getGlobalValue(); | |
| 2277 return ret; | |
| 2278 } | |
| 2279 | |
| 2280 visitConditionalExpression(ConditionalExpression node) { | |
| 2281 var test = visitBool(node.test); | |
| 2282 var trueBranch = visitValue(node.trueBranch); | |
| 2283 var falseBranch = visitValue(node.falseBranch); | |
| 2284 | |
| 2285 // TODO(jmesserly): is there a way to use Value.union here, even though | |
| 2286 // we need different code? | |
| 2287 return new Value(Type.union(trueBranch.type, falseBranch.type), | |
| 2288 '${test.code} ? ${trueBranch.code} : ${falseBranch.code}', node.span); | |
| 2289 } | |
| 2290 | |
| 2291 visitIsExpression(IsExpression node) { | |
| 2292 var value = visitValue(node.x); | |
| 2293 var type = method.resolveType(node.type, true, true); | |
| 2294 if (type.isVar) { | |
| 2295 return Value.comma(value, Value.fromBool(true, node.span)); | |
| 2296 } | |
| 2297 | |
| 2298 return value.instanceOf(this, type, node.span, node.isTrue); | |
| 2299 } | |
| 2300 | |
| 2301 visitParenExpression(ParenExpression node) { | |
| 2302 var body = visitValue(node.body); | |
| 2303 // Assumption implicit here that const values never need parens... | |
| 2304 if (body.isConst) return body; | |
| 2305 return new Value(body.type, '(${body.code})', node.span); | |
| 2306 } | |
| 2307 | |
| 2308 visitDotExpression(DotExpression node) { | |
| 2309 // Types are legal targets of . | |
| 2310 var target = node.self.visit(this); | |
| 2311 return target.get_(this, node.name.name, node.name); | |
| 2312 } | |
| 2313 | |
| 2314 visitVarExpression(VarExpression node) { | |
| 2315 final name = node.name.name; | |
| 2316 | |
| 2317 // First check in block scopes. | |
| 2318 var ret = _scope.lookup(name); | |
| 2319 if (ret != null) return ret; | |
| 2320 | |
| 2321 return _makeThisOrType(node.span).get_(this, name, node); | |
| 2322 } | |
| 2323 | |
| 2324 _makeMissingValue(String name) { | |
| 2325 // TODO(jimhug): Probably goes away to be fully replaced by noSuchMethod | |
| 2326 return new Value(world.varType, '$name()/*NotFound*/', null); | |
| 2327 } | |
| 2328 | |
| 2329 _makeThisOrType(SourceSpan span) { | |
| 2330 return new BareValue(this, _getOutermostMethod(), span); | |
| 2331 } | |
| 2332 | |
| 2333 visitThisExpression(ThisExpression node) { | |
| 2334 return _makeThisValue(node); | |
| 2335 } | |
| 2336 | |
| 2337 visitSuperExpression(SuperExpression node) { | |
| 2338 return _makeSuperValue(node); | |
| 2339 } | |
| 2340 | |
| 2341 visitLiteralExpression(LiteralExpression node) { | |
| 2342 return node.value; | |
| 2343 } | |
| 2344 | |
| 2345 _isUnaryIncrement(Expression item) { | |
| 2346 if (item is UnaryExpression) { | |
| 2347 UnaryExpression u = item; | |
| 2348 return u.op.kind == TokenKind.INCR || u.op.kind == TokenKind.DECR; | |
| 2349 } else { | |
| 2350 return false; | |
| 2351 } | |
| 2352 } | |
| 2353 | |
| 2354 String foldStrings(List<StringValue> strings) { | |
| 2355 StringBuffer buffer = new StringBuffer(); | |
| 2356 for (var part in strings) buffer.add(part.constValue.actualValue); | |
| 2357 return buffer.toString(); | |
| 2358 } | |
| 2359 | |
| 2360 visitStringConcatExpression(StringConcatExpression node) { | |
| 2361 var items = []; | |
| 2362 var itemsConst = []; | |
| 2363 for (var item in node.strings) { | |
| 2364 Value val = visitValue(item); | |
| 2365 assert(val.type.isString); | |
| 2366 if (val.isConst) itemsConst.add(val); | |
| 2367 items.add(val.code); | |
| 2368 } | |
| 2369 if (items.length == itemsConst.length) { | |
| 2370 return new StringValue(foldStrings(itemsConst), true, node.span); | |
| 2371 } else { | |
| 2372 String code = '(${Strings.join(items, " + ")})'; | |
| 2373 return new Value(world.stringType, code, node.span); | |
| 2374 } | |
| 2375 } | |
| 2376 | |
| 2377 visitStringInterpExpression(StringInterpExpression node) { | |
| 2378 var items = []; | |
| 2379 var itemsConst = []; | |
| 2380 for (var item in node.pieces) { | |
| 2381 var val = visitValue(item); | |
| 2382 bool isConst = val.isConst && val.type.isString; | |
| 2383 if (!isConst) { | |
| 2384 val.invoke(this, 'toString', item, Arguments.EMPTY); | |
| 2385 } | |
| 2386 // TODO(jimhug): Ensure this solves all precedence problems. | |
| 2387 // TODO(jmesserly): We could be smarter about prefix/postfix, but we'd | |
| 2388 // need to know if it will compile to a ++ or to some sort of += form. | |
| 2389 var code = val.code; | |
| 2390 if (_expressionNeedsParens(item)) { | |
| 2391 code = '(${code})'; | |
| 2392 } | |
| 2393 // No need to concat empty strings except the first. | |
| 2394 if (items.length == 0 || (code != "''" && code != '""')) { | |
| 2395 items.add(code); | |
| 2396 if (isConst) itemsConst.add(val); | |
| 2397 } | |
| 2398 } | |
| 2399 if (items.length == itemsConst.length) { | |
| 2400 return new StringValue(foldStrings(itemsConst), true, node.span); | |
| 2401 } else { | |
| 2402 String code = '(${Strings.join(items, " + ")})'; | |
| 2403 return new Value(world.stringType, code, node.span); | |
| 2404 } | |
| 2405 } | |
| 2406 } | |
| 2407 | |
| 2408 | |
| 2409 // TODO(jmesserly): move this into its own file? | |
| 2410 class Arguments { | |
| 2411 static Arguments _empty; | |
| 2412 static Arguments get EMPTY() { | |
| 2413 if (_empty == null) { | |
| 2414 _empty = new Arguments(null, []); | |
| 2415 } | |
| 2416 return _empty; | |
| 2417 } | |
| 2418 | |
| 2419 List<Value> values; | |
| 2420 List<ArgumentNode> nodes; | |
| 2421 int _bareCount; | |
| 2422 | |
| 2423 Arguments(this.nodes, this.values); | |
| 2424 | |
| 2425 /** Constructs a bare list of arguments. */ | |
| 2426 factory Arguments.bare(int arity) { | |
| 2427 var values = []; | |
| 2428 for (int i = 0; i < arity; i++) { | |
| 2429 // TODO(jimhug): Need a firm rule about null SourceSpans are allowed. | |
| 2430 values.add(new VariableValue(world.varType, '\$$i', null)); | |
| 2431 } | |
| 2432 return new Arguments(null, values); | |
| 2433 } | |
| 2434 | |
| 2435 int get nameCount() => length - bareCount; | |
| 2436 bool get hasNames() => bareCount < length; | |
| 2437 | |
| 2438 int get length() => values.length; | |
| 2439 | |
| 2440 String getName(int i) => nodes[i].label.name; | |
| 2441 | |
| 2442 int getIndexOfName(String name) { | |
| 2443 for (int i = bareCount; i < length; i++) { | |
| 2444 if (getName(i) == name) { | |
| 2445 return i; | |
| 2446 } | |
| 2447 } | |
| 2448 return -1; | |
| 2449 } | |
| 2450 | |
| 2451 Value getValue(String name) { | |
| 2452 int i = getIndexOfName(name); | |
| 2453 return i >= 0 ? values[i] : null; | |
| 2454 } | |
| 2455 | |
| 2456 int get bareCount() { | |
| 2457 if (_bareCount == null) { | |
| 2458 _bareCount = length; | |
| 2459 if (nodes != null) { | |
| 2460 for (int i = 0; i < nodes.length; i++) { | |
| 2461 if (nodes[i].label != null) { | |
| 2462 _bareCount = i; | |
| 2463 break; | |
| 2464 } | |
| 2465 } | |
| 2466 } | |
| 2467 } | |
| 2468 return _bareCount; | |
| 2469 } | |
| 2470 | |
| 2471 String getCode() { | |
| 2472 var argsCode = []; | |
| 2473 for (int i = 0; i < length; i++) { | |
| 2474 argsCode.add(values[i].code); | |
| 2475 } | |
| 2476 removeTrailingNulls(argsCode); | |
| 2477 return Strings.join(argsCode, ", "); | |
| 2478 } | |
| 2479 | |
| 2480 List<String> getBareCodes() { | |
| 2481 var result = []; | |
| 2482 for (int i = 0; i < bareCount; i++) { | |
| 2483 result.add(values[i].code); | |
| 2484 } | |
| 2485 return result; | |
| 2486 } | |
| 2487 | |
| 2488 List<String> getNamedCodes() { | |
| 2489 var result = []; | |
| 2490 for (int i = bareCount; i < length; i++) { | |
| 2491 result.add(values[i].code); | |
| 2492 } | |
| 2493 return result; | |
| 2494 } | |
| 2495 | |
| 2496 static removeTrailingNulls(List<Value> argsCode) { | |
| 2497 // We simplify calls with null defaults by relying on JS and our | |
| 2498 // choice to make undefined === null for Dart generated code. This helps | |
| 2499 // and ensures correct defaults values for native calls. | |
| 2500 while (argsCode.length > 0 && argsCode.last() == 'null') { | |
| 2501 argsCode.removeLast(); | |
| 2502 } | |
| 2503 } | |
| 2504 | |
| 2505 /** Gets the named arguments. */ | |
| 2506 List<String> getNames() { | |
| 2507 var names = []; | |
| 2508 for (int i = bareCount; i < length; i++) { | |
| 2509 names.add(getName(i)); | |
| 2510 } | |
| 2511 return names; | |
| 2512 } | |
| 2513 | |
| 2514 /** Gets the argument names used in a call stub; uses $0 $1 for bare args. */ | |
| 2515 Arguments toCallStubArgs() { | |
| 2516 var result = []; | |
| 2517 for (int i = 0; i < bareCount; i++) { | |
| 2518 result.add(new VariableValue(world.varType, '\$$i', null)); | |
| 2519 } | |
| 2520 for (int i = bareCount; i < length; i++) { | |
| 2521 var name = getName(i); | |
| 2522 if (name == null) name = '\$$i'; | |
| 2523 result.add(new VariableValue(world.varType, name, null)); | |
| 2524 } | |
| 2525 return new Arguments(nodes, result); | |
| 2526 } | |
| 2527 | |
| 2528 bool matches(Arguments other) { | |
| 2529 if (length != other.length) return false; | |
| 2530 if (bareCount != other.bareCount) return false; | |
| 2531 | |
| 2532 for (int i = 0; i < bareCount; i++) { | |
| 2533 if (values[i].type != other.values[i].type) return false; | |
| 2534 } | |
| 2535 // TODO(jimhug): Needs to check that named args also match! | |
| 2536 return true; | |
| 2537 } | |
| 2538 | |
| 2539 } | |
| 2540 | |
| 2541 class ReturnKind { | |
| 2542 static final int IGNORE = 1; | |
| 2543 static final int POST = 2; | |
| 2544 static final int PRE = 3; | |
| 2545 } | |
| OLD | NEW |