blob: 94c98c5f9b08edbfd6af8cfd32eb8b6b882bee4e [file] [log] [blame]
// 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.
class Constant implements Hashable {
const Constant();
bool isNull() => false;
bool isBool() => false;
bool isTrue() => false;
bool isFalse() => false;
bool isInt() => false;
bool isDouble() => false;
bool isNum() => false;
bool isString() => false;
bool isList() => false;
bool isMap() => false;
bool isConstructedObject() => false;
/** Returns true if the constant is null, a bool, a number or a string. */
bool isPrimitive() => false;
/** Returns true if the constant is a list, a map or a constructed object. */
bool isObject() => false;
abstract void writeJsCode(StringBuffer buffer, ConstantHandler handler);
/**
* Unless the constant can be emitted multiple times (as for numbers and
* strings) adds its canonical name to the buffer.
*/
abstract void writeCanonicalizedJsCode(StringBuffer buffer,
ConstantHandler handler);
abstract List<Constant> getDependencies();
}
class PrimitiveConstant extends Constant {
abstract get value();
const PrimitiveConstant();
bool isPrimitive() => true;
bool operator ==(var other) {
if (other is !PrimitiveConstant) return false;
PrimitiveConstant otherPrimitive = other;
// We use == instead of === so that DartStrings compare correctly.
return value == otherPrimitive.value;
}
String toString() => value.toString();
// Primitive constants don't have dependencies.
List<Constant> getDependencies() => const <Constant>[];
abstract DartString toDartString();
void writeCanonicalizedJsCode(StringBuffer buffer, ConstantHandler handler) {
writeJsCode(buffer, handler);
}
}
class NullConstant extends PrimitiveConstant {
factory NullConstant() => const NullConstant._internal();
const NullConstant._internal();
bool isNull() => true;
get value() => null;
void writeJsCode(StringBuffer buffer, ConstantHandler handler) {
buffer.add("(void 0)");
}
// The magic constant has no meaning. It is just a random value.
int hashCode() => 785965825;
DartString toDartString() => const LiteralDartString("null");
}
class NumConstant extends PrimitiveConstant {
abstract num get value();
const NumConstant();
bool isNum() => true;
}
class IntConstant extends NumConstant {
final int value;
factory IntConstant(int value) {
switch(value) {
case 0: return const IntConstant._internal(0);
case 1: return const IntConstant._internal(1);
case 2: return const IntConstant._internal(2);
case 3: return const IntConstant._internal(3);
case 4: return const IntConstant._internal(4);
case 5: return const IntConstant._internal(5);
case 6: return const IntConstant._internal(6);
case 7: return const IntConstant._internal(7);
case 8: return const IntConstant._internal(8);
case 9: return const IntConstant._internal(9);
case 10: return const IntConstant._internal(10);
case -1: return const IntConstant._internal(-1);
case -2: return const IntConstant._internal(-2);
default: return new IntConstant._internal(value);
}
}
const IntConstant._internal(this.value);
bool isInt() => true;
void writeJsCode(StringBuffer buffer, ConstantHandler handler) {
buffer.add("$value");
}
// We have to override the equality operator so that ints and doubles are
// treated as separate constants.
// The is [:!IntConstant:] check at the beginning of the function makes sure
// that we compare only equal to integer constants.
bool operator ==(var other) {
if (other is !IntConstant) return false;
IntConstant otherInt = other;
return value == otherInt.value;
}
int hashCode() => value.hashCode();
DartString toDartString() => new DartString.literal(value.toString());
}
class DoubleConstant extends NumConstant {
final double value;
factory DoubleConstant(double value) {
if (value.isNaN()) {
return const DoubleConstant._internal(double.NAN);
} else if (value == double.INFINITY) {
return const DoubleConstant._internal(double.INFINITY);
} else if (value == -double.INFINITY) {
return const DoubleConstant._internal(-double.INFINITY);
} else if (value == 0.0 && !value.isNegative()) {
return const DoubleConstant._internal(0.0);
} else if (value == 1.0) {
return const DoubleConstant._internal(1.0);
} else {
return new DoubleConstant._internal(value);
}
}
const DoubleConstant._internal(this.value);
bool isDouble() => true;
void writeJsCode(StringBuffer buffer, ConstantHandler handler) {
if (value.isNaN()) {
buffer.add("(0/0)");
} else if (value == double.INFINITY) {
buffer.add("(1/0)");
} else if (value == -double.INFINITY) {
buffer.add("(-1/0)");
} else {
buffer.add("$value");
}
}
bool operator ==(var other) {
if (other is !DoubleConstant) return false;
DoubleConstant otherDouble = other;
double otherValue = otherDouble.value;
if (value == 0.0 && otherValue == 0.0) {
return value.isNegative() == otherValue.isNegative();
} else if (value.isNaN()) {
return otherValue.isNaN();
} else {
return value == otherValue;
}
}
int hashCode() => value.hashCode();
DartString toDartString() => new DartString.literal(value.toString());
}
class BoolConstant extends PrimitiveConstant {
factory BoolConstant(value) {
return value ? new TrueConstant() : new FalseConstant();
}
const BoolConstant._internal();
bool isBool() => true;
BoolConstant unaryFold(String op) {
if (op == "!") return new BoolConstant(!value);
return null;
}
abstract BoolConstant negate();
}
class TrueConstant extends BoolConstant {
final bool value = true;
factory TrueConstant() => const TrueConstant._internal();
const TrueConstant._internal() : super._internal();
bool isTrue() => true;
void writeJsCode(StringBuffer buffer, ConstantHandler handler) {
buffer.add("true");
}
FalseConstant negate() => new FalseConstant();
bool operator ==(var other) => this === other;
// The magic constant is just a random value. It does not have any
// significance.
int hashCode() => 499;
DartString toDartString() => const LiteralDartString("true");
}
class FalseConstant extends BoolConstant {
final bool value = false;
factory FalseConstant() => const FalseConstant._internal();
const FalseConstant._internal() : super._internal();
bool isFalse() => true;
void writeJsCode(StringBuffer buffer, ConstantHandler handler) {
buffer.add("false");
}
TrueConstant negate() => new TrueConstant();
bool operator ==(var other) => this === other;
// The magic constant is just a random value. It does not have any
// significance.
int hashCode() => 536555975;
DartString toDartString() => const LiteralDartString("false");
}
class StringConstant extends PrimitiveConstant {
final DartString value;
int _hashCode;
StringConstant(this.value) {
// TODO(floitsch): cache StringConstants.
// TODO(floitsch): compute hashcode without calling toString() on the
// DartString.
_hashCode = value.slowToString().hashCode();
}
bool isString() => true;
void writeJsCode(StringBuffer buffer, ConstantHandler handler) {
buffer.add("'");
ConstantHandler.writeEscapedString(value, buffer, (reason) {
throw new CompilerCancelledException(reason);
});
buffer.add("'");
}
bool operator ==(var other) {
if (other is !StringConstant) return false;
StringConstant otherString = other;
return (_hashCode == otherString._hashCode) && (value == otherString.value);
}
int hashCode() => _hashCode;
DartString toDartString() => value;
}
class ObjectConstant extends Constant {
final Type type;
ObjectConstant(this.type);
bool isObject() => true;
// TODO(1603): The class should be marked as abstract, but the VM doesn't
// currently allow this.
abstract int hashCode();
void writeCanonicalizedJsCode(StringBuffer buffer, ConstantHandler handler) {
String name = handler.getNameForConstant(this);
String isolatePrototype = "${handler.compiler.namer.ISOLATE}.prototype";
buffer.add("$isolatePrototype.$name");
}
}
class ListConstant extends ObjectConstant {
final List<Constant> entries;
int _hashCode;
ListConstant(Type type, this.entries) : super(type) {
// TODO(floitsch): create a better hash.
int hash = 0;
for (Constant input in entries) hash ^= input.hashCode();
_hashCode = hash;
}
bool isList() => true;
void writeJsCode(StringBuffer buffer, ConstantHandler handler) {
// TODO(floitsch): we should not need to go through the compiler to make
// the list constant.
String isolatePrototype = "${handler.compiler.namer.ISOLATE}.prototype";
buffer.add("$isolatePrototype.makeConstantList");
buffer.add("([");
for (int i = 0; i < entries.length; i++) {
if (i != 0) buffer.add(", ");
Constant entry = entries[i];
entry.writeCanonicalizedJsCode(buffer, handler);
}
buffer.add("])");
}
bool operator ==(var other) {
if (other is !ListConstant) return false;
ListConstant otherList = other;
if (hashCode() != otherList.hashCode()) return false;
// TODO(floitsch): verify that the generic types are the same.
if (entries.length != otherList.entries.length) return false;
for (int i = 0; i < entries.length; i++) {
if (entries[i] != otherList.entries[i]) return false;
}
return true;
}
int hashCode() => _hashCode;
List<Constant> getDependencies() => entries;
}
class MapConstant extends ObjectConstant {
/** The dart class implementing constant map literals. */
static final SourceString DART_CLASS = const SourceString("ConstantMap");
static final SourceString LENGTH_NAME = const SourceString("length");
static final SourceString JS_OBJECT_NAME = const SourceString("_jsObject");
static final SourceString KEYS_NAME = const SourceString("_keys");
final ListConstant keys;
final List<Constant> values;
int _hashCode;
MapConstant(Type type, this.keys, this.values) : super(type) {
// TODO(floitsch): create a better hash.
int hash = 0;
for (Constant value in values) hash ^= value.hashCode();
_hashCode = hash;
}
bool isMap() => true;
void writeJsCode(StringBuffer buffer, ConstantHandler handler) {
void writeJsMap() {
buffer.add("{");
for (int i = 0; i < keys.entries.length; i++) {
if (i != 0) buffer.add(", ");
StringConstant key = keys.entries[i];
key.writeJsCode(buffer, handler);
buffer.add(": ");
Constant value = values[i];
value.writeCanonicalizedJsCode(buffer, handler);
}
buffer.add("}");
}
void badFieldCountError() {
handler.compiler.internalError(
"Compiler and ConstantMap disagree on number of fields.");
}
ClassElement classElement = type.element;
buffer.add("new ");
buffer.add(handler.getJsConstructor(classElement));
buffer.add("(");
// The arguments of the JavaScript constructor for any given Dart class
// are in the same order as the members of the class element.
int emittedArgumentCount = 0;
for (Element element in classElement.members) {
if (element.name == LENGTH_NAME) {
buffer.add(keys.entries.length);
} else if (element.name == JS_OBJECT_NAME) {
writeJsMap();
} else if (element.name == KEYS_NAME) {
keys.writeCanonicalizedJsCode(buffer, handler);
} else {
// Skip methods.
if (element.kind == ElementKind.FIELD) badFieldCountError();
continue;
}
emittedArgumentCount++;
if (emittedArgumentCount == 3) {
break; // All arguments have been emitted.
} else {
buffer.add(", ");
}
}
if (emittedArgumentCount != 3) badFieldCountError();
buffer.add(")");
}
bool operator ==(var other) {
if (other is !MapConstant) return false;
MapConstant otherMap = other;
if (hashCode() != otherMap.hashCode()) return false;
// TODO(floitsch): verify that the generic types are the same.
if (keys != otherMap.keys) return false;
for (int i = 0; i < values.length; i++) {
if (values[i] != otherMap.values[i]) return false;
}
return true;
}
int hashCode() => _hashCode;
List<Constant> getDependencies() {
List<Constant> result = <Constant>[keys];
result.addAll(values);
return result;
}
}
class ConstructedConstant extends ObjectConstant {
final List<Constant> fields;
int _hashCode;
ConstructedConstant(Type type, this.fields) : super(type) {
assert(type !== null);
// TODO(floitsch): create a better hash.
int hash = 0;
for (Constant field in fields) {
hash ^= field.hashCode();
}
hash ^= type.element.hashCode();
_hashCode = hash;
}
bool isConstructedObject() => true;
void writeJsCode(StringBuffer buffer, ConstantHandler handler) {
buffer.add("new ");
buffer.add(handler.getJsConstructor(type.element));
buffer.add("(");
for (int i = 0; i < fields.length; i++) {
if (i != 0) buffer.add(", ");
Constant field = fields[i];
field.writeCanonicalizedJsCode(buffer, handler);
}
buffer.add(")");
}
bool operator ==(var otherVar) {
if (otherVar is !ConstructedConstant) return false;
ConstructedConstant other = otherVar;
if (hashCode() != other.hashCode()) return false;
// TODO(floitsch): verify that the (generic) types are the same.
if (type.element != other.type.element) return false;
if (fields.length != other.fields.length) return false;
for (int i = 0; i < fields.length; i++) {
if (fields[i] != other.fields[i]) return false;
}
return true;
}
int hashCode() => _hashCode;
List<Constant> getDependencies() => fields;
}
/**
* The [ConstantHandler] keeps track of compile-time constants,
* initializations of global and static fields, and default values of
* optional parameters.
*/
class ConstantHandler extends CompilerTask {
// Contains the initial value of fields. Must contain all static and global
// initializations of used fields. May contain caches for instance fields.
final Map<VariableElement, Constant> initialVariableValues;
// Map from compile-time constants to their JS name.
final Map<Constant, String> compiledConstants;
// The set of variable elements that are in the process of being computed.
final Set<VariableElement> pendingVariables;
ConstantHandler(Compiler compiler)
: initialVariableValues = new Map<VariableElement, Dynamic>(),
compiledConstants = new Map<Constant, String>(),
pendingVariables = new Set<VariableElement>(),
super(compiler);
String get name() => 'ConstantHandler';
void registerCompileTimeConstant(Constant constant) {
Function ifAbsentThunk = (() => compiler.namer.getFreshGlobalName("CTC"));
compiledConstants.putIfAbsent(constant, ifAbsentThunk);
}
/**
* Compiles the initial value of the given field and stores it in an internal
* map.
*
* [WorkItem] must contain a [VariableElement] refering to a global or
* static field.
*/
void compileWorkItem(WorkItem work) {
measure(() {
assert(work.element.kind == ElementKind.FIELD
|| work.element.kind == ElementKind.PARAMETER
|| work.element.kind == ElementKind.FIELD_PARAMETER);
VariableElement element = work.element;
// Shortcut if it has already been compiled.
if (initialVariableValues.containsKey(element)) return;
compileVariableWithDefinitions(element, work.resolutionTree);
assert(pendingVariables.isEmpty());
});
}
Constant compileVariable(VariableElement element) {
return measure(() {
if (initialVariableValues.containsKey(element)) {
Constant result = initialVariableValues[element];
return result;
}
TreeElements definitions = compiler.analyzeElement(element);
Constant constant = compileVariableWithDefinitions(element, definitions);
return constant;
});
}
Constant compileVariableWithDefinitions(VariableElement element,
TreeElements definitions) {
return measure(() {
Node node = element.parseNode(compiler);
if (pendingVariables.contains(element)) {
MessageKind kind = MessageKind.CYCLIC_COMPILE_TIME_CONSTANTS;
compiler.reportError(node,
new CompileTimeConstantError(kind, const []));
}
pendingVariables.add(element);
SendSet assignment = node.asSendSet();
Constant value;
if (assignment === null) {
// No initial value.
value = new NullConstant();
} else {
Node right = assignment.arguments.head;
value = compileNodeWithDefinitions(right, definitions);
}
initialVariableValues[element] = value;
pendingVariables.remove(element);
return value;
});
}
Constant compileNodeWithDefinitions(Node node, TreeElements definitions) {
return measure(() {
assert(node !== null);
CompileTimeConstantEvaluator evaluator =
new CompileTimeConstantEvaluator(definitions, compiler);
return evaluator.evaluate(node);
});
}
/**
* Returns a [List] of static non final fields that need to be initialized.
* The list must be evaluated in order since the fields might depend on each
* other.
*/
List<VariableElement> getStaticNonFinalFieldsForEmission() {
return initialVariableValues.getKeys().filter((element) {
return element.kind == ElementKind.FIELD
&& !element.isInstanceMember()
&& !element.modifiers.isFinal();
});
}
/**
* Returns a [List] of static final fields that need to be initialized. The
* list must be evaluated in order since the fields might depend on each
* other.
*/
List<VariableElement> getStaticFinalFieldsForEmission() {
return initialVariableValues.getKeys().filter((element) {
return element.kind == ElementKind.FIELD
&& !element.isInstanceMember()
&& element.modifiers.isFinal();
});
}
List<Constant> getConstantsForEmission() {
// We must emit dependencies before their uses.
Set<Constant> seenConstants = new Set<Constant>();
List<Constant> result = new List<Constant>();
void addConstant(Constant constant) {
if (!seenConstants.contains(constant)) {
constant.getDependencies().forEach(addConstant);
assert(!seenConstants.contains(constant));
result.add(constant);
seenConstants.add(constant);
}
}
compiledConstants.forEach((Constant key, ignored) => addConstant(key));
return result;
}
String getNameForConstant(Constant constant) {
return compiledConstants[constant];
}
StringBuffer writeJsCode(StringBuffer buffer, Constant value) {
value.writeJsCode(buffer, this);
return buffer;
}
StringBuffer writeJsCodeForVariable(StringBuffer buffer,
VariableElement element) {
if (!initialVariableValues.containsKey(element)) {
compiler.internalError("No initial value for given element",
element: element);
}
Constant constant = initialVariableValues[element];
if (constant.isObject()) {
String name = compiledConstants[constant];
buffer.add("${compiler.namer.ISOLATE}.prototype.$name");
} else {
writeJsCode(buffer, constant);
}
return buffer;
}
/**
* Write the contents of the quoted string to a [StringBuffer] in
* a form that is valid as JavaScript string literal content.
* The string is assumed quoted by single quote characters.
*/
static void writeEscapedString(DartString string,
StringBuffer buffer,
void cancel(String reason)) {
Iterator<int> iterator = string.iterator();
while (iterator.hasNext()) {
int code = iterator.next();
if (code === $SQ) {
buffer.add(@"\'");
} else if (code === $LF) {
buffer.add(@'\n');
} else if (code === $CR) {
buffer.add(@'\r');
} else if (code === $LS) {
// This Unicode line terminator and $PS are invalid in JS string
// literals.
buffer.add(@'\u2028');
} else if (code === $PS) {
buffer.add(@'\u2029');
} else if (code === $BACKSLASH) {
buffer.add(@'\\');
} else {
if (code > 0xffff) {
cancel('Unhandled non-BMP character: U+${code.toRadixString(16)}');
}
// TODO(lrn): Consider whether all codes above 0x7f really need to
// be escaped. We build a Dart string here, so it should be a literal
// stage that converts it to, e.g., UTF-8 for a JS interpreter.
if (code < 0x20) {
buffer.add(@'\x');
if (code < 0x10) buffer.add('0');
buffer.add(code.toRadixString(16));
} else if (code >= 0x80) {
if (code < 0x100) {
buffer.add(@'\x');
buffer.add(code.toRadixString(16));
} else {
buffer.add(@'\u');
if (code < 0x1000) {
buffer.add('0');
}
buffer.add(code.toRadixString(16));
}
} else {
buffer.add(new String.fromCharCodes(<int>[code]));
}
}
}
}
String getJsConstructor(ClassElement element) {
return compiler.namer.isolatePropertyAccess(element);
}
}
class CompileTimeConstantEvaluator extends AbstractVisitor {
final TreeElements elements;
final Compiler compiler;
CompileTimeConstantEvaluator(this.elements, this.compiler);
Constant evaluate(Node node) {
return node.accept(this);
}
visitNode(Node node) {
error(node);
}
Constant visitLiteralBool(LiteralBool node) {
return new BoolConstant(node.value);
}
Constant visitLiteralDouble(LiteralDouble node) {
return new DoubleConstant(node.value);
}
Constant visitLiteralInt(LiteralInt node) {
return new IntConstant(node.value);
}
Constant visitLiteralList(LiteralList node) {
if (!node.isConst()) error(node);
List<Constant> arguments = <Constant>[];
for (Link<Node> link = node.elements.nodes;
!link.isEmpty();
link = link.tail) {
arguments.add(evaluate(link.head));
}
// TODO(floitsch): get type from somewhere.
Type type = null;
Constant constant = new ListConstant(type, arguments);
compiler.constantHandler.registerCompileTimeConstant(constant);
return constant;
}
Constant visitLiteralMap(LiteralMap node) {
// TODO(floitsch): check for isConst, once the parser adds it into the node.
// if (!node.isConst()) error(node);
List<StringConstant> keys = <StringConstant>[];
List<Constant> values = <Constant>[];
bool hasProtoKey = false;
for (Link<Node> link = node.entries.nodes;
!link.isEmpty();
link = link.tail) {
LiteralMapEntry entry = link.head;
Constant key = evaluate(entry.key);
if (!key.isString() || entry.key.asLiteralString() === null) {
MessageKind kind = MessageKind.KEY_NOT_A_STRING_LITERAL;
compiler.reportError(entry.key, new ResolutionError(kind, const []));
}
// TODO(floitsch): make this faster.
StringConstant keyConstant = key;
if (keyConstant.value == new LiteralDartString("__proto__")) {
hasProtoKey = true;
}
keys.add(key);
values.add(evaluate(entry.value));
}
if (hasProtoKey) {
compiler.unimplemented("visitLiteralMap with __proto__ key",
node: node);
}
// TODO(floitsch): this should be a List<String> type.
Type keysType = null;
ListConstant keysList = new ListConstant(keysType, keys);
compiler.constantHandler.registerCompileTimeConstant(keysList);
ClassElement classElement =
compiler.jsHelperLibrary.find(MapConstant.DART_CLASS);
classElement.ensureResolved(compiler);
// TODO(floitsch): copy over the generic type.
Type type = new SimpleType(classElement.name, classElement);
compiler.registerInstantiatedClass(classElement);
Constant constant = new MapConstant(type, keysList, values);
compiler.constantHandler.registerCompileTimeConstant(constant);
return constant;
}
Constant visitLiteralNull(LiteralNull node) {
return new NullConstant();
}
Constant visitLiteralString(LiteralString node) {
return new StringConstant(node.dartString);
}
Constant visitStringJuxtaposition(StringJuxtaposition node) {
StringConstant left = evaluate(node.first);
StringConstant right = evaluate(node.second);
return new StringConstant(new DartString.concat(left.value, right.value));
}
Constant visitStringInterpolation(StringInterpolation node) {
StringConstant initialString = evaluate(node.string);
DartString accumulator = initialString.value;
for (StringInterpolationPart part in node.parts) {
Constant expression = evaluate(part.expression);
DartString expressionString;
if (expression.isNum() || expression.isBool()) {
Object value = expression.value;
expressionString = new DartString.literal(value.toString());
} else if (expression.isString()) {
expressionString = expression.value;
} else {
error(part.expression);
}
accumulator = new DartString.concat(accumulator, expressionString);
StringConstant partString = evaluate(part.string);
accumulator = new DartString.concat(accumulator, partString.value);
};
return new StringConstant(accumulator);
}
// TODO(floitsch): provide better error-messages.
Constant visitSend(Send send) {
Element element = elements[send];
if (Elements.isStaticOrTopLevelField(element)) {
if (element.modifiers === null ||
!element.modifiers.isFinal()) {
error(send);
}
return compiler.compileVariable(element);
} else if (send.isPrefix) {
assert(send.isOperator);
Constant receiverConstant = evaluate(send.receiver);
Operator op = send.selector;
Constant folded;
switch (op.source.stringValue) {
case "!":
folded = const NotOperation().fold(receiverConstant);
break;
case "-":
folded = const NegateOperation().fold(receiverConstant);
break;
case "~":
folded = const BitNotOperation().fold(receiverConstant);
break;
default:
compiler.internalError("Unexpected operator.", node: op);
break;
}
if (folded === null) error(send);
return folded;
} else if (send.isOperator && !send.isPostfix) {
assert(send.argumentCount() == 1);
Constant left = evaluate(send.receiver);
Constant right = evaluate(send.argumentsNode.nodes.head);
Operator op = send.selector.asOperator();
Constant folded;
switch (op.source.stringValue) {
case "+":
if (left.isString() && !right.isString()) {
// At the moment only compile-time concatenation of two strings is
// allowed.
error(send);
}
folded = const AddOperation().fold(left, right);
break;
case "-":
folded = const SubtractOperation().fold(left, right);
break;
case "*":
folded = const MultiplyOperation().fold(left, right);
break;
case "/":
folded = const DivideOperation().fold(left, right);
break;
case "%":
folded = const ModuloOperation().fold(left, right);
break;
case "~/":
folded = const TruncatingDivideOperation().fold(left, right);
break;
case "|":
folded = const BitOrOperation().fold(left, right);
break;
case "&":
folded = const BitAndOperation().fold(left, right);
break;
case "^":
folded = const BitXorOperation().fold(left, right);
break;
case "||":
folded = const BooleanOr().fold(left, right);
break;
case "&&":
folded = const BooleanAnd().fold(left, right);
break;
case "<<":
folded = const ShiftLeftOperation().fold(left, right);
break;
case ">>":
folded = const ShiftRightOperation().fold(left, right);
break;
case "<":
folded = const LessOperation().fold(left, right);
break;
case "<=":
folded = const LessEqualOperation().fold(left, right);
break;
case ">":
folded = const GreaterOperation().fold(left, right);
break;
case ">=":
folded = const GreaterEqualOperation().fold(left, right);
break;
case "==":
if (left.isPrimitive() && right.isPrimitive()) {
folded = const EqualsOperation().fold(left, right);
}
break;
case "===":
if (left.isPrimitive() && right.isPrimitive()) {
folded = const IdentityOperation().fold(left, right);
}
break;
case "!=":
if (left.isPrimitive() && right.isPrimitive()) {
BoolConstant areEquals = const EqualsOperation().fold(left, right);
if (areEquals === null) {
folded = null;
} else {
folded = areEquals.negate();
}
}
break;
case "!==":
if (left.isPrimitive() && right.isPrimitive()) {
BoolConstant areIdentical =
const IdentityOperation().fold(left, right);
if (areIdentical === null) {
folded = null;
} else {
folded = areIdentical.negate();
}
}
break;
default:
compiler.internalError("Unexpected operator.", node: op);
break;
}
if (folded === null) error(send);
return folded;
}
return super.visitSend(send);
}
visitSendSet(SendSet node) {
error(node);
}
/** Returns the list of constants that are passed to the static function. */
List<Constant> evaluateArgumentsToConstructor(Send send,
FunctionElement target) {
FunctionParameters parameters = target.computeParameters(compiler);
List<Constant> arguments = <Constant>[];
Selector selector = elements.getSelector(send);
Function compileArgument = evaluate;
Function compileConstant = compiler.compileVariable;
bool succeeded = selector.addSendArgumentsToList(
send, arguments, parameters, compileArgument, compileConstant);
if (!succeeded) error(send);
return arguments;
}
Constant visitNewExpression(NewExpression node) {
if (!node.isConst()) error(node);
FunctionElement constructor = elements[node.send];
ClassElement classElement = constructor.enclosingElement;
if (classElement.isInterface()) {
compiler.resolver.resolveMethodElement(constructor);
constructor = constructor.defaultImplementation;
classElement = constructor.enclosingElement;
}
List<Constant> arguments =
evaluateArgumentsToConstructor(node.send, constructor);
ConstructorEvaluator evaluator =
new ConstructorEvaluator(constructor, compiler);
evaluator.evaluateConstructorFieldValues(arguments);
List<Constant>jsNewArguments = evaluator.buildJsNewArguments(classElement);
compiler.registerInstantiatedClass(classElement);
// TODO(floitsch): take generic types into account.
Type type = classElement.computeType(compiler);
Constant constant = new ConstructedConstant(type, jsNewArguments);
compiler.constantHandler.registerCompileTimeConstant(constant);
return constant;
}
Constant visitParenthesizedExpression(ParenthesizedExpression node) {
return node.expression.accept(this);
}
error(Node node) {
// TODO(floitsch): get the list of constants that are currently compiled
// and present some kind of stack-trace.
MessageKind kind = MessageKind.NOT_A_COMPILE_TIME_CONSTANT;
compiler.reportError(node, new CompileTimeConstantError(kind, const []));
}
}
class ConstructorEvaluator extends CompileTimeConstantEvaluator {
FunctionElement constructor;
final Map<Element, Constant> definitions;
final Map<Element, Constant> fieldValues;
ConstructorEvaluator(FunctionElement constructor, Compiler compiler)
: this.constructor = constructor,
this.definitions = new Map<Element, Constant>(),
this.fieldValues = new Map<Element, Constant>(),
super(compiler.resolver.resolveMethodElement(constructor),
compiler);
Constant visitSend(Send send) {
Element element = elements[send];
if (Elements.isLocal(element)) {
Constant constant = definitions[element];
if (constant === null) {
compiler.internalError("Local variable without value", node: send);
}
return constant;
}
return super.visitSend(send);
}
/**
* Given the arguments (a list of constants) assigns them to the parameters,
* updating the definitions map. If the constructor has field-initializer
* parameters (like [:this.x:]), also updates the [fieldValues] map.
*/
void assignArgumentsToParameters(List<Constant> arguments) {
// Assign arguments to parameters.
FunctionParameters parameters = constructor.computeParameters(compiler);
int index = 0;
parameters.forEachParameter((Element parameter) {
Constant argument = arguments[index++];
definitions[parameter] = argument;
if (parameter.kind == ElementKind.FIELD_PARAMETER) {
FieldParameterElement fieldParameterElement = parameter;
fieldValues[fieldParameterElement.fieldElement] = argument;
}
});
}
void evaluateSuperOrRedirectSend(FunctionElement targetConstructor,
List<Constant> targetArguments) {
ConstructorEvaluator evaluator =
new ConstructorEvaluator(targetConstructor, compiler);
evaluator.evaluateConstructorFieldValues(targetArguments);
// Copy over the fieldValues from the super/redirect-constructor.
evaluator.fieldValues.forEach((key, value) => fieldValues[key] = value);
}
/**
* Runs through the initializers of the given [constructor] and updates
* the [fieldValues] map.
*/
void evaluateConstructorInitializers() {
FunctionExpression functionNode = constructor.parseNode(compiler);
NodeList initializerList = functionNode.initializers;
bool foundSuperOrRedirect = false;
if (initializerList !== null) {
for (Link<Node> link = initializerList.nodes;
!link.isEmpty();
link = link.tail) {
assert(link.head is Send);
if (link.head is !SendSet) {
// A super initializer or constructor redirection.
Send call = link.head;
FunctionElement targetConstructor = elements[call];
List<Constant> targetArguments =
evaluateArgumentsToConstructor(call, targetConstructor);
evaluateSuperOrRedirectSend(targetConstructor, targetArguments);
foundSuperOrRedirect = true;
} else {
// A field initializer.
SendSet init = link.head;
Link<Node> initArguments = init.arguments;
assert(!initArguments.isEmpty() && initArguments.tail.isEmpty());
Constant fieldValue = evaluate(initArguments.head);
fieldValues[elements[init]] = fieldValue;
}
}
}
if (!foundSuperOrRedirect) {
// No super initializer found. Try to find the default constructor if
// the class is not Object.
ClassElement enclosingClass = constructor.enclosingElement;
ClassElement superClass = enclosingClass.superclass;
if (enclosingClass != compiler.objectClass) {
assert(superClass !== null);
assert(superClass.isResolved);
FunctionElement targetConstructor =
superClass.lookupConstructor(superClass.name);
if (targetConstructor === null) {
compiler.internalError("no default constructor available");
}
evaluateSuperOrRedirectSend(targetConstructor, const <Constant>[]);
}
}
}
/**
* Simulates the execution of the [constructor] with the given
* [arguments] to obtain the field values that need to be passed to the
* native JavaScript constructor.
*/
void evaluateConstructorFieldValues(List<Constant> arguments) {
compiler.withCurrentElement(constructor, () {
assignArgumentsToParameters(arguments);
evaluateConstructorInitializers();
});
}
List<Constant> buildJsNewArguments(ClassElement classElement) {
List<Constant> jsNewArguments = <Constant>[];
// TODO(floitsch): share this code with the emitter, so that we don't
// need to care about the order of fields here.
while (classElement != compiler.objectClass) {
for (Element member in classElement.members) {
if (member.isInstanceMember() && member.kind == ElementKind.FIELD) {
Constant fieldValue = fieldValues[member];
if (fieldValue === null) {
// Use the default value.
fieldValue = compiler.compileVariable(member);
}
jsNewArguments.add(fieldValue);
}
}
classElement = classElement.superclass;
}
return jsNewArguments;
}
}