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 |