OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 | |
6 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 } | |
OLD | NEW |