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

Side by Side Diff: frog/gen.dart

Issue 10548047: Remove frog from the repository. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Move test and update apidoc.gyp. Created 8 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « frog/frogc.dart ('k') | frog/lang.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 }
OLDNEW
« no previous file with comments | « frog/frogc.dart ('k') | frog/lang.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698