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

Side by Side Diff: lib/dartdoc/frog/value.dart

Issue 10696191: Frog removed from dartdoc. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 8 years, 5 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
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 interface CallingContext {
7 MemberSet findMembers(String name);
8 CounterLog get counters();
9 Library get library();
10 bool get isStatic();
11 MethodMember get method();
12
13 bool get needsCode();
14 bool get showWarnings();
15
16 // Hopefully remove the 5 members below that are only used for code gen.
17 String _makeThisCode();
18
19 Value getTemp(Value value);
20 VariableValue forceTemp(Value value);
21 Value assignTemp(Value tmp, Value v);
22 void freeTemp(VariableValue value);
23 }
24
25 // TODO(jimhug): Value needs better separation into three parts:
26 // 1. Static analysis
27 // 2. Type inferred abstract interpretation analysis
28 // 3. Actual code generation
29 /**
30 * This subtype of value is the one and only version used for static
31 * analysis. It has no code and its type is always the static type.
32 */
33 class PureStaticValue extends Value {
34 bool isConst;
35 bool isType;
36
37 // TODO(jimhug): Can we remove span?
38 PureStaticValue(Type type, SourceSpan span,
39 [this.isConst = false, this.isType = false]):
40 super(type, null, span);
41
42 Member getMem(CallingContext context, String name, Node node) {
43 var member = type.getMember(name);
44
45 if (member == null) {
46 world.warning('cannot find "$name" on "${type.name}"', node.span);
47 return null;
48 }
49
50 if (isType && !member.isStatic) {
51 world.error('cannot refer to instance member as static', node.span);
52 }
53
54 return member;
55 }
56
57 Value get_(CallingContext context, String name, Node node) {
58 if (type.isVar) return new PureStaticValue(world.varType, node.span);
59 var member = getMem(context, name, node);
60 if (member == null) return new PureStaticValue(world.varType, node.span);
61
62 return member._get(context, node, this);
63 }
64
65 Value set_(CallingContext context, String name, Node node, Value value,
66 [int kind=0, int returnKind=ReturnKind.IGNORE]) {
67 if (type.isVar) return new PureStaticValue(world.varType, node.span);
68
69 var member = getMem(context, name, node);
70 if (member != null) {
71 member._set(context, node, this, value);
72 }
73 return new PureStaticValue(value.type, node.span);
74 }
75
76 Value setIndex(CallingContext context, Value index, Node node, Value value,
77 [int kind=0, int returnKind=ReturnKind.IGNORE]) {
78 var tmp = invoke(context, ':setindex', node,
79 new Arguments(null, [index, value]));
80 return new PureStaticValue(value.type, node.span);
81 }
82
83 Value unop(int kind, CallingContext context, var node) {
84 switch (kind) {
85 case TokenKind.NOT:
86 // TODO(jimhug): Issue #359 seeks to clarify this behavior.
87 // ?var newVal = convertTo(context, world.nonNullBool);
88 return new PureStaticValue(world.boolType, node.span);
89 case TokenKind.ADD:
90 if (!isConst && !type.isNum) {
91 world.error('no unary add operator in dart', node.span);
92 }
93 return new PureStaticValue(world.numType, node.span);
94 case TokenKind.SUB:
95 return invoke(context, ':negate', node, Arguments.EMPTY);
96 case TokenKind.BIT_NOT:
97 return invoke(context, ':bit_not', node, Arguments.EMPTY);
98 }
99 world.internalError('unimplemented: ${node.op}', node.span);
100 }
101
102 Value binop(int kind, Value other, CallingContext context, var node) {
103 var isConst = isConst && other.isConst;
104
105
106 switch (kind) {
107 case TokenKind.AND:
108 case TokenKind.OR:
109 return new PureStaticValue(world.boolType, node.span, isConst);
110 case TokenKind.EQ_STRICT:
111 return new PureStaticValue(world.boolType, node.span, isConst);
112 case TokenKind.NE_STRICT:
113 return new PureStaticValue(world.boolType, node.span, isConst);
114 }
115
116 var name = kind == TokenKind.NE ? ':ne': TokenKind.binaryMethodName(kind);
117 var ret = invoke(context, name, node, new Arguments(null, [other]));
118 if (isConst) {
119 ret = new PureStaticValue(ret.type, node.span, isConst);
120 }
121 return ret;
122 }
123
124
125 Value invoke(CallingContext context, String name, Node node,
126 Arguments args) {
127 if (type.isVar) return new PureStaticValue(world.varType, node.span);
128 if (type.isFunction && name == ':call') {
129 return new PureStaticValue(world.varType, node.span);
130 }
131
132 var member = getMem(context, name, node);
133 if (member == null) return new PureStaticValue(world.varType, node.span);
134
135 return member.invoke(context, node, this, args);
136 }
137
138 Value invokeNoSuchMethod(CallingContext context, String name, Node node,
139 [Arguments args]) {
140 if (isType) {
141 world.error('member lookup failed for "$name"', node.span);
142 }
143
144 var member = getMem(context, 'noSuchMethod', node);
145 if (member == null) return new PureStaticValue(world.varType, node.span);
146
147 final noSuchArgs = new Arguments(null, [
148 new PureStaticValue(world.stringType, node.span),
149 new PureStaticValue(world.listType, node.span)]);
150
151 return member.invoke(context, node, this, noSuchArgs);
152 }
153
154 // These are implementation details of convertTo. (Eventually we might find it
155 // easier to just implement convertTo itself).
156
157 Value _typeAssert(CallingContext context, Type toType) {
158 return _changeStaticType(toType);
159 }
160
161 Value _changeStaticType(Type toType) {
162 if (toType === type) return this;
163 return new PureStaticValue(toType, span, isConst, isType);
164 }
165 }
166
167
168 /**
169 * Represents a meta-value for code generation.
170 */
171 class Value {
172 /** The inferred (i.e. most precise) [Type] of the [Value]. */
173 final Type type;
174
175 /** The javascript code to generate this value. */
176 final String code;
177
178 /** The source location that created this value for error messages. */
179 final SourceSpan span;
180
181 Value(this.type, this.code, this.span) {
182 if (type == null) world.internalError('type passed as null', span);
183 }
184
185
186 /** Is this a pretend first-class type? */
187 bool get isType() => false;
188
189 /** Is this a reference to super? */
190 bool get isSuper() => false;
191
192 /** Is this value a constant expression? */
193 bool get isConst() => false;
194
195 /** Is this a final variable? */
196 bool get isFinal() => false;
197
198 /** If we reference this value multiple times, do we need a temp? */
199 bool get needsTemp() => true;
200
201 /**
202 * The statically declared [Type] of the [Value]. This type determines which
203 * kind of static type warnings are issued. It's also the type that is used
204 * for generating type assertions (i.e. given `Foo x; ...; x = expr;`,
205 * expr will be checked against "Foo" regardless of the inferred type of `x`).
206 */
207 Type get staticType() => type;
208
209 /** If [isConst], the [EvaluatedValue] that defines this value. */
210 EvaluatedValue get constValue() => null;
211
212 static Value comma(Value x, Value y) {
213 return new Value(y.type, '(${x.code}, ${y.code})', null);
214 }
215
216 // TODO(jmesserly): more work is needed to make unifying all kinds of Values
217 // work properly.
218 static Value union(Value x, Value y) {
219 if (y === null || x == y) return x;
220 if (x === null) return y;
221
222 var ret = x._tryUnion(y);
223 if (ret != null) return ret;
224
225 // TODO(jmesserly): might want to call a _tryUnionReversed here.
226 ret = y._tryUnion(x);
227 if (ret != null) return ret;
228
229 // TODO(jmesserly): should use something like UnionValue and track the
230 // precise set of types. For now we find the Type.union.
231
232 // TODO(jmesserly): What to do about code? Right now, we're intentionally
233 // throwing it away because they aren't used in the current flow-insensitive
234 // inference.
235 return new Value(Type.union(x.type, y.type), null, null);
236 }
237
238 Value _tryUnion(Value right) => null;
239
240 // TODO(jimhug): remove once type system works better.
241 setField(Member field, Value value, [bool duringInit = false]) { }
242
243 // Nothing to do in general?
244 validateInitialized(SourceSpan span) { }
245
246 // TODO(jimhug): Fix these names once get/set are truly pseudo-keywords.
247 // See issue #379.
248 Value get_(CallingContext context, String name, Node node) {
249 final member = _resolveMember(context, name, node);
250 if (member != null) {
251 return member._get(context, node, this);
252 } else {
253 return invokeNoSuchMethod(context, 'get:$name', node);
254 }
255 }
256
257 Value set_(CallingContext context, String name, Node node, Value value,
258 [int kind=0, int returnKind=ReturnKind.IGNORE]) {
259 final member = _resolveMember(context, name, node);
260 if (member != null) {
261 var thisValue = this;
262 var thisTmp = null;
263 var retTmp = null;
264 if (kind != 0) {
265 // TODO(jimhug): Very special number optimizations will go here...
266 thisTmp = context.getTemp(thisValue);
267 thisValue = context.assignTemp(thisTmp, thisValue);
268 var lhs = member._get(context, node, thisTmp);
269 if (returnKind == ReturnKind.PRE) {
270 retTmp = context.forceTemp(lhs);
271 lhs = context.assignTemp(retTmp, lhs);
272 }
273 value = lhs.binop(kind, value, context, node);
274 }
275
276 if (returnKind == ReturnKind.POST) {
277 // TODO(jimhug): Optimize this away when native JS is detected.
278 retTmp = context.forceTemp(value);
279 value = context.assignTemp(retTmp, value);
280 }
281
282 var ret = member._set(context, node, thisValue, value);
283 if (thisTmp != null && thisTmp != this) context.freeTemp(thisTmp);
284 if (retTmp != null) {
285 context.freeTemp(retTmp);
286 return Value.comma(ret, retTmp);
287 } else {
288 return ret;
289 }
290 } else {
291 // TODO(jimhug): Need to support += and noSuchMethod better.
292 return invokeNoSuchMethod(context, 'set:$name', node,
293 new Arguments(null, [value]));
294 }
295 }
296
297 // TODO(jimhug): This method body has too much in common with set_ above.
298 Value setIndex(CallingContext context, Value index, Node node, Value value,
299 [int kind=0, int returnKind=ReturnKind.IGNORE]) {
300 final member = _resolveMember(context, ':setindex', node);
301 if (member != null) {
302 var thisValue = this;
303 var indexValue = index;
304 var thisTmp = null;
305 var indexTmp = null;
306 var retTmp = null;
307 if (returnKind == ReturnKind.POST) {
308 // TODO(jimhug): Optimize this away when native JS works.
309 retTmp = context.forceTemp(value);
310 }
311 if (kind != 0) {
312 // TODO(jimhug): Very special number optimizations will go here...
313 thisTmp = context.getTemp(this);
314 indexTmp = context.getTemp(index);
315 thisValue = context.assignTemp(thisTmp, thisValue);
316 indexValue = context.assignTemp(indexTmp, indexValue);
317
318 if (returnKind == ReturnKind.PRE) {
319 retTmp = context.forceTemp(value);
320 }
321
322 var lhs = thisTmp.invoke(context, ':index', node,
323 new Arguments(null, [indexTmp]));
324 if (returnKind == ReturnKind.PRE) {
325 lhs = context.assignTemp(retTmp, lhs);
326 }
327 value = lhs.binop(kind, value, context, node);
328 }
329 if (returnKind == ReturnKind.POST) {
330 value = context.assignTemp(retTmp, value);
331 }
332
333 var ret = member.invoke(context, node, thisValue,
334 new Arguments(null, [indexValue, value]));
335 if (thisTmp != null && thisTmp != this) context.freeTemp(thisTmp);
336 if (indexTmp != null && indexTmp != index) context.freeTemp(indexTmp);
337 if (retTmp != null) {
338 context.freeTemp(retTmp);
339 return Value.comma(ret, retTmp);
340 } else {
341 return ret;
342 }
343 } else {
344 // TODO(jimhug): Need to support += and noSuchMethod better.
345 return invokeNoSuchMethod(context, ':index', node,
346 new Arguments(null, [index, value]));
347 }
348 }
349
350 //Value getIndex(CallingContext context, Value index, var node) {
351 //}
352
353 Value unop(int kind, CallingContext context, var node) {
354 switch (kind) {
355 case TokenKind.NOT:
356 // TODO(jimhug): Issue #359 seeks to clarify this behavior.
357 var newVal = convertTo(context, world.nonNullBool);
358 return new Value(newVal.type, '!${newVal.code}', node.span);
359 case TokenKind.ADD:
360 world.error('no unary add operator in dart', node.span);
361 break;
362 case TokenKind.SUB:
363 return invoke(context, ':negate', node, Arguments.EMPTY);
364 case TokenKind.BIT_NOT:
365 return invoke(context, ':bit_not', node, Arguments.EMPTY);
366 }
367 world.internalError('unimplemented: ${node.op}', node.span);
368 }
369
370 bool _mayOverrideEqual() {
371 // TODO(jimhug): Need to check subtypes as well
372 return type.isVar || type.isObject ||
373 !type.getMember(':eq').declaringType.isObject;
374 }
375
376 Value binop(int kind, Value other, CallingContext context, var node) {
377 switch (kind) {
378 case TokenKind.AND:
379 case TokenKind.OR:
380 final code = '${code} ${node.op} ${other.code}';
381 return new Value(world.nonNullBool, code, node.span);
382
383 case TokenKind.EQ_STRICT:
384 case TokenKind.NE_STRICT:
385 var op = kind == TokenKind.EQ_STRICT ? '==' : '!=';
386 if (code == 'null') {
387 return new Value(world.nonNullBool,
388 'null ${op} ${other.code}', node.span);
389 } else if (other.code == 'null') {
390 return new Value(world.nonNullBool,
391 'null ${op} ${code}', node.span);
392 } else {
393 // TODO(jimhug): Add check to see if we can just use op on this type
394 // TODO(jimhug): Optimize case of other.needsTemp == false.
395 var ret;
396 var check;
397 if (needsTemp) {
398 var tmp = context.forceTemp(this);
399 ret = tmp.code;
400 check = '(${ret} = ${code}) == null';
401 } else {
402 ret = code;
403 check = 'null == ${code}';
404 }
405 return new Value(world.nonNullBool,
406 '(${check} ? null ${op} (${other.code}) : ${ret} ${op}= ${other.code })',
407 node.span);
408 }
409
410 case TokenKind.EQ:
411 if (other.code == 'null') {
412 if (!_mayOverrideEqual()) {
413 return new Value(world.nonNullBool, '${code} == ${other.code}',
414 node.span);
415 }
416 } else if (code == 'null') {
417 return new Value(world.nonNullBool, '${code} == ${other.code}',
418 node.span);
419 }
420 break;
421 case TokenKind.NE:
422 if (other.code == 'null') {
423 if (!_mayOverrideEqual()) {
424 return new Value(world.nonNullBool, '${code} != ${other.code}',
425 node.span);
426 }
427 } else if (code == 'null') {
428 return new Value(world.nonNullBool, '${code} != ${other.code}',
429 node.span);
430 }
431 break;
432
433 }
434
435 var name = kind == TokenKind.NE ? ':ne': TokenKind.binaryMethodName(kind);
436 return invoke(context, name, node, new Arguments(null, [other]));
437 }
438
439
440 Value invoke(CallingContext context, String name, Node node,
441 Arguments args) {
442 // TODO(jmesserly): it'd be nice to remove these special cases
443 // We could create a :call in world members, and have that handle the
444 // canInvoke/Invoke logic.
445
446 // Note: this check is a little different than the one in canInvoke, because
447 // sometimes we need to call dynamically even if we found the :call method
448 // statically.
449
450 if (name == ':call') {
451 if (isType) {
452 world.error('must use "new" or "const" to construct a new instance',
453 node.span);
454 }
455 if (type.needsVarCall(args)) {
456 return _varCall(context, node, args);
457 }
458 }
459
460 var member = _resolveMember(context, name, node);
461 if (member == null) {
462 return invokeNoSuchMethod(context, name, node, args);
463 } else {
464 return member.invoke(context, node, this, args);
465 }
466 }
467
468 /**
469 * True if this class (or some related class that is not Object) overrides
470 * noSuchMethod. If it does we suppress warnings about unknown members.
471 */
472 // TODO(jmesserly): should we be doing this?
473 bool _hasOverriddenNoSuchMethod() {
474 var m = type.getMember('noSuchMethod');
475 return m != null && !m.declaringType.isObject;
476 }
477
478 // TODO(jimhug): Handle more precise types here, i.e. consts or closed...
479 bool get isPreciseType() => isSuper || isType;
480
481 void _missingMemberError(CallingContext context, String name, Node node) {
482 bool onStaticType = false;
483 if (type != staticType) {
484 onStaticType = staticType.getMember(name) !== null;
485 }
486
487 if (!onStaticType && context.showWarnings &&
488 !_isVarOrParameterType(staticType) && !_hasOverriddenNoSuchMethod()) {
489 // warn if the member was not found, or error if it is a static lookup.
490 var typeName = staticType.name;
491 if (typeName == null) typeName = staticType.library.name;
492 var message = 'cannot resolve "$name" on "${typeName}"';
493 if (isType) {
494 world.error(message, node.span);
495 } else {
496 world.warning(message, node.span);
497 }
498 }
499 }
500
501
502
503 MemberSet _tryResolveMember(CallingContext context, String name, Node node) {
504 var member = type.getMember(name);
505 if (member == null) {
506 _missingMemberError(context, name, node);
507 return null;
508 } else {
509 if (isType && !member.isStatic && context.showWarnings) {
510 world.error('cannot refer to instance member as static', node.span);
511 return null;
512 }
513 }
514
515 if (isPreciseType || member.isStatic) {
516 return member.preciseMemberSet;
517 } else {
518 return member.potentialMemberSet;
519 }
520 }
521
522 // TODO(jmesserly): until reified generics are fixed, treat ParameterType as
523 // "var".
524 bool _isVarOrParameterType(Type t) => t.isVar || t is ParameterType;
525
526 bool _shouldBindDynamically() {
527 return _isVarOrParameterType(type) || options.forceDynamic && !isConst;
528 }
529
530 // TODO(jimhug): Better type here - currently is union(Member, MemberSet)
531 MemberSet _resolveMember(CallingContext context, String name, Node node) {
532 var member = null;
533 if (!_shouldBindDynamically()) {
534 member = _tryResolveMember(context, name, node);
535 }
536
537 // Fall back to a dynamic operation for instance members
538 if (member == null && !isSuper && !isType) {
539 member = context.findMembers(name);
540 if (member == null && context.showWarnings) {
541 var where = 'the world';
542 if (name.startsWith('_')) {
543 where = 'library "${context.library.name}"';
544 }
545 world.warning('$name is not defined anywhere in $where.',
546 node.span);
547 }
548 }
549
550 return member;
551 }
552
553 checkFirstClass(SourceSpan span) {
554 if (isType) {
555 world.error('Types are not first class', span);
556 }
557 }
558
559 /** Generate a call to an unknown function type. */
560 Value _varCall(CallingContext context, Node node, Arguments args) {
561 // TODO(jmesserly): calls to unknown functions will bypass type checks,
562 // which normally happen on the caller side, or in the generated stub for
563 // dynamic method calls. What should we do?
564 var stub = world.functionType.getCallStub(args);
565 return stub.invoke(context, node, this, args);
566 }
567
568 /** True if convertTo would generate a conversion. */
569 bool needsConversion(Type toType) {
570 var c = convertTo(null, toType);
571 return c == null || code != c.code;
572 }
573
574 /**
575 * Assign or convert this value to another type.
576 * This is used for converting between function types, inserting type
577 * checks when --enable_type_checks is enabled, and wrapping callback
578 * functions passed to the dom so we can restore their isolate context.
579 */
580 Value convertTo(CallingContext context, Type toType) {
581
582 // Issue type warnings unless we are processing a dynamic operation.
583 bool checked = context != null && context.showWarnings;
584
585 var callMethod = toType.getCallMethod();
586 if (callMethod != null) {
587 if (checked && !toType.isAssignable(type)) {
588 convertWarning(toType);
589 }
590
591 return _maybeWrapFunction(toType, callMethod);
592 }
593
594 // If we're assigning from a var, pretend it's Object for the purpose of
595 // runtime checks.
596
597 // TODO(jmesserly): I'm a little bothered by the fact that we can't call
598 // isSubtypeOf directly. If we tracked null literals as the bottom type,
599 // and then only allowed Dynamic to be bottom for generic type args, I think
600 // we'd get the right behavior from isSubtypeOf.
601 Type fromType = type;
602 if (type.isVar && (code != 'null' || !toType.isNullable)) {
603 fromType = world.objectType;
604 }
605
606 // TODO(jmesserly): remove the special case for "num" when our num handling
607 // is better.
608 bool bothNum = type.isNum && toType.isNum;
609 if (!fromType.isSubtypeOf(toType) && !bothNum) {
610 // If it is a narrowing conversion, we'll need a check in checked mode.
611
612 if (checked && !toType.isSubtypeOf(type)) {
613 // According to the static types, this conversion can't work.
614 convertWarning(toType);
615 }
616
617 if (options.enableTypeChecks) {
618 if (context == null) {
619 // If we're called from needsConversion, we don't need a context.
620 // Just return null so it knows a conversion is required.
621 return null;
622 }
623 return _typeAssert(context, toType);
624 }
625 }
626
627 return _changeStaticType(toType);
628 }
629
630 // Nothing to do in general.
631 Value _changeStaticType(Type toType) => this;
632
633 /**
634 * Wraps a function with a conversion, so it can be called directly from
635 * Dart or JS code with the proper arity. We avoid the wrapping if the target
636 * function has the same arity.
637 *
638 * Also wraps a callback attached to the dom (e.g. event listeners,
639 * setTimeout) so we can restore it's isolate context information. This is
640 * needed so that callbacks are executed within the context of the isolate
641 * that created them in the first place.
642 */
643 Value _maybeWrapFunction(Type toType, MethodMember callMethod) {
644 int arity = callMethod.parameters.length;
645 var myCall = type.getCallMethod();
646
647 Value result = this;
648 if (myCall == null || myCall.parameters.length != arity) {
649 final stub = world.functionType.getCallStub(new Arguments.bare(arity));
650 result = new Value(toType, 'to\$${stub.name}($code)', span);
651 }
652
653 // TODO(jmesserly): handle when type or toType are type parameters.
654 if (toType.library.isDomOrHtml && !type.library.isDomOrHtml) {
655 // TODO(jmesserly): either remove this or make it a more first class
656 // feature of our native interop. We shouldn't be checking for the DOM
657 // library--any host environment (like node.js) might need this feature
658 // for isolates too. But we don't want to wrap every function we send to
659 // native code--many callbacks like List.filter are perfectly safe.
660 if (arity == 0) {
661 world.gen.corejs.useWrap0 = true;
662 } else if (arity == 1) {
663 world.gen.corejs.useWrap1 = true;
664 } else if (arity == 2) {
665 world.gen.corejs.useWrap2 = true;
666 }
667
668 result = new Value(toType, '\$wrap_call\$$arity(${result.code})', span);
669 }
670
671 return result._changeStaticType(toType);
672 }
673
674 /**
675 * Generates a run time type assertion for the given value. This works like
676 * [instanceOf], but it allows null since Dart types are nullable.
677 * Also it will throw a TypeError if it gets the wrong type.
678 */
679 Value _typeAssert(CallingContext context, Type toType) {
680 if (toType is ParameterType) {
681 ParameterType p = toType;
682 toType = p.extendsType;
683 }
684
685 if (toType.isObject || toType.isVar) {
686 world.internalError(
687 'We thought ${type.name} is not a subtype of ${toType.name}?');
688 }
689
690 // Prevent a stack overflow when forceDynamic and type checks are both
691 // enabled. forceDynamic would cause the TypeError constructor to type check
692 // its arguments, which in turn invokes the TypeError constructor, ad
693 // infinitum.
694 String throwTypeError(String paramName) => world.withoutForceDynamic(() {
695 final typeErrorCtor = world.typeErrorType.getConstructor('_internal');
696 world.gen.corejs.ensureTypeNameOf();
697 final result = typeErrorCtor.invoke(context, null,
698 new TypeValue(world.typeErrorType, null),
699 new Arguments(null, [
700 new Value(world.objectType, paramName, null),
701 new Value(world.stringType, '"${toType.name}"', null)]));
702 world.gen.corejs.useThrow = true;
703 return '\$throw(${result.code})';
704 });
705
706 // TODO(jmesserly): better assert for integers?
707 if (toType.isNum) toType = world.numType;
708
709 // Generate a check like these:
710 // obj && obj.is$TypeName()
711 // $assert_int(obj)
712 //
713 // We rely on the fact that calling an undefined method produces a JS
714 // TypeError. Alternatively we could define fallbacks on Object that throw.
715 String check;
716 if (toType.isVoid) {
717 check = '\$assert_void($code)';
718 if (toType.typeCheckCode == null) {
719 toType.typeCheckCode = '''
720 function \$assert_void(x) {
721 if (x == null) return null;
722 ${throwTypeError("x")}
723 }''';
724 }
725 } else if (toType == world.nonNullBool) {
726 // This could be made less of a special case
727 world.gen.corejs.useNotNullBool = true;
728 check = '\$notnull_bool($code)';
729
730 } else if (toType.library.isCore && toType.typeofName != null) {
731 check = '\$assert_${toType.name}($code)';
732
733 if (toType.typeCheckCode == null) {
734 toType.typeCheckCode = '''
735 function \$assert_${toType.name}(x) {
736 if (x == null || typeof(x) == "${toType.typeofName}") return x;
737 ${throwTypeError("x")}
738 }''';
739 }
740 } else {
741 toType.isChecked = true;
742
743 String checkName = 'assert\$${toType.jsname}';
744
745 // If we track nullability, we could simplify this check.
746 var temp = context.getTemp(this);
747 check = '(${context.assignTemp(temp, this).code} == null ? null :';
748 check += ' ${temp.code}.$checkName())';
749 if (this != temp) context.freeTemp(temp);
750
751 // Generate the fallback on Object (that throws a TypeError)
752 world.objectType.varStubs.putIfAbsent(checkName,
753 () => new VarMethodStub(checkName, null, Arguments.EMPTY,
754 throwTypeError('this')));
755 }
756
757 context.counters.typeAsserts++;
758 return new Value(toType, check, span);
759 }
760
761 /**
762 * Test to see if value is an instance of this type.
763 *
764 * - If a primitive type, then uses the JavaScript typeof.
765 * - If it's a non-generic class, use instanceof.
766 * - Otherwise add a fake member to test for. This value is generated
767 * as a function so that it can be called for a runtime failure.
768 */
769 Value instanceOf(CallingContext context, Type toType, SourceSpan span,
770 [bool isTrue=true, bool forceCheck=false]) {
771 // TODO(jimhug): Optimize away tests that will always pass unless
772 // forceCheck is true.
773
774 if (toType.isVar) {
775 world.error('cannot resolve type', span);
776 }
777
778 String testCode = null;
779 if (toType.isVar || toType.isObject || toType is ParameterType) {
780 // Note: everything is an Object, including null.
781 if (needsTemp) {
782 return new Value(world.nonNullBool, '($code, true)', span);
783 } else {
784 // TODO(jimhug): Mark non-const?
785 return Value.fromBool(true, span);
786 }
787 }
788
789 if (toType.library.isCore) {
790 var typeofName = toType.typeofName;
791 if (typeofName != null) {
792 testCode = "(typeof($code) ${isTrue ? '==' : '!='} '$typeofName')";
793 }
794 }
795
796 if (toType.isClass
797 && !toType.isHiddenNativeType && !toType.isConcreteGeneric) {
798 toType.markUsed();
799 testCode = '($code instanceof ${toType.jsname})';
800 if (!isTrue) {
801 testCode = '!${testCode}';
802 }
803 }
804 if (testCode == null) {
805 toType.isTested = true;
806
807 // If we track nullability, we could simplify this check.
808 var temp = context.getTemp(this);
809
810 String checkName = 'is\$${toType.jsname}';
811 testCode = '(${context.assignTemp(temp, this).code} &&'
812 ' ${temp.code}.$checkName())';
813 if (isTrue) {
814 // Add !! to convert to boolean.
815 // TODO(jimhug): only do this if needed
816 testCode = '!!${testCode}';
817 } else {
818 // The single ! here nicely converts undefined to false and function
819 // to true.
820 testCode = '!${testCode}';
821 }
822 if (this != temp) context.freeTemp(temp);
823
824 // Generate the fallback on Object (that returns false)
825 if (!world.objectType.varStubs.containsKey(checkName)) {
826 world.objectType.varStubs[checkName] =
827 new VarMethodStub(checkName, null, Arguments.EMPTY, 'return false');
828 }
829 }
830 return new Value(world.nonNullBool, testCode, span);
831 }
832
833 void convertWarning(Type toType) {
834 // TODO(jmesserly): better error messages for type conversion failures
835 world.warning(
836 'type "${type.fullname}" is not assignable to "${toType.fullname}"',
837 span);
838 }
839
840 Value invokeNoSuchMethod(CallingContext context, String name, Node node,
841 [Arguments args]) {
842 if (isType) {
843 world.error('member lookup failed for "$name"', node.span);
844 }
845
846 var pos = '';
847 if (args != null) {
848 var argsCode = [];
849 for (int i = 0; i < args.length; i++) {
850 argsCode.add(args.values[i].code);
851 }
852 pos = Strings.join(argsCode, ", "); // don't remove trailing nulls
853 }
854 final noSuchArgs = [
855 new Value(world.stringType, '"$name"', node.span),
856 new Value(world.listType, '[$pos]', node.span)];
857
858 // TODO(jmesserly): should be passing names but that breaks tests. Oh well.
859 /*if (args != null && args.hasNames) {
860 var names = [];
861 for (int i = args.bareCount; i < args.length; i++) {
862 names.add('"${args.getName(i)}", ${args.values[i].code}');
863 }
864 noSuchArgs.add(new Value(world.gen.useMapFactory(),
865 '\$map(${Strings.join(names, ", ")})'));
866 }*/
867
868 // Finally, invoke noSuchMethod
869 return _resolveMember(context, 'noSuchMethod', node).invoke(
870 context, node, this, new Arguments(null, noSuchArgs));
871 }
872
873
874 static Value fromBool(bool value, SourceSpan span) {
875 return new BoolValue(value, true, span);
876 }
877
878 static Value fromInt(int value, SourceSpan span) {
879 return new IntValue(value, true, span);
880 }
881
882 static Value fromDouble(double value, SourceSpan span) {
883 return new DoubleValue(value, true, span);
884 }
885
886 static Value fromString(String value, SourceSpan span) {
887 return new StringValue(value, true, span);
888 }
889
890 static Value fromNull(SourceSpan span) {
891 return new NullValue(true, span);
892 }
893 }
894
895
896 // TODO(jimhug): rename to PrimitiveValue and refactor further
897 class EvaluatedValue extends Value implements Hashable {
898 /** Is this value treated as const by dart language? */
899 final bool isConst;
900
901 EvaluatedValue(this.isConst, Type type, SourceSpan span):
902 super(type, '@@@', span);
903
904 String get code() {
905 world.internalError('Should not be getting code from raw EvaluatedValue',
906 span);
907 }
908
909 get actualValue() {
910 world.internalError('Should not be getting actual value '
911 'from raw EvaluatedValue', span);
912 }
913
914 bool get needsTemp() => false;
915
916 EvaluatedValue get constValue() => this;
917
918 // TODO(jimhug): Using computed code here without caching is major fear.
919 int hashCode() => code.hashCode();
920
921 bool operator ==(var other) {
922 return other is EvaluatedValue && other.type == this.type &&
923 other.code == this.code;
924 }
925 }
926
927
928 class NullValue extends EvaluatedValue {
929 NullValue(bool isConst, SourceSpan span):
930 super(isConst, world.varType, span);
931
932 get actualValue() => null;
933
934 String get code() => 'null';
935
936 Value binop(int kind, var other, CallingContext context, var node) {
937 if (other is! NullValue) return super.binop(kind, other, context, node);
938
939 final c = isConst && other.isConst;
940 final s = node.span;
941 switch (kind) {
942 case TokenKind.EQ_STRICT:
943 case TokenKind.EQ:
944 return new BoolValue(true, c, s);
945 case TokenKind.NE_STRICT:
946 case TokenKind.NE:
947 return new BoolValue(false, c, s);
948 }
949
950 return super.binop(kind, other, context, node);
951 }
952 }
953
954 class BoolValue extends EvaluatedValue {
955 final bool actualValue;
956
957 BoolValue(this.actualValue, bool isConst, SourceSpan span):
958 super(isConst, world.nonNullBool, span);
959
960 String get code() => actualValue ? 'true' : 'false';
961
962 Value unop(int kind, CallingContext context, var node) {
963 switch (kind) {
964 case TokenKind.NOT:
965 return new BoolValue(!actualValue, isConst, node.span);
966 }
967 return super.unop(kind, context, node);
968 }
969
970 Value binop(int kind, var other, CallingContext context, var node) {
971 if (other is! BoolValue) return super.binop(kind, other, context, node);
972
973 final c = isConst && other.isConst;
974 final s = node.span;
975 bool x = actualValue, y = other.actualValue;
976 switch (kind) {
977 case TokenKind.EQ_STRICT:
978 case TokenKind.EQ:
979 return new BoolValue(x == y, c, s);
980 case TokenKind.NE_STRICT:
981 case TokenKind.NE:
982 return new BoolValue(x != y, c, s);
983 case TokenKind.AND:
984 return new BoolValue(x && y, c, s);
985 case TokenKind.OR:
986 return new BoolValue(x || y, c, s);
987 }
988
989 return super.binop(kind, other, context, node);
990 }
991 }
992
993 class IntValue extends EvaluatedValue {
994 final int actualValue;
995
996 IntValue(this.actualValue, bool isConst, SourceSpan span):
997 super(isConst, world.intType, span);
998
999 // TODO(jimhug): Only add parens when needed.
1000 String get code() => '(${actualValue})';
1001
1002 Value unop(int kind, CallingContext context, var node) {
1003 switch (kind) {
1004 case TokenKind.ADD:
1005 // This is allowed on numeric constants only
1006 return new IntValue(actualValue, isConst, span);
1007 case TokenKind.SUB:
1008 return new IntValue(-actualValue, isConst, span);
1009 case TokenKind.BIT_NOT:
1010 return new IntValue(~actualValue, isConst, span);
1011 }
1012 return super.unop(kind, context, node);
1013 }
1014
1015
1016 Value binop(int kind, var other, CallingContext context, var node) {
1017 final c = isConst && other.isConst;
1018 final s = node.span;
1019 if (other is IntValue) {
1020 int x = actualValue;
1021 int y = other.actualValue;
1022 switch (kind) {
1023 case TokenKind.EQ_STRICT:
1024 case TokenKind.EQ:
1025 return new BoolValue(x == y, c, s);
1026 case TokenKind.NE_STRICT:
1027 case TokenKind.NE:
1028 return new BoolValue(x != y, c, s);
1029
1030 case TokenKind.BIT_OR:
1031 return new IntValue(x | y, c, s);
1032 case TokenKind.BIT_XOR:
1033 return new IntValue(x ^ y, c, s);
1034 case TokenKind.BIT_AND:
1035 return new IntValue(x & y, c, s);
1036 case TokenKind.SHL:
1037 return new IntValue(x << y, c, s);
1038 case TokenKind.SAR:
1039 return new IntValue(x >> y, c, s);
1040 case TokenKind.ADD:
1041 return new IntValue(x + y, c, s);
1042 case TokenKind.SUB:
1043 return new IntValue(x - y, c, s);
1044 case TokenKind.MUL:
1045 return new IntValue(x * y, c, s);
1046 case TokenKind.DIV:
1047 return new DoubleValue(x / y, c, s);
1048 case TokenKind.TRUNCDIV:
1049 return new IntValue(x ~/ y, c, s);
1050 case TokenKind.MOD:
1051 return new IntValue(x % y, c, s);
1052 case TokenKind.LT:
1053 return new BoolValue(x < y, c, s);
1054 case TokenKind.GT:
1055 return new BoolValue(x > y, c, s);
1056 case TokenKind.LTE:
1057 return new BoolValue(x <= y, c, s);
1058 case TokenKind.GTE:
1059 return new BoolValue(x >= y, c, s);
1060 }
1061 } else if (other is DoubleValue) {
1062 int x = actualValue;
1063 double y = other.actualValue;
1064 switch (kind) {
1065 case TokenKind.EQ_STRICT:
1066 case TokenKind.EQ:
1067 return new BoolValue(x == y, c, s);
1068 case TokenKind.NE_STRICT:
1069 case TokenKind.NE:
1070 return new BoolValue(x != y, c, s);
1071
1072 case TokenKind.ADD:
1073 return new DoubleValue(x + y, c, s);
1074 case TokenKind.SUB:
1075 return new DoubleValue(x - y, c, s);
1076 case TokenKind.MUL:
1077 return new DoubleValue(x * y, c, s);
1078 case TokenKind.DIV:
1079 return new DoubleValue(x / y, c, s);
1080 case TokenKind.TRUNCDIV:
1081 // TODO(jimhug): I expected int, but corelib says double here...
1082 return new DoubleValue(x ~/ y, c, s);
1083 case TokenKind.MOD:
1084 return new DoubleValue(x % y, c, s);
1085 case TokenKind.LT:
1086 return new BoolValue(x < y, c, s);
1087 case TokenKind.GT:
1088 return new BoolValue(x > y, c, s);
1089 case TokenKind.LTE:
1090 return new BoolValue(x <= y, c, s);
1091 case TokenKind.GTE:
1092 return new BoolValue(x >= y, c, s);
1093 }
1094 }
1095
1096 return super.binop(kind, other, context, node);
1097 }
1098 }
1099
1100 class DoubleValue extends EvaluatedValue {
1101 final double actualValue;
1102
1103 DoubleValue(this.actualValue, bool isConst, SourceSpan span):
1104 super(isConst, world.doubleType, span);
1105
1106 String get code() => '(${actualValue})';
1107
1108 Value unop(int kind, CallingContext context, var node) {
1109 switch (kind) {
1110 case TokenKind.ADD:
1111 // This is allowed on numeric constants only
1112 return new DoubleValue(actualValue, isConst, span);
1113 case TokenKind.SUB:
1114 return new DoubleValue(-actualValue, isConst, span);
1115 }
1116 return super.unop(kind, context, node);
1117 }
1118
1119 Value binop(int kind, var other, CallingContext context, var node) {
1120 final c = isConst && other.isConst;
1121 final s = node.span;
1122 if (other is DoubleValue) {
1123 double x = actualValue;
1124 double y = other.actualValue;
1125 switch (kind) {
1126 case TokenKind.EQ_STRICT:
1127 case TokenKind.EQ:
1128 return new BoolValue(x == y, c, s);
1129 case TokenKind.NE_STRICT:
1130 case TokenKind.NE:
1131 return new BoolValue(x != y, c, s);
1132
1133 case TokenKind.ADD:
1134 return new DoubleValue(x + y, c, s);
1135 case TokenKind.SUB:
1136 return new DoubleValue(x - y, c, s);
1137 case TokenKind.MUL:
1138 return new DoubleValue(x * y, c, s);
1139 case TokenKind.DIV:
1140 return new DoubleValue(x / y, c, s);
1141 case TokenKind.TRUNCDIV:
1142 // TODO(jimhug): I expected int, but corelib says double here...
1143 return new DoubleValue(x ~/ y, c, s);
1144 case TokenKind.MOD:
1145 return new DoubleValue(x % y, c, s);
1146 case TokenKind.LT:
1147 return new BoolValue(x < y, c, s);
1148 case TokenKind.GT:
1149 return new BoolValue(x > y, c, s);
1150 case TokenKind.LTE:
1151 return new BoolValue(x <= y, c, s);
1152 case TokenKind.GTE:
1153 return new BoolValue(x >= y, c, s);
1154 }
1155 } else if (other is IntValue) {
1156 double x = actualValue;
1157 int y = other.actualValue;
1158 switch (kind) {
1159 case TokenKind.EQ_STRICT:
1160 case TokenKind.EQ:
1161 return new BoolValue(x == y, c, s);
1162 case TokenKind.NE_STRICT:
1163 case TokenKind.NE:
1164 return new BoolValue(x != y, c, s);
1165
1166 case TokenKind.ADD:
1167 return new DoubleValue(x + y, c, s);
1168 case TokenKind.SUB:
1169 return new DoubleValue(x - y, c, s);
1170 case TokenKind.MUL:
1171 return new DoubleValue(x * y, c, s);
1172 case TokenKind.DIV:
1173 return new DoubleValue(x / y, c, s);
1174 case TokenKind.TRUNCDIV:
1175 // TODO(jimhug): I expected int, but corelib says double here...
1176 return new DoubleValue(x ~/ y, c, s);
1177 case TokenKind.MOD:
1178 return new DoubleValue(x % y, c, s);
1179 case TokenKind.LT:
1180 return new BoolValue(x < y, c, s);
1181 case TokenKind.GT:
1182 return new BoolValue(x > y, c, s);
1183 case TokenKind.LTE:
1184 return new BoolValue(x <= y, c, s);
1185 case TokenKind.GTE:
1186 return new BoolValue(x >= y, c, s);
1187 }
1188 }
1189
1190 return super.binop(kind, other, context, node);
1191 }
1192 }
1193
1194 class StringValue extends EvaluatedValue {
1195 final String actualValue;
1196
1197 StringValue(this.actualValue, bool isConst, SourceSpan span):
1198 super(isConst, world.stringType, span);
1199
1200 Value binop(int kind, var other, CallingContext context, var node) {
1201 if (other is! StringValue) return super.binop(kind, other, context, node);
1202
1203 final c = isConst && other.isConst;
1204 final s = node.span;
1205 String x = actualValue, y = other.actualValue;
1206 switch (kind) {
1207 case TokenKind.EQ_STRICT:
1208 case TokenKind.EQ:
1209 return new BoolValue(x == y, c, s);
1210 case TokenKind.NE_STRICT:
1211 case TokenKind.NE:
1212 return new BoolValue(x != y, c, s);
1213 case TokenKind.ADD:
1214 return new StringValue(x + y, c, s);
1215 }
1216
1217 return super.binop(kind, other, context, node);
1218 }
1219
1220
1221 // This is expensive and we may want to cache its value if called often
1222 String get code() {
1223 // TODO(jimhug): This could be much more efficient
1224 StringBuffer buf = new StringBuffer();
1225 buf.add('"');
1226 for (int i=0; i < actualValue.length; i++) {
1227 var ch = actualValue.charCodeAt(i);
1228 switch (ch) {
1229 case 9/*'\t'*/: buf.add(@'\t'); break;
1230 case 10/*'\n'*/: buf.add(@'\n'); break;
1231 case 13/*'\r'*/: buf.add(@'\r'); break;
1232 case 34/*"*/: buf.add(@'\"'); break;
1233 case 92/*\*/: buf.add(@'\\'); break;
1234 default:
1235 if (ch >= 32 && ch <= 126) {
1236 buf.add(actualValue[i]);
1237 } else {
1238 final hex = ch.toRadixString(16);
1239 switch (hex.length) {
1240 case 1: buf.add(@'\x0'); buf.add(hex); break;
1241 case 2: buf.add(@'\x'); buf.add(hex); break;
1242 case 3: buf.add(@'\u0'); buf.add(hex); break;
1243 case 4: buf.add(@'\u'); buf.add(hex); break;
1244 default:
1245 world.internalError(
1246 'unicode values greater than 2 bytes not implemented');
1247 break;
1248 }
1249 }
1250 break;
1251 }
1252 }
1253 buf.add('"');
1254 return buf.toString();
1255 }
1256 }
1257
1258 class ListValue extends EvaluatedValue {
1259 final List<Value> values;
1260
1261 ListValue(this.values, bool isConst, Type type, SourceSpan span):
1262 super(isConst, type, span);
1263
1264 String get code() {
1265 final buf = new StringBuffer();
1266 buf.add('[');
1267 for (var i = 0; i < values.length; i++) {
1268 if (i > 0) buf.add(', ');
1269 buf.add(values[i].code);
1270 }
1271 buf.add(']');
1272 var listCode = buf.toString();
1273
1274 if (!isConst) return listCode;
1275
1276 var v = new Value(world.listType, listCode, span);
1277 final immutableListCtor = world.immutableListType.getConstructor('from');
1278 final result = immutableListCtor.invoke(world.gen.mainContext, null,
1279 new TypeValue(v.type, span), new Arguments(null, [v]));
1280 return result.code;
1281 }
1282
1283 Value binop(int kind, var other, CallingContext context, var node) {
1284 // TODO(jimhug): Support int/double better
1285 if (other is! ListValue) return super.binop(kind, other, context, node);
1286
1287 switch (kind) {
1288 case TokenKind.EQ_STRICT:
1289 return new BoolValue(type == other.type && code == other.code,
1290 isConst && other.isConst, node.span);
1291 case TokenKind.NE_STRICT:
1292 return new BoolValue(type != other.type || code != other.code,
1293 isConst && other.isConst, node.span);
1294 }
1295
1296 return super.binop(kind, other, context, node);
1297 }
1298
1299 GlobalValue getGlobalValue() {
1300 assert(isConst);
1301
1302 return world.gen.globalForConst(this, values);
1303 }
1304 }
1305
1306
1307 class MapValue extends EvaluatedValue {
1308 final List<Value> values;
1309
1310 MapValue(this.values, bool isConst, Type type, SourceSpan span):
1311 super(isConst, type, span);
1312
1313 String get code() {
1314 // Cache?
1315 var items = new ListValue(values, false, world.listType, span);
1316 var tp = world.coreimpl.topType;
1317 Member f = isConst ? tp.getMember('_constMap') : tp.getMember('_map');
1318 // TODO(jimhug): Clean up invoke signature
1319 var value = f.invoke(world.gen.mainContext, null, new TypeValue(tp, null),
1320 new Arguments(null, [items]));
1321 return value.code;
1322 }
1323
1324 GlobalValue getGlobalValue() {
1325 assert(isConst);
1326
1327 return world.gen.globalForConst(this, values);
1328 }
1329
1330 Value binop(int kind, var other, CallingContext context, var node) {
1331 if (other is! MapValue) return super.binop(kind, other, context, node);
1332
1333 switch (kind) {
1334 case TokenKind.EQ_STRICT:
1335 return new BoolValue(type == other.type && code == other.code,
1336 isConst && other.isConst, node.span);
1337 case TokenKind.NE_STRICT:
1338 return new BoolValue(type != other.type || code != other.code,
1339 isConst && other.isConst, node.span);
1340 }
1341
1342 return super.binop(kind, other, context, node);
1343 }
1344 }
1345
1346
1347 class ObjectValue extends EvaluatedValue {
1348 final Map<FieldMember, Value> fields;
1349 final List<FieldMember> fieldsInInitOrder;
1350 bool seenNativeInitializer = false;
1351
1352 String _code;
1353
1354 ObjectValue(bool isConst, Type type, SourceSpan span)
1355 : fields = new Map<FieldMember, Value>(),
1356 fieldsInInitOrder = <FieldMember>[],
1357 super(isConst, type, span);
1358
1359 String get code() {
1360 if (_code === null) validateInitialized(null);
1361 return _code;
1362 }
1363
1364 initFields() {
1365 var allMembers = world.gen._orderValues(type.genericType.getAllMembers());
1366 for (var f in allMembers) {
1367 if (f.isField && !f.isStatic && f.declaringType.isClass) {
1368 _replaceField(f, f.computeValue(), true);
1369 }
1370 }
1371 }
1372
1373 setField(Member field, Value value, [bool duringInit = false]) {
1374 // Unpack constant values
1375 if (value.isConst && value is VariableValue) {
1376 value = value.dynamic.value;
1377 }
1378 var currentValue = fields[field];
1379 if (isConst && !value.isConst) {
1380 world.error('used of non-const value in const intializer', value.span);
1381 }
1382
1383 if (currentValue === null) {
1384 _replaceField(field, value, duringInit);
1385 if (field.isFinal && !duringInit) {
1386 world.error('cannot initialize final fields outside of initializer',
1387 value.span);
1388 }
1389 } else {
1390 // TODO(jimhug): Clarify spec on reinitializing fields with defaults.
1391 if (field.isFinal && field.computeValue() === null) {
1392 world.error('reassignment of field not allowed', value.span,
1393 field.span);
1394 } else {
1395 _replaceField(field, value, duringInit);
1396 }
1397 }
1398 }
1399
1400 _replaceField(Member field, Value value, bool duringInit) {
1401 if (duringInit) {
1402 for (int i = 0; i < fieldsInInitOrder.length; i++) {
1403 if (fieldsInInitOrder[i] == field) {
1404 fieldsInInitOrder[i] = null;
1405 break;
1406 }
1407 }
1408 // TODO(sra): What if the overridden value contains an effect?
1409 fieldsInInitOrder.add(field);
1410 }
1411 fields[field] = value; //currentValue.union(value);
1412 }
1413
1414 validateInitialized(SourceSpan span) {
1415 var buf = new StringBuffer();
1416 buf.add('Object.create(');
1417 buf.add('${type.jsname}.prototype, ');
1418
1419 buf.add('{');
1420 bool addComma = false;
1421 for (var field in fields.getKeys()) {
1422 if (addComma) buf.add(', ');
1423 buf.add(field.jsname);
1424 buf.add(': ');
1425 buf.add('{"value": ');
1426 if (fields[field] === null) {
1427 world.error("Required field '${field.name}' was not initialized",
1428 span, field.span);
1429 buf.add('null');
1430 } else {
1431 buf.add(fields[field].code);
1432 }
1433 buf.add(', writeable: false}');
1434 addComma = true;
1435 }
1436 buf.add('})');
1437 _code = buf.toString();
1438 }
1439
1440 Value binop(int kind, var other, CallingContext context, var node) {
1441 if (other is! ObjectValue) return super.binop(kind, other, context, node);
1442
1443 switch (kind) {
1444 case TokenKind.EQ_STRICT:
1445 case TokenKind.EQ:
1446 return new BoolValue(type == other.type && code == other.code,
1447 isConst && other.isConst, node.span);
1448 case TokenKind.NE_STRICT:
1449 case TokenKind.NE:
1450 return new BoolValue(type != other.type || code != other.code,
1451 isConst && other.isConst, node.span);
1452 }
1453
1454 return super.binop(kind, other, context, node);
1455 }
1456
1457 }
1458
1459
1460 /**
1461 * A global value in the generated code, which corresponds to either a static
1462 * field or a memoized const expressions.
1463 */
1464 class GlobalValue extends Value implements Comparable {
1465 /** Static field definition (null for constant exp). */
1466 final FieldMember field;
1467
1468 /**
1469 * When [this] represents a constant expression, the global variable name
1470 * generated for it.
1471 */
1472 final String name;
1473
1474 /** The value of the field or constant expression to declare. */
1475 final Value exp;
1476
1477 /** True for either cont expressions or a final static field. */
1478 final bool isConst;
1479
1480 /** The actual constant value, when [isConst] is true. */
1481 get actualValue() => exp.dynamic.actualValue;
1482
1483 /** If [isConst], the [EvaluatedValue] that defines this value. */
1484 EvaluatedValue get constValue() => isConst ? exp.constValue : null;
1485
1486 /** Other globals that should be defined before this global. */
1487 final List<GlobalValue> dependencies;
1488
1489 GlobalValue(Type type, String code, bool isConst,
1490 this.field, this.name, this.exp,
1491 SourceSpan span, List<Value> deps):
1492 isConst = isConst,
1493 dependencies = <GlobalValue>[],
1494 super(type, code, span) {
1495
1496 // store transitive-dependencies so sorting algorithm works correctly.
1497 for (var dep in deps) {
1498 if (dep is GlobalValue) {
1499 dependencies.add(dep);
1500 dependencies.addAll(dep.dependencies);
1501 }
1502 }
1503 }
1504
1505 bool get needsTemp() => !isConst;
1506
1507 int compareTo(GlobalValue other) {
1508 // order by dependencies, o.w. by name
1509 if (other == this) {
1510 return 0;
1511 } else if (dependencies.indexOf(other) >= 0) {
1512 return 1;
1513 } else if (other.dependencies.indexOf(this) >= 0) {
1514 return -1;
1515 } else if (dependencies.length > other.dependencies.length) {
1516 return 1;
1517 } else if (dependencies.length < other.dependencies.length) {
1518 return -1;
1519 } else if (name == null && other.name != null) {
1520 return 1;
1521 } else if (name != null && other.name == null) {
1522 return -1;
1523 } else if (name != null) {
1524 return name.compareTo(other.name);
1525 } else {
1526 return field.name.compareTo(other.field.name);
1527 }
1528 }
1529 }
1530
1531 /**
1532 * Represents the hidden or implicit value in a bare reference like 'a'.
1533 * This could be this, the current type, or the current library for purposes
1534 * of resolving members.
1535 */
1536 class BareValue extends Value {
1537 final bool isType;
1538 final CallingContext home;
1539
1540 String _code;
1541
1542 BareValue(this.home, CallingContext outermost, SourceSpan span):
1543 isType = outermost.isStatic,
1544 super(outermost.method.declaringType, null, span);
1545
1546 bool get needsTemp() => false;
1547 bool _shouldBindDynamically() => false;
1548
1549 String get code() => _code;
1550
1551 // TODO(jimhug): Lazy initialization here is weird!
1552 void _ensureCode() {
1553 if (_code === null) _code = isType ? type.jsname : home._makeThisCode();
1554 }
1555
1556 MemberSet _tryResolveMember(CallingContext context, String name, Node node) {
1557 assert(context == home);
1558
1559 // TODO(jimhug): Confirm this matches final resolution of issue 641.
1560 var member = type.getMember(name);
1561 if (member == null || member.declaringType != type) {
1562 var libMember = home.library.lookup(name, span);
1563 if (libMember !== null) {
1564 return libMember.preciseMemberSet;
1565 }
1566 }
1567
1568 _ensureCode();
1569 return super._tryResolveMember(context, name, node);
1570 }
1571 }
1572
1573 /** A reference to 'super'. */
1574 // TODO(jmesserly): override resolveMember to clean up the one on Value
1575 class SuperValue extends Value {
1576 SuperValue(Type parentType, SourceSpan span):
1577 super(parentType, 'this', span);
1578
1579 bool get needsTemp() => false;
1580 bool get isSuper() => true;
1581 bool _shouldBindDynamically() => false;
1582
1583 Value _tryUnion(Value right) => right is SuperValue ? this : null;
1584 }
1585
1586 /** A reference to 'this'. */
1587 class ThisValue extends Value {
1588 ThisValue(Type type, String code, SourceSpan span):
1589 super(type, code, span);
1590
1591 bool get needsTemp() => false;
1592 bool _shouldBindDynamically() => false;
1593
1594 Value _tryUnion(Value right) => right is ThisValue ? this : null;
1595 }
1596
1597 /** A pretend first-class type. */
1598 class TypeValue extends Value {
1599 TypeValue(Type type, SourceSpan span):
1600 super(type, null, span);
1601
1602 bool get needsTemp() => false;
1603 bool get isType() => true;
1604 bool _shouldBindDynamically() => false;
1605
1606 Value _tryUnion(Value right) => right is TypeValue ? this : null;
1607 }
1608
1609
1610 /**
1611 * A value that represents a variable or parameter. The [assigned] value can be
1612 * mutated when the variable is assigned to a new Value.
1613 */
1614 class VariableValue extends Value {
1615 final bool isFinal;
1616 final Value value;
1617
1618 VariableValue(Type staticType, String code, SourceSpan span,
1619 [this.isFinal=false, Value value]):
1620 value = _unwrap(value),
1621 super(staticType, code, span) {
1622
1623 // these are not really first class
1624 assert(value === null || !value.isType && !value.isSuper);
1625
1626 // TODO(jmesserly): should we do convertTo here, so the check doesn't get
1627 // missed? There are some cases where this assert doesn't hold.
1628 // assert(value === null || value.staticType == staticType);
1629 }
1630
1631 static Value _unwrap(Value v) {
1632 if (v === null) return null;
1633 if (v is VariableValue) {
1634 v = v.dynamic.value;
1635 }
1636 return v;
1637 }
1638
1639 Value _tryUnion(Value right) => Value.union(value, right);
1640
1641 bool get needsTemp() => false;
1642 Type get type() => value !== null ? value.type : staticType;
1643 Type get staticType() => super.type;
1644 bool get isConst() => value !== null ? value.isConst : false;
1645
1646 // TODO(jmesserly): we could use this for checking uninitialized values
1647 bool get isInitialized() => value != null;
1648
1649 VariableValue replaceValue(Value v) =>
1650 new VariableValue(staticType, code, span, isFinal, v);
1651
1652 // TODO(jmesserly): anything else to override?
1653 Value unop(int kind, CallingContext context, var node) {
1654 if (value != null) {
1655 return replaceValue(value.unop(kind, context, node));
1656 }
1657 return super.unop(kind, context, node);
1658 }
1659 Value binop(int kind, var other, CallingContext context, var node) {
1660 if (value != null) {
1661 return replaceValue(value.binop(kind, _unwrap(other), context, node));
1662 }
1663 return super.binop(kind, other, context, node);
1664 }
1665 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698