Index: frog/value.dart |
diff --git a/frog/value.dart b/frog/value.dart |
deleted file mode 100644 |
index 13e9c0547efa42ea399710700fcbfcb78aea8501..0000000000000000000000000000000000000000 |
--- a/frog/value.dart |
+++ /dev/null |
@@ -1,1665 +0,0 @@ |
-// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
-// for details. All rights reserved. Use of this source code is governed by a |
-// BSD-style license that can be found in the LICENSE file. |
- |
- |
-interface CallingContext { |
- MemberSet findMembers(String name); |
- CounterLog get counters(); |
- Library get library(); |
- bool get isStatic(); |
- MethodMember get method(); |
- |
- bool get needsCode(); |
- bool get showWarnings(); |
- |
- // Hopefully remove the 5 members below that are only used for code gen. |
- String _makeThisCode(); |
- |
- Value getTemp(Value value); |
- VariableValue forceTemp(Value value); |
- Value assignTemp(Value tmp, Value v); |
- void freeTemp(VariableValue value); |
-} |
- |
-// TODO(jimhug): Value needs better separation into three parts: |
-// 1. Static analysis |
-// 2. Type inferred abstract interpretation analysis |
-// 3. Actual code generation |
-/** |
- * This subtype of value is the one and only version used for static |
- * analysis. It has no code and its type is always the static type. |
- */ |
-class PureStaticValue extends Value { |
- bool isConst; |
- bool isType; |
- |
- // TODO(jimhug): Can we remove span? |
- PureStaticValue(Type type, SourceSpan span, |
- [this.isConst = false, this.isType = false]): |
- super(type, null, span); |
- |
- Member getMem(CallingContext context, String name, Node node) { |
- var member = type.getMember(name); |
- |
- if (member == null) { |
- world.warning('cannot find "$name" on "${type.name}"', node.span); |
- return null; |
- } |
- |
- if (isType && !member.isStatic) { |
- world.error('cannot refer to instance member as static', node.span); |
- } |
- |
- return member; |
- } |
- |
- Value get_(CallingContext context, String name, Node node) { |
- if (type.isVar) return new PureStaticValue(world.varType, node.span); |
- var member = getMem(context, name, node); |
- if (member == null) return new PureStaticValue(world.varType, node.span); |
- |
- return member._get(context, node, this); |
- } |
- |
- Value set_(CallingContext context, String name, Node node, Value value, |
- [int kind=0, int returnKind=ReturnKind.IGNORE]) { |
- if (type.isVar) return new PureStaticValue(world.varType, node.span); |
- |
- var member = getMem(context, name, node); |
- if (member != null) { |
- member._set(context, node, this, value); |
- } |
- return new PureStaticValue(value.type, node.span); |
- } |
- |
- Value setIndex(CallingContext context, Value index, Node node, Value value, |
- [int kind=0, int returnKind=ReturnKind.IGNORE]) { |
- var tmp = invoke(context, ':setindex', node, |
- new Arguments(null, [index, value])); |
- return new PureStaticValue(value.type, node.span); |
- } |
- |
- Value unop(int kind, CallingContext context, var node) { |
- switch (kind) { |
- case TokenKind.NOT: |
- // TODO(jimhug): Issue #359 seeks to clarify this behavior. |
- // ?var newVal = convertTo(context, world.nonNullBool); |
- return new PureStaticValue(world.boolType, node.span); |
- case TokenKind.ADD: |
- if (!isConst && !type.isNum) { |
- world.error('no unary add operator in dart', node.span); |
- } |
- return new PureStaticValue(world.numType, node.span); |
- case TokenKind.SUB: |
- return invoke(context, ':negate', node, Arguments.EMPTY); |
- case TokenKind.BIT_NOT: |
- return invoke(context, ':bit_not', node, Arguments.EMPTY); |
- } |
- world.internalError('unimplemented: ${node.op}', node.span); |
- } |
- |
- Value binop(int kind, Value other, CallingContext context, var node) { |
- var isConst = isConst && other.isConst; |
- |
- |
- switch (kind) { |
- case TokenKind.AND: |
- case TokenKind.OR: |
- return new PureStaticValue(world.boolType, node.span, isConst); |
- case TokenKind.EQ_STRICT: |
- return new PureStaticValue(world.boolType, node.span, isConst); |
- case TokenKind.NE_STRICT: |
- return new PureStaticValue(world.boolType, node.span, isConst); |
- } |
- |
- var name = kind == TokenKind.NE ? ':ne': TokenKind.binaryMethodName(kind); |
- var ret = invoke(context, name, node, new Arguments(null, [other])); |
- if (isConst) { |
- ret = new PureStaticValue(ret.type, node.span, isConst); |
- } |
- return ret; |
- } |
- |
- |
- Value invoke(CallingContext context, String name, Node node, |
- Arguments args) { |
- if (type.isVar) return new PureStaticValue(world.varType, node.span); |
- if (type.isFunction && name == ':call') { |
- return new PureStaticValue(world.varType, node.span); |
- } |
- |
- var member = getMem(context, name, node); |
- if (member == null) return new PureStaticValue(world.varType, node.span); |
- |
- return member.invoke(context, node, this, args); |
- } |
- |
- Value invokeNoSuchMethod(CallingContext context, String name, Node node, |
- [Arguments args]) { |
- if (isType) { |
- world.error('member lookup failed for "$name"', node.span); |
- } |
- |
- var member = getMem(context, 'noSuchMethod', node); |
- if (member == null) return new PureStaticValue(world.varType, node.span); |
- |
- final noSuchArgs = new Arguments(null, [ |
- new PureStaticValue(world.stringType, node.span), |
- new PureStaticValue(world.listType, node.span)]); |
- |
- return member.invoke(context, node, this, noSuchArgs); |
- } |
- |
- // These are implementation details of convertTo. (Eventually we might find it |
- // easier to just implement convertTo itself). |
- |
- Value _typeAssert(CallingContext context, Type toType) { |
- return _changeStaticType(toType); |
- } |
- |
- Value _changeStaticType(Type toType) { |
- if (toType === type) return this; |
- return new PureStaticValue(toType, span, isConst, isType); |
- } |
-} |
- |
- |
-/** |
- * Represents a meta-value for code generation. |
- */ |
-class Value { |
- /** The inferred (i.e. most precise) [Type] of the [Value]. */ |
- final Type type; |
- |
- /** The javascript code to generate this value. */ |
- final String code; |
- |
- /** The source location that created this value for error messages. */ |
- final SourceSpan span; |
- |
- Value(this.type, this.code, this.span) { |
- if (type == null) world.internalError('type passed as null', span); |
- } |
- |
- |
- /** Is this a pretend first-class type? */ |
- bool get isType() => false; |
- |
- /** Is this a reference to super? */ |
- bool get isSuper() => false; |
- |
- /** Is this value a constant expression? */ |
- bool get isConst() => false; |
- |
- /** Is this a final variable? */ |
- bool get isFinal() => false; |
- |
- /** If we reference this value multiple times, do we need a temp? */ |
- bool get needsTemp() => true; |
- |
- /** |
- * The statically declared [Type] of the [Value]. This type determines which |
- * kind of static type warnings are issued. It's also the type that is used |
- * for generating type assertions (i.e. given `Foo x; ...; x = expr;`, |
- * expr will be checked against "Foo" regardless of the inferred type of `x`). |
- */ |
- Type get staticType() => type; |
- |
- /** If [isConst], the [EvaluatedValue] that defines this value. */ |
- EvaluatedValue get constValue() => null; |
- |
- static Value comma(Value x, Value y) { |
- return new Value(y.type, '(${x.code}, ${y.code})', null); |
- } |
- |
- // TODO(jmesserly): more work is needed to make unifying all kinds of Values |
- // work properly. |
- static Value union(Value x, Value y) { |
- if (y === null || x == y) return x; |
- if (x === null) return y; |
- |
- var ret = x._tryUnion(y); |
- if (ret != null) return ret; |
- |
- // TODO(jmesserly): might want to call a _tryUnionReversed here. |
- ret = y._tryUnion(x); |
- if (ret != null) return ret; |
- |
- // TODO(jmesserly): should use something like UnionValue and track the |
- // precise set of types. For now we find the Type.union. |
- |
- // TODO(jmesserly): What to do about code? Right now, we're intentionally |
- // throwing it away because they aren't used in the current flow-insensitive |
- // inference. |
- return new Value(Type.union(x.type, y.type), null, null); |
- } |
- |
- Value _tryUnion(Value right) => null; |
- |
- // TODO(jimhug): remove once type system works better. |
- setField(Member field, Value value, [bool duringInit = false]) { } |
- |
- // Nothing to do in general? |
- validateInitialized(SourceSpan span) { } |
- |
- // TODO(jimhug): Fix these names once get/set are truly pseudo-keywords. |
- // See issue #379. |
- Value get_(CallingContext context, String name, Node node) { |
- final member = _resolveMember(context, name, node); |
- if (member != null) { |
- return member._get(context, node, this); |
- } else { |
- return invokeNoSuchMethod(context, 'get:$name', node); |
- } |
- } |
- |
- Value set_(CallingContext context, String name, Node node, Value value, |
- [int kind=0, int returnKind=ReturnKind.IGNORE]) { |
- final member = _resolveMember(context, name, node); |
- if (member != null) { |
- var thisValue = this; |
- var thisTmp = null; |
- var retTmp = null; |
- if (kind != 0) { |
- // TODO(jimhug): Very special number optimizations will go here... |
- thisTmp = context.getTemp(thisValue); |
- thisValue = context.assignTemp(thisTmp, thisValue); |
- var lhs = member._get(context, node, thisTmp); |
- if (returnKind == ReturnKind.PRE) { |
- retTmp = context.forceTemp(lhs); |
- lhs = context.assignTemp(retTmp, lhs); |
- } |
- value = lhs.binop(kind, value, context, node); |
- } |
- |
- if (returnKind == ReturnKind.POST) { |
- // TODO(jimhug): Optimize this away when native JS is detected. |
- retTmp = context.forceTemp(value); |
- value = context.assignTemp(retTmp, value); |
- } |
- |
- var ret = member._set(context, node, thisValue, value); |
- if (thisTmp != null && thisTmp != this) context.freeTemp(thisTmp); |
- if (retTmp != null) { |
- context.freeTemp(retTmp); |
- return Value.comma(ret, retTmp); |
- } else { |
- return ret; |
- } |
- } else { |
- // TODO(jimhug): Need to support += and noSuchMethod better. |
- return invokeNoSuchMethod(context, 'set:$name', node, |
- new Arguments(null, [value])); |
- } |
- } |
- |
- // TODO(jimhug): This method body has too much in common with set_ above. |
- Value setIndex(CallingContext context, Value index, Node node, Value value, |
- [int kind=0, int returnKind=ReturnKind.IGNORE]) { |
- final member = _resolveMember(context, ':setindex', node); |
- if (member != null) { |
- var thisValue = this; |
- var indexValue = index; |
- var thisTmp = null; |
- var indexTmp = null; |
- var retTmp = null; |
- if (returnKind == ReturnKind.POST) { |
- // TODO(jimhug): Optimize this away when native JS works. |
- retTmp = context.forceTemp(value); |
- } |
- if (kind != 0) { |
- // TODO(jimhug): Very special number optimizations will go here... |
- thisTmp = context.getTemp(this); |
- indexTmp = context.getTemp(index); |
- thisValue = context.assignTemp(thisTmp, thisValue); |
- indexValue = context.assignTemp(indexTmp, indexValue); |
- |
- if (returnKind == ReturnKind.PRE) { |
- retTmp = context.forceTemp(value); |
- } |
- |
- var lhs = thisTmp.invoke(context, ':index', node, |
- new Arguments(null, [indexTmp])); |
- if (returnKind == ReturnKind.PRE) { |
- lhs = context.assignTemp(retTmp, lhs); |
- } |
- value = lhs.binop(kind, value, context, node); |
- } |
- if (returnKind == ReturnKind.POST) { |
- value = context.assignTemp(retTmp, value); |
- } |
- |
- var ret = member.invoke(context, node, thisValue, |
- new Arguments(null, [indexValue, value])); |
- if (thisTmp != null && thisTmp != this) context.freeTemp(thisTmp); |
- if (indexTmp != null && indexTmp != index) context.freeTemp(indexTmp); |
- if (retTmp != null) { |
- context.freeTemp(retTmp); |
- return Value.comma(ret, retTmp); |
- } else { |
- return ret; |
- } |
- } else { |
- // TODO(jimhug): Need to support += and noSuchMethod better. |
- return invokeNoSuchMethod(context, ':index', node, |
- new Arguments(null, [index, value])); |
- } |
- } |
- |
- //Value getIndex(CallingContext context, Value index, var node) { |
- //} |
- |
- Value unop(int kind, CallingContext context, var node) { |
- switch (kind) { |
- case TokenKind.NOT: |
- // TODO(jimhug): Issue #359 seeks to clarify this behavior. |
- var newVal = convertTo(context, world.nonNullBool); |
- return new Value(newVal.type, '!${newVal.code}', node.span); |
- case TokenKind.ADD: |
- world.error('no unary add operator in dart', node.span); |
- break; |
- case TokenKind.SUB: |
- return invoke(context, ':negate', node, Arguments.EMPTY); |
- case TokenKind.BIT_NOT: |
- return invoke(context, ':bit_not', node, Arguments.EMPTY); |
- } |
- world.internalError('unimplemented: ${node.op}', node.span); |
- } |
- |
- bool _mayOverrideEqual() { |
- // TODO(jimhug): Need to check subtypes as well |
- return type.isVar || type.isObject || |
- !type.getMember(':eq').declaringType.isObject; |
- } |
- |
- Value binop(int kind, Value other, CallingContext context, var node) { |
- switch (kind) { |
- case TokenKind.AND: |
- case TokenKind.OR: |
- final code = '${code} ${node.op} ${other.code}'; |
- return new Value(world.nonNullBool, code, node.span); |
- |
- case TokenKind.EQ_STRICT: |
- case TokenKind.NE_STRICT: |
- var op = kind == TokenKind.EQ_STRICT ? '==' : '!='; |
- if (code == 'null') { |
- return new Value(world.nonNullBool, |
- 'null ${op} ${other.code}', node.span); |
- } else if (other.code == 'null') { |
- return new Value(world.nonNullBool, |
- 'null ${op} ${code}', node.span); |
- } else { |
- // TODO(jimhug): Add check to see if we can just use op on this type |
- // TODO(jimhug): Optimize case of other.needsTemp == false. |
- var ret; |
- var check; |
- if (needsTemp) { |
- var tmp = context.forceTemp(this); |
- ret = tmp.code; |
- check = '(${ret} = ${code}) == null'; |
- } else { |
- ret = code; |
- check = 'null == ${code}'; |
- } |
- return new Value(world.nonNullBool, |
- '(${check} ? null ${op} (${other.code}) : ${ret} ${op}= ${other.code})', |
- node.span); |
- } |
- |
- case TokenKind.EQ: |
- if (other.code == 'null') { |
- if (!_mayOverrideEqual()) { |
- return new Value(world.nonNullBool, '${code} == ${other.code}', |
- node.span); |
- } |
- } else if (code == 'null') { |
- return new Value(world.nonNullBool, '${code} == ${other.code}', |
- node.span); |
- } |
- break; |
- case TokenKind.NE: |
- if (other.code == 'null') { |
- if (!_mayOverrideEqual()) { |
- return new Value(world.nonNullBool, '${code} != ${other.code}', |
- node.span); |
- } |
- } else if (code == 'null') { |
- return new Value(world.nonNullBool, '${code} != ${other.code}', |
- node.span); |
- } |
- break; |
- |
- } |
- |
- var name = kind == TokenKind.NE ? ':ne': TokenKind.binaryMethodName(kind); |
- return invoke(context, name, node, new Arguments(null, [other])); |
- } |
- |
- |
- Value invoke(CallingContext context, String name, Node node, |
- Arguments args) { |
- // TODO(jmesserly): it'd be nice to remove these special cases |
- // We could create a :call in world members, and have that handle the |
- // canInvoke/Invoke logic. |
- |
- // Note: this check is a little different than the one in canInvoke, because |
- // sometimes we need to call dynamically even if we found the :call method |
- // statically. |
- |
- if (name == ':call') { |
- if (isType) { |
- world.error('must use "new" or "const" to construct a new instance', |
- node.span); |
- } |
- if (type.needsVarCall(args)) { |
- return _varCall(context, node, args); |
- } |
- } |
- |
- var member = _resolveMember(context, name, node); |
- if (member == null) { |
- return invokeNoSuchMethod(context, name, node, args); |
- } else { |
- return member.invoke(context, node, this, args); |
- } |
- } |
- |
- /** |
- * True if this class (or some related class that is not Object) overrides |
- * noSuchMethod. If it does we suppress warnings about unknown members. |
- */ |
- // TODO(jmesserly): should we be doing this? |
- bool _hasOverriddenNoSuchMethod() { |
- var m = type.getMember('noSuchMethod'); |
- return m != null && !m.declaringType.isObject; |
- } |
- |
- // TODO(jimhug): Handle more precise types here, i.e. consts or closed... |
- bool get isPreciseType() => isSuper || isType; |
- |
- void _missingMemberError(CallingContext context, String name, Node node) { |
- bool onStaticType = false; |
- if (type != staticType) { |
- onStaticType = staticType.getMember(name) !== null; |
- } |
- |
- if (!onStaticType && context.showWarnings && |
- !_isVarOrParameterType(staticType) && !_hasOverriddenNoSuchMethod()) { |
- // warn if the member was not found, or error if it is a static lookup. |
- var typeName = staticType.name; |
- if (typeName == null) typeName = staticType.library.name; |
- var message = 'cannot resolve "$name" on "${typeName}"'; |
- if (isType) { |
- world.error(message, node.span); |
- } else { |
- world.warning(message, node.span); |
- } |
- } |
- } |
- |
- |
- |
- MemberSet _tryResolveMember(CallingContext context, String name, Node node) { |
- var member = type.getMember(name); |
- if (member == null) { |
- _missingMemberError(context, name, node); |
- return null; |
- } else { |
- if (isType && !member.isStatic && context.showWarnings) { |
- world.error('cannot refer to instance member as static', node.span); |
- return null; |
- } |
- } |
- |
- if (isPreciseType || member.isStatic) { |
- return member.preciseMemberSet; |
- } else { |
- return member.potentialMemberSet; |
- } |
- } |
- |
- // TODO(jmesserly): until reified generics are fixed, treat ParameterType as |
- // "var". |
- bool _isVarOrParameterType(Type t) => t.isVar || t is ParameterType; |
- |
- bool _shouldBindDynamically() { |
- return _isVarOrParameterType(type) || options.forceDynamic && !isConst; |
- } |
- |
- // TODO(jimhug): Better type here - currently is union(Member, MemberSet) |
- MemberSet _resolveMember(CallingContext context, String name, Node node) { |
- var member = null; |
- if (!_shouldBindDynamically()) { |
- member = _tryResolveMember(context, name, node); |
- } |
- |
- // Fall back to a dynamic operation for instance members |
- if (member == null && !isSuper && !isType) { |
- member = context.findMembers(name); |
- if (member == null && context.showWarnings) { |
- var where = 'the world'; |
- if (name.startsWith('_')) { |
- where = 'library "${context.library.name}"'; |
- } |
- world.warning('$name is not defined anywhere in $where.', |
- node.span); |
- } |
- } |
- |
- return member; |
- } |
- |
- checkFirstClass(SourceSpan span) { |
- if (isType) { |
- world.error('Types are not first class', span); |
- } |
- } |
- |
- /** Generate a call to an unknown function type. */ |
- Value _varCall(CallingContext context, Node node, Arguments args) { |
- // TODO(jmesserly): calls to unknown functions will bypass type checks, |
- // which normally happen on the caller side, or in the generated stub for |
- // dynamic method calls. What should we do? |
- var stub = world.functionType.getCallStub(args); |
- return stub.invoke(context, node, this, args); |
- } |
- |
- /** True if convertTo would generate a conversion. */ |
- bool needsConversion(Type toType) { |
- var c = convertTo(null, toType); |
- return c == null || code != c.code; |
- } |
- |
- /** |
- * Assign or convert this value to another type. |
- * This is used for converting between function types, inserting type |
- * checks when --enable_type_checks is enabled, and wrapping callback |
- * functions passed to the dom so we can restore their isolate context. |
- */ |
- Value convertTo(CallingContext context, Type toType) { |
- |
- // Issue type warnings unless we are processing a dynamic operation. |
- bool checked = context != null && context.showWarnings; |
- |
- var callMethod = toType.getCallMethod(); |
- if (callMethod != null) { |
- if (checked && !toType.isAssignable(type)) { |
- convertWarning(toType); |
- } |
- |
- return _maybeWrapFunction(toType, callMethod); |
- } |
- |
- // If we're assigning from a var, pretend it's Object for the purpose of |
- // runtime checks. |
- |
- // TODO(jmesserly): I'm a little bothered by the fact that we can't call |
- // isSubtypeOf directly. If we tracked null literals as the bottom type, |
- // and then only allowed Dynamic to be bottom for generic type args, I think |
- // we'd get the right behavior from isSubtypeOf. |
- Type fromType = type; |
- if (type.isVar && (code != 'null' || !toType.isNullable)) { |
- fromType = world.objectType; |
- } |
- |
- // TODO(jmesserly): remove the special case for "num" when our num handling |
- // is better. |
- bool bothNum = type.isNum && toType.isNum; |
- if (!fromType.isSubtypeOf(toType) && !bothNum) { |
- // If it is a narrowing conversion, we'll need a check in checked mode. |
- |
- if (checked && !toType.isSubtypeOf(type)) { |
- // According to the static types, this conversion can't work. |
- convertWarning(toType); |
- } |
- |
- if (options.enableTypeChecks) { |
- if (context == null) { |
- // If we're called from needsConversion, we don't need a context. |
- // Just return null so it knows a conversion is required. |
- return null; |
- } |
- return _typeAssert(context, toType); |
- } |
- } |
- |
- return _changeStaticType(toType); |
- } |
- |
- // Nothing to do in general. |
- Value _changeStaticType(Type toType) => this; |
- |
- /** |
- * Wraps a function with a conversion, so it can be called directly from |
- * Dart or JS code with the proper arity. We avoid the wrapping if the target |
- * function has the same arity. |
- * |
- * Also wraps a callback attached to the dom (e.g. event listeners, |
- * setTimeout) so we can restore it's isolate context information. This is |
- * needed so that callbacks are executed within the context of the isolate |
- * that created them in the first place. |
- */ |
- Value _maybeWrapFunction(Type toType, MethodMember callMethod) { |
- int arity = callMethod.parameters.length; |
- var myCall = type.getCallMethod(); |
- |
- Value result = this; |
- if (myCall == null || myCall.parameters.length != arity) { |
- final stub = world.functionType.getCallStub(new Arguments.bare(arity)); |
- result = new Value(toType, 'to\$${stub.name}($code)', span); |
- } |
- |
- // TODO(jmesserly): handle when type or toType are type parameters. |
- if (toType.library.isDomOrHtml && !type.library.isDomOrHtml) { |
- // TODO(jmesserly): either remove this or make it a more first class |
- // feature of our native interop. We shouldn't be checking for the DOM |
- // library--any host environment (like node.js) might need this feature |
- // for isolates too. But we don't want to wrap every function we send to |
- // native code--many callbacks like List.filter are perfectly safe. |
- if (arity == 0) { |
- world.gen.corejs.useWrap0 = true; |
- } else if (arity == 1) { |
- world.gen.corejs.useWrap1 = true; |
- } else if (arity == 2) { |
- world.gen.corejs.useWrap2 = true; |
- } |
- |
- result = new Value(toType, '\$wrap_call\$$arity(${result.code})', span); |
- } |
- |
- return result._changeStaticType(toType); |
- } |
- |
- /** |
- * Generates a run time type assertion for the given value. This works like |
- * [instanceOf], but it allows null since Dart types are nullable. |
- * Also it will throw a TypeError if it gets the wrong type. |
- */ |
- Value _typeAssert(CallingContext context, Type toType) { |
- if (toType is ParameterType) { |
- ParameterType p = toType; |
- toType = p.extendsType; |
- } |
- |
- if (toType.isObject || toType.isVar) { |
- world.internalError( |
- 'We thought ${type.name} is not a subtype of ${toType.name}?'); |
- } |
- |
- // Prevent a stack overflow when forceDynamic and type checks are both |
- // enabled. forceDynamic would cause the TypeError constructor to type check |
- // its arguments, which in turn invokes the TypeError constructor, ad |
- // infinitum. |
- String throwTypeError(String paramName) => world.withoutForceDynamic(() { |
- final typeErrorCtor = world.typeErrorType.getConstructor('_internal'); |
- world.gen.corejs.ensureTypeNameOf(); |
- final result = typeErrorCtor.invoke(context, null, |
- new TypeValue(world.typeErrorType, null), |
- new Arguments(null, [ |
- new Value(world.objectType, paramName, null), |
- new Value(world.stringType, '"${toType.name}"', null)])); |
- world.gen.corejs.useThrow = true; |
- return '\$throw(${result.code})'; |
- }); |
- |
- // TODO(jmesserly): better assert for integers? |
- if (toType.isNum) toType = world.numType; |
- |
- // Generate a check like these: |
- // obj && obj.is$TypeName() |
- // $assert_int(obj) |
- // |
- // We rely on the fact that calling an undefined method produces a JS |
- // TypeError. Alternatively we could define fallbacks on Object that throw. |
- String check; |
- if (toType.isVoid) { |
- check = '\$assert_void($code)'; |
- if (toType.typeCheckCode == null) { |
- toType.typeCheckCode = ''' |
-function \$assert_void(x) { |
- if (x == null) return null; |
- ${throwTypeError("x")} |
-}'''; |
- } |
- } else if (toType == world.nonNullBool) { |
- // This could be made less of a special case |
- world.gen.corejs.useNotNullBool = true; |
- check = '\$notnull_bool($code)'; |
- |
- } else if (toType.library.isCore && toType.typeofName != null) { |
- check = '\$assert_${toType.name}($code)'; |
- |
- if (toType.typeCheckCode == null) { |
- toType.typeCheckCode = ''' |
-function \$assert_${toType.name}(x) { |
- if (x == null || typeof(x) == "${toType.typeofName}") return x; |
- ${throwTypeError("x")} |
-}'''; |
- } |
- } else { |
- toType.isChecked = true; |
- |
- String checkName = 'assert\$${toType.jsname}'; |
- |
- // If we track nullability, we could simplify this check. |
- var temp = context.getTemp(this); |
- check = '(${context.assignTemp(temp, this).code} == null ? null :'; |
- check += ' ${temp.code}.$checkName())'; |
- if (this != temp) context.freeTemp(temp); |
- |
- // Generate the fallback on Object (that throws a TypeError) |
- world.objectType.varStubs.putIfAbsent(checkName, |
- () => new VarMethodStub(checkName, null, Arguments.EMPTY, |
- throwTypeError('this'))); |
- } |
- |
- context.counters.typeAsserts++; |
- return new Value(toType, check, span); |
- } |
- |
- /** |
- * Test to see if value is an instance of this type. |
- * |
- * - If a primitive type, then uses the JavaScript typeof. |
- * - If it's a non-generic class, use instanceof. |
- * - Otherwise add a fake member to test for. This value is generated |
- * as a function so that it can be called for a runtime failure. |
- */ |
- Value instanceOf(CallingContext context, Type toType, SourceSpan span, |
- [bool isTrue=true, bool forceCheck=false]) { |
- // TODO(jimhug): Optimize away tests that will always pass unless |
- // forceCheck is true. |
- |
- if (toType.isVar) { |
- world.error('cannot resolve type', span); |
- } |
- |
- String testCode = null; |
- if (toType.isVar || toType.isObject || toType is ParameterType) { |
- // Note: everything is an Object, including null. |
- if (needsTemp) { |
- return new Value(world.nonNullBool, '($code, true)', span); |
- } else { |
- // TODO(jimhug): Mark non-const? |
- return Value.fromBool(true, span); |
- } |
- } |
- |
- if (toType.library.isCore) { |
- var typeofName = toType.typeofName; |
- if (typeofName != null) { |
- testCode = "(typeof($code) ${isTrue ? '==' : '!='} '$typeofName')"; |
- } |
- } |
- |
- if (toType.isClass |
- && !toType.isHiddenNativeType && !toType.isConcreteGeneric) { |
- toType.markUsed(); |
- testCode = '($code instanceof ${toType.jsname})'; |
- if (!isTrue) { |
- testCode = '!${testCode}'; |
- } |
- } |
- if (testCode == null) { |
- toType.isTested = true; |
- |
- // If we track nullability, we could simplify this check. |
- var temp = context.getTemp(this); |
- |
- String checkName = 'is\$${toType.jsname}'; |
- testCode = '(${context.assignTemp(temp, this).code} &&' |
- ' ${temp.code}.$checkName())'; |
- if (isTrue) { |
- // Add !! to convert to boolean. |
- // TODO(jimhug): only do this if needed |
- testCode = '!!${testCode}'; |
- } else { |
- // The single ! here nicely converts undefined to false and function |
- // to true. |
- testCode = '!${testCode}'; |
- } |
- if (this != temp) context.freeTemp(temp); |
- |
- // Generate the fallback on Object (that returns false) |
- if (!world.objectType.varStubs.containsKey(checkName)) { |
- world.objectType.varStubs[checkName] = |
- new VarMethodStub(checkName, null, Arguments.EMPTY, 'return false'); |
- } |
- } |
- return new Value(world.nonNullBool, testCode, span); |
- } |
- |
- void convertWarning(Type toType) { |
- // TODO(jmesserly): better error messages for type conversion failures |
- world.warning( |
- 'type "${type.fullname}" is not assignable to "${toType.fullname}"', |
- span); |
- } |
- |
- Value invokeNoSuchMethod(CallingContext context, String name, Node node, |
- [Arguments args]) { |
- if (isType) { |
- world.error('member lookup failed for "$name"', node.span); |
- } |
- |
- var pos = ''; |
- if (args != null) { |
- var argsCode = []; |
- for (int i = 0; i < args.length; i++) { |
- argsCode.add(args.values[i].code); |
- } |
- pos = Strings.join(argsCode, ", "); // don't remove trailing nulls |
- } |
- final noSuchArgs = [ |
- new Value(world.stringType, '"$name"', node.span), |
- new Value(world.listType, '[$pos]', node.span)]; |
- |
- // TODO(jmesserly): should be passing names but that breaks tests. Oh well. |
- /*if (args != null && args.hasNames) { |
- var names = []; |
- for (int i = args.bareCount; i < args.length; i++) { |
- names.add('"${args.getName(i)}", ${args.values[i].code}'); |
- } |
- noSuchArgs.add(new Value(world.gen.useMapFactory(), |
- '\$map(${Strings.join(names, ", ")})')); |
- }*/ |
- |
- // Finally, invoke noSuchMethod |
- return _resolveMember(context, 'noSuchMethod', node).invoke( |
- context, node, this, new Arguments(null, noSuchArgs)); |
- } |
- |
- |
- static Value fromBool(bool value, SourceSpan span) { |
- return new BoolValue(value, true, span); |
- } |
- |
- static Value fromInt(int value, SourceSpan span) { |
- return new IntValue(value, true, span); |
- } |
- |
- static Value fromDouble(double value, SourceSpan span) { |
- return new DoubleValue(value, true, span); |
- } |
- |
- static Value fromString(String value, SourceSpan span) { |
- return new StringValue(value, true, span); |
- } |
- |
- static Value fromNull(SourceSpan span) { |
- return new NullValue(true, span); |
- } |
-} |
- |
- |
-// TODO(jimhug): rename to PrimitiveValue and refactor further |
-class EvaluatedValue extends Value implements Hashable { |
- /** Is this value treated as const by dart language? */ |
- final bool isConst; |
- |
- EvaluatedValue(this.isConst, Type type, SourceSpan span): |
- super(type, '@@@', span); |
- |
- String get code() { |
- world.internalError('Should not be getting code from raw EvaluatedValue', |
- span); |
- } |
- |
- get actualValue() { |
- world.internalError('Should not be getting actual value ' |
- 'from raw EvaluatedValue', span); |
- } |
- |
- bool get needsTemp() => false; |
- |
- EvaluatedValue get constValue() => this; |
- |
- // TODO(jimhug): Using computed code here without caching is major fear. |
- int hashCode() => code.hashCode(); |
- |
- bool operator ==(var other) { |
- return other is EvaluatedValue && other.type == this.type && |
- other.code == this.code; |
- } |
-} |
- |
- |
-class NullValue extends EvaluatedValue { |
- NullValue(bool isConst, SourceSpan span): |
- super(isConst, world.varType, span); |
- |
- get actualValue() => null; |
- |
- String get code() => 'null'; |
- |
- Value binop(int kind, var other, CallingContext context, var node) { |
- if (other is! NullValue) return super.binop(kind, other, context, node); |
- |
- final c = isConst && other.isConst; |
- final s = node.span; |
- switch (kind) { |
- case TokenKind.EQ_STRICT: |
- case TokenKind.EQ: |
- return new BoolValue(true, c, s); |
- case TokenKind.NE_STRICT: |
- case TokenKind.NE: |
- return new BoolValue(false, c, s); |
- } |
- |
- return super.binop(kind, other, context, node); |
- } |
-} |
- |
-class BoolValue extends EvaluatedValue { |
- final bool actualValue; |
- |
- BoolValue(this.actualValue, bool isConst, SourceSpan span): |
- super(isConst, world.nonNullBool, span); |
- |
- String get code() => actualValue ? 'true' : 'false'; |
- |
- Value unop(int kind, CallingContext context, var node) { |
- switch (kind) { |
- case TokenKind.NOT: |
- return new BoolValue(!actualValue, isConst, node.span); |
- } |
- return super.unop(kind, context, node); |
- } |
- |
- Value binop(int kind, var other, CallingContext context, var node) { |
- if (other is! BoolValue) return super.binop(kind, other, context, node); |
- |
- final c = isConst && other.isConst; |
- final s = node.span; |
- bool x = actualValue, y = other.actualValue; |
- switch (kind) { |
- case TokenKind.EQ_STRICT: |
- case TokenKind.EQ: |
- return new BoolValue(x == y, c, s); |
- case TokenKind.NE_STRICT: |
- case TokenKind.NE: |
- return new BoolValue(x != y, c, s); |
- case TokenKind.AND: |
- return new BoolValue(x && y, c, s); |
- case TokenKind.OR: |
- return new BoolValue(x || y, c, s); |
- } |
- |
- return super.binop(kind, other, context, node); |
- } |
-} |
- |
-class IntValue extends EvaluatedValue { |
- final int actualValue; |
- |
- IntValue(this.actualValue, bool isConst, SourceSpan span): |
- super(isConst, world.intType, span); |
- |
- // TODO(jimhug): Only add parens when needed. |
- String get code() => '(${actualValue})'; |
- |
- Value unop(int kind, CallingContext context, var node) { |
- switch (kind) { |
- case TokenKind.ADD: |
- // This is allowed on numeric constants only |
- return new IntValue(actualValue, isConst, span); |
- case TokenKind.SUB: |
- return new IntValue(-actualValue, isConst, span); |
- case TokenKind.BIT_NOT: |
- return new IntValue(~actualValue, isConst, span); |
- } |
- return super.unop(kind, context, node); |
- } |
- |
- |
- Value binop(int kind, var other, CallingContext context, var node) { |
- final c = isConst && other.isConst; |
- final s = node.span; |
- if (other is IntValue) { |
- int x = actualValue; |
- int y = other.actualValue; |
- switch (kind) { |
- case TokenKind.EQ_STRICT: |
- case TokenKind.EQ: |
- return new BoolValue(x == y, c, s); |
- case TokenKind.NE_STRICT: |
- case TokenKind.NE: |
- return new BoolValue(x != y, c, s); |
- |
- case TokenKind.BIT_OR: |
- return new IntValue(x | y, c, s); |
- case TokenKind.BIT_XOR: |
- return new IntValue(x ^ y, c, s); |
- case TokenKind.BIT_AND: |
- return new IntValue(x & y, c, s); |
- case TokenKind.SHL: |
- return new IntValue(x << y, c, s); |
- case TokenKind.SAR: |
- return new IntValue(x >> y, c, s); |
- case TokenKind.ADD: |
- return new IntValue(x + y, c, s); |
- case TokenKind.SUB: |
- return new IntValue(x - y, c, s); |
- case TokenKind.MUL: |
- return new IntValue(x * y, c, s); |
- case TokenKind.DIV: |
- return new DoubleValue(x / y, c, s); |
- case TokenKind.TRUNCDIV: |
- return new IntValue(x ~/ y, c, s); |
- case TokenKind.MOD: |
- return new IntValue(x % y, c, s); |
- case TokenKind.LT: |
- return new BoolValue(x < y, c, s); |
- case TokenKind.GT: |
- return new BoolValue(x > y, c, s); |
- case TokenKind.LTE: |
- return new BoolValue(x <= y, c, s); |
- case TokenKind.GTE: |
- return new BoolValue(x >= y, c, s); |
- } |
- } else if (other is DoubleValue) { |
- int x = actualValue; |
- double y = other.actualValue; |
- switch (kind) { |
- case TokenKind.EQ_STRICT: |
- case TokenKind.EQ: |
- return new BoolValue(x == y, c, s); |
- case TokenKind.NE_STRICT: |
- case TokenKind.NE: |
- return new BoolValue(x != y, c, s); |
- |
- case TokenKind.ADD: |
- return new DoubleValue(x + y, c, s); |
- case TokenKind.SUB: |
- return new DoubleValue(x - y, c, s); |
- case TokenKind.MUL: |
- return new DoubleValue(x * y, c, s); |
- case TokenKind.DIV: |
- return new DoubleValue(x / y, c, s); |
- case TokenKind.TRUNCDIV: |
- // TODO(jimhug): I expected int, but corelib says double here... |
- return new DoubleValue(x ~/ y, c, s); |
- case TokenKind.MOD: |
- return new DoubleValue(x % y, c, s); |
- case TokenKind.LT: |
- return new BoolValue(x < y, c, s); |
- case TokenKind.GT: |
- return new BoolValue(x > y, c, s); |
- case TokenKind.LTE: |
- return new BoolValue(x <= y, c, s); |
- case TokenKind.GTE: |
- return new BoolValue(x >= y, c, s); |
- } |
- } |
- |
- return super.binop(kind, other, context, node); |
- } |
-} |
- |
-class DoubleValue extends EvaluatedValue { |
- final double actualValue; |
- |
- DoubleValue(this.actualValue, bool isConst, SourceSpan span): |
- super(isConst, world.doubleType, span); |
- |
- String get code() => '(${actualValue})'; |
- |
- Value unop(int kind, CallingContext context, var node) { |
- switch (kind) { |
- case TokenKind.ADD: |
- // This is allowed on numeric constants only |
- return new DoubleValue(actualValue, isConst, span); |
- case TokenKind.SUB: |
- return new DoubleValue(-actualValue, isConst, span); |
- } |
- return super.unop(kind, context, node); |
- } |
- |
- Value binop(int kind, var other, CallingContext context, var node) { |
- final c = isConst && other.isConst; |
- final s = node.span; |
- if (other is DoubleValue) { |
- double x = actualValue; |
- double y = other.actualValue; |
- switch (kind) { |
- case TokenKind.EQ_STRICT: |
- case TokenKind.EQ: |
- return new BoolValue(x == y, c, s); |
- case TokenKind.NE_STRICT: |
- case TokenKind.NE: |
- return new BoolValue(x != y, c, s); |
- |
- case TokenKind.ADD: |
- return new DoubleValue(x + y, c, s); |
- case TokenKind.SUB: |
- return new DoubleValue(x - y, c, s); |
- case TokenKind.MUL: |
- return new DoubleValue(x * y, c, s); |
- case TokenKind.DIV: |
- return new DoubleValue(x / y, c, s); |
- case TokenKind.TRUNCDIV: |
- // TODO(jimhug): I expected int, but corelib says double here... |
- return new DoubleValue(x ~/ y, c, s); |
- case TokenKind.MOD: |
- return new DoubleValue(x % y, c, s); |
- case TokenKind.LT: |
- return new BoolValue(x < y, c, s); |
- case TokenKind.GT: |
- return new BoolValue(x > y, c, s); |
- case TokenKind.LTE: |
- return new BoolValue(x <= y, c, s); |
- case TokenKind.GTE: |
- return new BoolValue(x >= y, c, s); |
- } |
- } else if (other is IntValue) { |
- double x = actualValue; |
- int y = other.actualValue; |
- switch (kind) { |
- case TokenKind.EQ_STRICT: |
- case TokenKind.EQ: |
- return new BoolValue(x == y, c, s); |
- case TokenKind.NE_STRICT: |
- case TokenKind.NE: |
- return new BoolValue(x != y, c, s); |
- |
- case TokenKind.ADD: |
- return new DoubleValue(x + y, c, s); |
- case TokenKind.SUB: |
- return new DoubleValue(x - y, c, s); |
- case TokenKind.MUL: |
- return new DoubleValue(x * y, c, s); |
- case TokenKind.DIV: |
- return new DoubleValue(x / y, c, s); |
- case TokenKind.TRUNCDIV: |
- // TODO(jimhug): I expected int, but corelib says double here... |
- return new DoubleValue(x ~/ y, c, s); |
- case TokenKind.MOD: |
- return new DoubleValue(x % y, c, s); |
- case TokenKind.LT: |
- return new BoolValue(x < y, c, s); |
- case TokenKind.GT: |
- return new BoolValue(x > y, c, s); |
- case TokenKind.LTE: |
- return new BoolValue(x <= y, c, s); |
- case TokenKind.GTE: |
- return new BoolValue(x >= y, c, s); |
- } |
- } |
- |
- return super.binop(kind, other, context, node); |
- } |
-} |
- |
-class StringValue extends EvaluatedValue { |
- final String actualValue; |
- |
- StringValue(this.actualValue, bool isConst, SourceSpan span): |
- super(isConst, world.stringType, span); |
- |
- Value binop(int kind, var other, CallingContext context, var node) { |
- if (other is! StringValue) return super.binop(kind, other, context, node); |
- |
- final c = isConst && other.isConst; |
- final s = node.span; |
- String x = actualValue, y = other.actualValue; |
- switch (kind) { |
- case TokenKind.EQ_STRICT: |
- case TokenKind.EQ: |
- return new BoolValue(x == y, c, s); |
- case TokenKind.NE_STRICT: |
- case TokenKind.NE: |
- return new BoolValue(x != y, c, s); |
- case TokenKind.ADD: |
- return new StringValue(x + y, c, s); |
- } |
- |
- return super.binop(kind, other, context, node); |
- } |
- |
- |
- // This is expensive and we may want to cache its value if called often |
- String get code() { |
- // TODO(jimhug): This could be much more efficient |
- StringBuffer buf = new StringBuffer(); |
- buf.add('"'); |
- for (int i=0; i < actualValue.length; i++) { |
- var ch = actualValue.charCodeAt(i); |
- switch (ch) { |
- case 9/*'\t'*/: buf.add(@'\t'); break; |
- case 10/*'\n'*/: buf.add(@'\n'); break; |
- case 13/*'\r'*/: buf.add(@'\r'); break; |
- case 34/*"*/: buf.add(@'\"'); break; |
- case 92/*\*/: buf.add(@'\\'); break; |
- default: |
- if (ch >= 32 && ch <= 126) { |
- buf.add(actualValue[i]); |
- } else { |
- final hex = ch.toRadixString(16); |
- switch (hex.length) { |
- case 1: buf.add(@'\x0'); buf.add(hex); break; |
- case 2: buf.add(@'\x'); buf.add(hex); break; |
- case 3: buf.add(@'\u0'); buf.add(hex); break; |
- case 4: buf.add(@'\u'); buf.add(hex); break; |
- default: |
- world.internalError( |
- 'unicode values greater than 2 bytes not implemented'); |
- break; |
- } |
- } |
- break; |
- } |
- } |
- buf.add('"'); |
- return buf.toString(); |
- } |
-} |
- |
-class ListValue extends EvaluatedValue { |
- final List<Value> values; |
- |
- ListValue(this.values, bool isConst, Type type, SourceSpan span): |
- super(isConst, type, span); |
- |
- String get code() { |
- final buf = new StringBuffer(); |
- buf.add('['); |
- for (var i = 0; i < values.length; i++) { |
- if (i > 0) buf.add(', '); |
- buf.add(values[i].code); |
- } |
- buf.add(']'); |
- var listCode = buf.toString(); |
- |
- if (!isConst) return listCode; |
- |
- var v = new Value(world.listType, listCode, span); |
- final immutableListCtor = world.immutableListType.getConstructor('from'); |
- final result = immutableListCtor.invoke(world.gen.mainContext, null, |
- new TypeValue(v.type, span), new Arguments(null, [v])); |
- return result.code; |
- } |
- |
- Value binop(int kind, var other, CallingContext context, var node) { |
- // TODO(jimhug): Support int/double better |
- if (other is! ListValue) return super.binop(kind, other, context, node); |
- |
- switch (kind) { |
- case TokenKind.EQ_STRICT: |
- return new BoolValue(type == other.type && code == other.code, |
- isConst && other.isConst, node.span); |
- case TokenKind.NE_STRICT: |
- return new BoolValue(type != other.type || code != other.code, |
- isConst && other.isConst, node.span); |
- } |
- |
- return super.binop(kind, other, context, node); |
- } |
- |
- GlobalValue getGlobalValue() { |
- assert(isConst); |
- |
- return world.gen.globalForConst(this, values); |
- } |
-} |
- |
- |
-class MapValue extends EvaluatedValue { |
- final List<Value> values; |
- |
- MapValue(this.values, bool isConst, Type type, SourceSpan span): |
- super(isConst, type, span); |
- |
- String get code() { |
- // Cache? |
- var items = new ListValue(values, false, world.listType, span); |
- var tp = world.coreimpl.topType; |
- Member f = isConst ? tp.getMember('_constMap') : tp.getMember('_map'); |
- // TODO(jimhug): Clean up invoke signature |
- var value = f.invoke(world.gen.mainContext, null, new TypeValue(tp, null), |
- new Arguments(null, [items])); |
- return value.code; |
- } |
- |
- GlobalValue getGlobalValue() { |
- assert(isConst); |
- |
- return world.gen.globalForConst(this, values); |
- } |
- |
- Value binop(int kind, var other, CallingContext context, var node) { |
- if (other is! MapValue) return super.binop(kind, other, context, node); |
- |
- switch (kind) { |
- case TokenKind.EQ_STRICT: |
- return new BoolValue(type == other.type && code == other.code, |
- isConst && other.isConst, node.span); |
- case TokenKind.NE_STRICT: |
- return new BoolValue(type != other.type || code != other.code, |
- isConst && other.isConst, node.span); |
- } |
- |
- return super.binop(kind, other, context, node); |
- } |
-} |
- |
- |
-class ObjectValue extends EvaluatedValue { |
- final Map<FieldMember, Value> fields; |
- final List<FieldMember> fieldsInInitOrder; |
- bool seenNativeInitializer = false; |
- |
- String _code; |
- |
- ObjectValue(bool isConst, Type type, SourceSpan span) |
- : fields = new Map<FieldMember, Value>(), |
- fieldsInInitOrder = <FieldMember>[], |
- super(isConst, type, span); |
- |
- String get code() { |
- if (_code === null) validateInitialized(null); |
- return _code; |
- } |
- |
- initFields() { |
- var allMembers = world.gen._orderValues(type.genericType.getAllMembers()); |
- for (var f in allMembers) { |
- if (f.isField && !f.isStatic && f.declaringType.isClass) { |
- _replaceField(f, f.computeValue(), true); |
- } |
- } |
- } |
- |
- setField(Member field, Value value, [bool duringInit = false]) { |
- // Unpack constant values |
- if (value.isConst && value is VariableValue) { |
- value = value.dynamic.value; |
- } |
- var currentValue = fields[field]; |
- if (isConst && !value.isConst) { |
- world.error('used of non-const value in const intializer', value.span); |
- } |
- |
- if (currentValue === null) { |
- _replaceField(field, value, duringInit); |
- if (field.isFinal && !duringInit) { |
- world.error('cannot initialize final fields outside of initializer', |
- value.span); |
- } |
- } else { |
- // TODO(jimhug): Clarify spec on reinitializing fields with defaults. |
- if (field.isFinal && field.computeValue() === null) { |
- world.error('reassignment of field not allowed', value.span, |
- field.span); |
- } else { |
- _replaceField(field, value, duringInit); |
- } |
- } |
- } |
- |
- _replaceField(Member field, Value value, bool duringInit) { |
- if (duringInit) { |
- for (int i = 0; i < fieldsInInitOrder.length; i++) { |
- if (fieldsInInitOrder[i] == field) { |
- fieldsInInitOrder[i] = null; |
- break; |
- } |
- } |
- // TODO(sra): What if the overridden value contains an effect? |
- fieldsInInitOrder.add(field); |
- } |
- fields[field] = value; //currentValue.union(value); |
- } |
- |
- validateInitialized(SourceSpan span) { |
- var buf = new StringBuffer(); |
- buf.add('Object.create('); |
- buf.add('${type.jsname}.prototype, '); |
- |
- buf.add('{'); |
- bool addComma = false; |
- for (var field in fields.getKeys()) { |
- if (addComma) buf.add(', '); |
- buf.add(field.jsname); |
- buf.add(': '); |
- buf.add('{"value": '); |
- if (fields[field] === null) { |
- world.error("Required field '${field.name}' was not initialized", |
- span, field.span); |
- buf.add('null'); |
- } else { |
- buf.add(fields[field].code); |
- } |
- buf.add(', writeable: false}'); |
- addComma = true; |
- } |
- buf.add('})'); |
- _code = buf.toString(); |
- } |
- |
- Value binop(int kind, var other, CallingContext context, var node) { |
- if (other is! ObjectValue) return super.binop(kind, other, context, node); |
- |
- switch (kind) { |
- case TokenKind.EQ_STRICT: |
- case TokenKind.EQ: |
- return new BoolValue(type == other.type && code == other.code, |
- isConst && other.isConst, node.span); |
- case TokenKind.NE_STRICT: |
- case TokenKind.NE: |
- return new BoolValue(type != other.type || code != other.code, |
- isConst && other.isConst, node.span); |
- } |
- |
- return super.binop(kind, other, context, node); |
- } |
- |
-} |
- |
- |
-/** |
- * A global value in the generated code, which corresponds to either a static |
- * field or a memoized const expressions. |
- */ |
-class GlobalValue extends Value implements Comparable { |
- /** Static field definition (null for constant exp). */ |
- final FieldMember field; |
- |
- /** |
- * When [this] represents a constant expression, the global variable name |
- * generated for it. |
- */ |
- final String name; |
- |
- /** The value of the field or constant expression to declare. */ |
- final Value exp; |
- |
- /** True for either cont expressions or a final static field. */ |
- final bool isConst; |
- |
- /** The actual constant value, when [isConst] is true. */ |
- get actualValue() => exp.dynamic.actualValue; |
- |
- /** If [isConst], the [EvaluatedValue] that defines this value. */ |
- EvaluatedValue get constValue() => isConst ? exp.constValue : null; |
- |
- /** Other globals that should be defined before this global. */ |
- final List<GlobalValue> dependencies; |
- |
- GlobalValue(Type type, String code, bool isConst, |
- this.field, this.name, this.exp, |
- SourceSpan span, List<Value> deps): |
- isConst = isConst, |
- dependencies = <GlobalValue>[], |
- super(type, code, span) { |
- |
- // store transitive-dependencies so sorting algorithm works correctly. |
- for (var dep in deps) { |
- if (dep is GlobalValue) { |
- dependencies.add(dep); |
- dependencies.addAll(dep.dependencies); |
- } |
- } |
- } |
- |
- bool get needsTemp() => !isConst; |
- |
- int compareTo(GlobalValue other) { |
- // order by dependencies, o.w. by name |
- if (other == this) { |
- return 0; |
- } else if (dependencies.indexOf(other) >= 0) { |
- return 1; |
- } else if (other.dependencies.indexOf(this) >= 0) { |
- return -1; |
- } else if (dependencies.length > other.dependencies.length) { |
- return 1; |
- } else if (dependencies.length < other.dependencies.length) { |
- return -1; |
- } else if (name == null && other.name != null) { |
- return 1; |
- } else if (name != null && other.name == null) { |
- return -1; |
- } else if (name != null) { |
- return name.compareTo(other.name); |
- } else { |
- return field.name.compareTo(other.field.name); |
- } |
- } |
-} |
- |
-/** |
- * Represents the hidden or implicit value in a bare reference like 'a'. |
- * This could be this, the current type, or the current library for purposes |
- * of resolving members. |
- */ |
-class BareValue extends Value { |
- final bool isType; |
- final CallingContext home; |
- |
- String _code; |
- |
- BareValue(this.home, CallingContext outermost, SourceSpan span): |
- isType = outermost.isStatic, |
- super(outermost.method.declaringType, null, span); |
- |
- bool get needsTemp() => false; |
- bool _shouldBindDynamically() => false; |
- |
- String get code() => _code; |
- |
- // TODO(jimhug): Lazy initialization here is weird! |
- void _ensureCode() { |
- if (_code === null) _code = isType ? type.jsname : home._makeThisCode(); |
- } |
- |
- MemberSet _tryResolveMember(CallingContext context, String name, Node node) { |
- assert(context == home); |
- |
- // TODO(jimhug): Confirm this matches final resolution of issue 641. |
- var member = type.getMember(name); |
- if (member == null || member.declaringType != type) { |
- var libMember = home.library.lookup(name, span); |
- if (libMember !== null) { |
- return libMember.preciseMemberSet; |
- } |
- } |
- |
- _ensureCode(); |
- return super._tryResolveMember(context, name, node); |
- } |
-} |
- |
-/** A reference to 'super'. */ |
-// TODO(jmesserly): override resolveMember to clean up the one on Value |
-class SuperValue extends Value { |
- SuperValue(Type parentType, SourceSpan span): |
- super(parentType, 'this', span); |
- |
- bool get needsTemp() => false; |
- bool get isSuper() => true; |
- bool _shouldBindDynamically() => false; |
- |
- Value _tryUnion(Value right) => right is SuperValue ? this : null; |
-} |
- |
-/** A reference to 'this'. */ |
-class ThisValue extends Value { |
- ThisValue(Type type, String code, SourceSpan span): |
- super(type, code, span); |
- |
- bool get needsTemp() => false; |
- bool _shouldBindDynamically() => false; |
- |
- Value _tryUnion(Value right) => right is ThisValue ? this : null; |
-} |
- |
-/** A pretend first-class type. */ |
-class TypeValue extends Value { |
- TypeValue(Type type, SourceSpan span): |
- super(type, null, span); |
- |
- bool get needsTemp() => false; |
- bool get isType() => true; |
- bool _shouldBindDynamically() => false; |
- |
- Value _tryUnion(Value right) => right is TypeValue ? this : null; |
-} |
- |
- |
-/** |
- * A value that represents a variable or parameter. The [assigned] value can be |
- * mutated when the variable is assigned to a new Value. |
- */ |
-class VariableValue extends Value { |
- final bool isFinal; |
- final Value value; |
- |
- VariableValue(Type staticType, String code, SourceSpan span, |
- [this.isFinal=false, Value value]): |
- value = _unwrap(value), |
- super(staticType, code, span) { |
- |
- // these are not really first class |
- assert(value === null || !value.isType && !value.isSuper); |
- |
- // TODO(jmesserly): should we do convertTo here, so the check doesn't get |
- // missed? There are some cases where this assert doesn't hold. |
- // assert(value === null || value.staticType == staticType); |
- } |
- |
- static Value _unwrap(Value v) { |
- if (v === null) return null; |
- if (v is VariableValue) { |
- v = v.dynamic.value; |
- } |
- return v; |
- } |
- |
- Value _tryUnion(Value right) => Value.union(value, right); |
- |
- bool get needsTemp() => false; |
- Type get type() => value !== null ? value.type : staticType; |
- Type get staticType() => super.type; |
- bool get isConst() => value !== null ? value.isConst : false; |
- |
- // TODO(jmesserly): we could use this for checking uninitialized values |
- bool get isInitialized() => value != null; |
- |
- VariableValue replaceValue(Value v) => |
- new VariableValue(staticType, code, span, isFinal, v); |
- |
- // TODO(jmesserly): anything else to override? |
- Value unop(int kind, CallingContext context, var node) { |
- if (value != null) { |
- return replaceValue(value.unop(kind, context, node)); |
- } |
- return super.unop(kind, context, node); |
- } |
- Value binop(int kind, var other, CallingContext context, var node) { |
- if (value != null) { |
- return replaceValue(value.binop(kind, _unwrap(other), context, node)); |
- } |
- return super.binop(kind, other, context, node); |
- } |
-} |