blob: 84ae345484981bf23eee8b8e769f4563bb9a1555 [file] [log] [blame]
// Copyright (c) 2017, 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.
/// This library implements a kernel2kernel constant evaluation transformation.
///
/// Even though it is expected that the frontend does not emit kernel AST which
/// contains compile-time errors, this transformation still performs some
/// valiation and throws a [ConstantEvaluationError] if there was a compile-time
/// errors.
///
/// Due to the lack information which is is only available in the front-end,
/// this validation is incomplete (e.g. whether an integer literal used the
/// hexadecimal syntax or not).
///
/// Furthermore due to the lowering of certain constructs in the front-end
/// (e.g. '??') we need to support a super-set of the normal constant expression
/// language. Issue(http://dartbug.com/31799)
library kernel.transformations.constants;
import '../kernel.dart';
import '../ast.dart';
import '../core_types.dart';
import '../type_algebra.dart';
import '../type_environment.dart';
import '../class_hierarchy.dart';
import 'treeshaker.dart' show findNativeName;
Component transformComponent(Component component, ConstantsBackend backend,
{bool keepFields: false,
bool strongMode: false,
bool enableAsserts: false,
CoreTypes coreTypes,
ClassHierarchy hierarchy}) {
coreTypes ??= new CoreTypes(component);
hierarchy ??= new ClassHierarchy(component);
final typeEnvironment =
new TypeEnvironment(coreTypes, hierarchy, strongMode: strongMode);
transformLibraries(component.libraries, backend, coreTypes, typeEnvironment,
keepFields: keepFields,
strongMode: strongMode,
enableAsserts: enableAsserts);
return component;
}
void transformLibraries(List<Library> libraries, ConstantsBackend backend,
CoreTypes coreTypes, TypeEnvironment typeEnvironment,
{bool keepFields: false,
bool keepVariables: false,
bool strongMode: false,
bool enableAsserts: false}) {
final ConstantsTransformer constantsTransformer = new ConstantsTransformer(
backend,
keepFields,
keepVariables,
coreTypes,
typeEnvironment,
strongMode,
enableAsserts);
for (final Library library in libraries) {
for (final Field field in library.fields.toList()) {
constantsTransformer.convertField(field);
}
for (final Procedure procedure in library.procedures) {
constantsTransformer.convertProcedure(procedure);
}
for (final Class klass in library.classes) {
constantsTransformer.convertClassAnnotations(klass);
for (final Field field in klass.fields.toList()) {
constantsTransformer.convertField(field);
}
for (final Procedure procedure in klass.procedures) {
constantsTransformer.convertProcedure(procedure);
}
for (final Constructor constructor in klass.constructors) {
constantsTransformer.convertConstructor(constructor);
}
}
for (final Typedef td in library.typedefs) {
constantsTransformer.convertTypedef(td);
}
if (!keepFields) {
// The transformer API does not iterate over `Library.additionalExports`,
// so we manually delete the references to shaken nodes.
library.additionalExports.removeWhere((Reference reference) {
return reference.canonicalName == null;
});
}
}
}
class ConstantsTransformer extends Transformer {
final ConstantEvaluator constantEvaluator;
final CoreTypes coreTypes;
final TypeEnvironment typeEnvironment;
/// Whether to preserve constant [Field]s. All use-sites will be rewritten.
final bool keepFields;
final bool keepVariables;
ConstantsTransformer(
ConstantsBackend backend,
this.keepFields,
this.keepVariables,
this.coreTypes,
this.typeEnvironment,
bool strongMode,
bool enableAsserts)
: constantEvaluator = new ConstantEvaluator(
backend, typeEnvironment, coreTypes, strongMode, enableAsserts);
// Transform the library/class members:
void convertClassAnnotations(Class klass) {
constantEvaluator.withNewEnvironment(() {
transformList(klass.annotations, this, klass);
});
}
void convertProcedure(Procedure procedure) {
constantEvaluator.withNewEnvironment(() {
procedure.accept(this);
});
}
void convertConstructor(Constructor constructor) {
constantEvaluator.withNewEnvironment(() {
constructor.accept(this);
});
}
void convertField(Field field) {
constantEvaluator.withNewEnvironment(() {
if (field.accept(this) == null) field.remove();
});
}
void convertTypedef(Typedef td) {
// A typedef can have annotations on variables which are constants.
constantEvaluator.withNewEnvironment(() {
td.accept(this);
});
}
// Handle definition of constants:
visitVariableDeclaration(VariableDeclaration node) {
if (node.isConst) {
final Constant constant = constantEvaluator.evaluate(node.initializer);
constantEvaluator.env.addVariableValue(node, constant);
if (keepVariables) {
// So the value of the variable is still available for debugging
// purposes we convert the constant variable to be a final variable
// initialized to the evaluated constant expression.
node.initializer = new ConstantExpression(constant)..parent = node;
node.isFinal = true;
node.isConst = false;
} else {
// Since we convert all use-sites of constants, the constant
// [VariableDeclaration] is unused and we'll therefore remove it.
return null;
}
}
return super.visitVariableDeclaration(node);
}
visitField(Field node) {
if (node.isConst) {
// Since we convert all use-sites of constants, the constant [Field]
// cannot be referenced anymore. We therefore get rid of it if
// [keepFields] was not specified.
if (!keepFields) return null;
// Otherwise we keep the constant [Field] and convert it's initializer.
if (node.initializer != null) {
final Constant constant = constantEvaluator.evaluate(node.initializer);
node.initializer = new ConstantExpression(constant)..parent = node;
}
return node;
}
return super.visitField(node);
}
// Handle use-sites of constants (and "inline" constant expressions):
visitStaticGet(StaticGet node) {
final Member target = node.target;
if (target is Field && target.isConst) {
final Constant constant = constantEvaluator.evaluate(target.initializer);
return new ConstantExpression(constant);
} else if (target is Procedure && target.kind == ProcedureKind.Method) {
final Constant constant = constantEvaluator.evaluate(node);
return new ConstantExpression(constant);
}
return super.visitStaticGet(node);
}
visitVariableGet(VariableGet node) {
if (node.variable.isConst) {
final Constant constant =
constantEvaluator.evaluate(node.variable.initializer);
return new ConstantExpression(constant);
}
return super.visitVariableGet(node);
}
visitListLiteral(ListLiteral node) {
if (node.isConst) {
return new ConstantExpression(constantEvaluator.evaluate(node));
}
return super.visitListLiteral(node);
}
visitMapLiteral(MapLiteral node) {
if (node.isConst) {
return new ConstantExpression(constantEvaluator.evaluate(node));
}
return super.visitMapLiteral(node);
}
visitConstructorInvocation(ConstructorInvocation node) {
if (node.isConst) {
return new ConstantExpression(constantEvaluator.evaluate(node));
}
return super.visitConstructorInvocation(node);
}
}
class ConstantEvaluator extends RecursiveVisitor {
final ConstantsBackend backend;
final CoreTypes coreTypes;
final TypeEnvironment typeEnvironment;
final bool strongMode;
final bool enableAsserts;
final Map<Constant, Constant> canonicalizationCache;
final Map<Node, Constant> nodeCache;
final NullConstant nullConstant = new NullConstant();
final BoolConstant trueConstant = new BoolConstant(true);
final BoolConstant falseConstant = new BoolConstant(false);
InstanceBuilder instanceBuilder;
EvaluationEnvironment env;
ConstantEvaluator(this.backend, this.typeEnvironment, this.coreTypes,
this.strongMode, this.enableAsserts)
: canonicalizationCache = <Constant, Constant>{},
nodeCache = <Node, Constant>{};
/// Evaluates [node] and possibly cache the evaluation result.
Constant evaluate(Expression node) {
if (node == null) return nullConstant;
if (env.isEmpty) {
return nodeCache.putIfAbsent(node, () => node.accept(this));
}
return node.accept(this);
}
defaultTreeNode(Node node) {
// Only a subset of the expression language is valid for constant
// evaluation.
throw new ConstantEvaluationError(
'Constant evaluation has no support for ${node.runtimeType} yet!');
}
visitNullLiteral(NullLiteral node) => nullConstant;
visitBoolLiteral(BoolLiteral node) {
return node.value ? trueConstant : falseConstant;
}
visitIntLiteral(IntLiteral node) {
// The frontend will ensure the integer literals are in signed 64-bit range
// in strong mode.
return canonicalize(new IntConstant(node.value));
}
visitDoubleLiteral(DoubleLiteral node) {
return canonicalize(new DoubleConstant(node.value));
}
visitStringLiteral(StringLiteral node) {
return canonicalize(new StringConstant(node.value));
}
visitTypeLiteral(TypeLiteral node) {
final DartType type = evaluateDartType(node.type);
return canonicalize(new TypeLiteralConstant(type));
}
visitConstantExpression(ConstantExpression node) {
// If there were already constants in the AST then we make sure we
// re-canonicalize them. After running the transformer we will therefore
// have a fully-canonicalized constant DAG with roots coming from the
// [ConstantExpression] nodes in the AST.
return canonicalize(node.constant);
}
visitListLiteral(ListLiteral node) {
final List<Constant> entries = new List<Constant>(node.expressions.length);
for (int i = 0; i < node.expressions.length; ++i) {
entries[i] = node.expressions[i].accept(this);
}
final DartType typeArgument = evaluateDartType(node.typeArgument);
final ListConstant listConstant = new ListConstant(typeArgument, entries);
return canonicalize(backend.lowerListConstant(listConstant));
}
visitMapLiteral(MapLiteral node) {
final Set<Constant> usedKeys = new Set<Constant>();
final List<ConstantMapEntry> entries =
new List<ConstantMapEntry>(node.entries.length);
for (int i = 0; i < node.entries.length; ++i) {
final key = node.entries[i].key.accept(this);
final value = node.entries[i].value.accept(this);
if (!usedKeys.add(key)) {
throw new ConstantEvaluationError(
'Duplicate key "$key" in constant map literal.');
}
entries[i] = new ConstantMapEntry(key, value);
}
final DartType keyType = evaluateDartType(node.keyType);
final DartType valueType = evaluateDartType(node.valueType);
final MapConstant mapConstant =
new MapConstant(keyType, valueType, entries);
return canonicalize(backend.lowerMapConstant(mapConstant));
}
visitConstructorInvocation(ConstructorInvocation node) {
final Constructor constructor = node.target;
final Class klass = constructor.enclosingClass;
final typeArguments = evaluateTypeArguments(node.arguments);
final positionals = evaluatePositionalArguments(node.arguments);
final named = evaluateNamedArguments(node.arguments);
// Fill in any missing type arguments with "dynamic".
for (int i = typeArguments.length; i < klass.typeParameters.length; i++) {
typeArguments.add(const DynamicType());
}
// Start building a new instance.
return withNewInstanceBuilder(klass, typeArguments, () {
// "Run" the constructor (and any super constructor calls), which will
// initialize the fields of the new instance.
handleConstructorInvocation(
constructor, typeArguments, positionals, named);
return canonicalize(instanceBuilder.buildInstance());
});
}
void handleConstructorInvocation(
Constructor constructor,
List<DartType> typeArguments,
List<Constant> positionalArguments,
Map<String, Constant> namedArguments) {
return withNewEnvironment(() {
final Class klass = constructor.enclosingClass;
final FunctionNode function = constructor.function;
// We simulate now the constructor invocation.
// Step 1) Map type arguments and normal arguments from caller to callee.
for (int i = 0; i < klass.typeParameters.length; i++) {
env.addTypeParameterValue(klass.typeParameters[i], typeArguments[i]);
}
for (int i = 0; i < function.positionalParameters.length; i++) {
final VariableDeclaration parameter = function.positionalParameters[i];
final Constant value = (i < positionalArguments.length)
? positionalArguments[i]
: evaluate(parameter.initializer);
env.addVariableValue(parameter, value);
}
for (final VariableDeclaration parameter in function.namedParameters) {
final Constant value =
namedArguments[parameter.name] ?? evaluate(parameter.initializer);
env.addVariableValue(parameter, value);
}
// Step 2) Run all initializers (including super calls) with environment setup.
for (final Field field in klass.fields) {
if (!field.isStatic) {
instanceBuilder.setFieldValue(field, evaluate(field.initializer));
}
}
for (final Initializer init in constructor.initializers) {
if (init is FieldInitializer) {
instanceBuilder.setFieldValue(init.field, evaluate(init.value));
} else if (init is LocalInitializer) {
final VariableDeclaration variable = init.variable;
env.addVariableValue(variable, evaluate(variable.initializer));
} else if (init is SuperInitializer) {
handleConstructorInvocation(
init.target,
evaluateSuperTypeArguments(constructor.enclosingClass.supertype),
evaluatePositionalArguments(init.arguments),
evaluateNamedArguments(init.arguments));
} else if (init is RedirectingInitializer) {
// Since a redirecting constructor targets a constructor of the same
// class, we pass the same [typeArguments].
handleConstructorInvocation(
init.target,
typeArguments,
evaluatePositionalArguments(init.arguments),
evaluateNamedArguments(init.arguments));
} else if (init is AssertInitializer) {
if (enableAsserts) {
final Constant condition = init.statement.condition.accept(this);
if (condition is BoolConstant) {
if (!condition.value) {
final Constant message = init.statement.message?.accept(this);
throw new ConstantEvaluationError(
'Assert initializer condition failed with message: $message.');
}
} else {
throw new ConstantEvaluationError(
'Assert initializer did not evaluate to a boolean condition.');
}
}
} else {
throw new ConstantEvaluationError(
'Cannot evaluate constant with [${init.runtimeType}].');
}
}
});
}
visitMethodInvocation(MethodInvocation node) {
// We have no support for generic method invocation atm.
assert(node.arguments.named.isEmpty);
final Constant receiver = evaluate(node.receiver);
final List<Constant> arguments =
evaluatePositionalArguments(node.arguments);
// Handle == and != first (it's common between all types).
if (arguments.length == 1 && node.name.name == '==') {
// TODO(http://dartbug.com/31799): Re-enable these checks.
//ensurePrimitiveConstant(receiver);
final right = arguments[0];
// TODO(http://dartbug.com/31799): Re-enable these checks.
//ensurePrimitiveConstant(right);
return receiver == right ? trueConstant : falseConstant;
}
if (arguments.length == 1 && node.name.name == '!=') {
// TODO(http://dartbug.com/31799): Re-enable these checks.
//ensurePrimitiveConstant(receiver);
final right = arguments[0];
// TODO(http://dartbug.com/31799): Re-enable these checks.
//ensurePrimitiveConstant(right);
return receiver != right ? trueConstant : falseConstant;
}
// This is a white-listed set of methods we need to support on constants.
if (receiver is StringConstant) {
if (arguments.length == 1) {
switch (node.name.name) {
case '+':
final StringConstant other = arguments[0];
return canonicalize(
new StringConstant(receiver.value + other.value));
}
}
} else if (receiver is BoolConstant) {
if (arguments.length == 1) {
switch (node.name.name) {
case '!':
return !receiver.value ? trueConstant : falseConstant;
}
} else if (arguments.length == 2) {
final right = arguments[0];
if (right is BoolConstant) {
switch (node.name.name) {
case '&&':
return (receiver.value && right.value)
? trueConstant
: falseConstant;
case '||':
return (receiver.value || right.value)
? trueConstant
: falseConstant;
}
}
throw new ConstantEvaluationError(
'Method "${node.name}" is only allowed with boolean arguments.');
}
} else if (receiver is IntConstant) {
if (arguments.length == 0) {
switch (node.name.name) {
case 'unary-':
return canonicalize(new IntConstant(-receiver.value));
case '~':
return canonicalize(new IntConstant(~receiver.value));
}
} else if (arguments.length == 1) {
final Constant other = arguments[0];
if (other is IntConstant) {
switch (node.name.name) {
case '|':
return canonicalize(
new IntConstant(receiver.value | other.value));
case '&':
return canonicalize(
new IntConstant(receiver.value & other.value));
case '^':
return canonicalize(
new IntConstant(receiver.value ^ other.value));
case '<<':
return canonicalize(
new IntConstant(receiver.value << other.value));
case '>>':
return canonicalize(
new IntConstant(receiver.value >> other.value));
}
}
if (other is IntConstant || other is DoubleConstant) {
final num value = (other is IntConstant)
? other.value
: (other as DoubleConstant).value;
return evaluateBinaryNumericOperation(
node.name.name, receiver.value, value);
}
}
} else if (receiver is DoubleConstant) {
if (arguments.length == 0) {
switch (node.name.name) {
case 'unary-':
return canonicalize(new DoubleConstant(-receiver.value));
}
} else if (arguments.length == 1) {
final Constant other = arguments[0];
if (other is IntConstant || other is DoubleConstant) {
final num value = (other is IntConstant)
? other.value
: (other as DoubleConstant).value;
return evaluateBinaryNumericOperation(
node.name.name, receiver.value, value);
}
}
}
throw new ConstantEvaluationError(
'Cannot evaluate general method invocation: '
'receiver: $receiver, method: ${node.name}, arguments: $arguments!');
}
visitLogicalExpression(LogicalExpression node) {
final Constant left = evaluate(node.left);
switch (node.operator) {
case '||':
if (left is BoolConstant) {
if (left.value) return trueConstant;
final Constant right = evaluate(node.right);
if (right is BoolConstant) {
return right;
}
throw new ConstantEvaluationError(
'"$right" is not bool constant and is disallowed with "||".');
}
throw new ConstantEvaluationError(
'"$left" is not bool constant and is disallowed with "||".');
case '&&':
if (left is BoolConstant) {
if (!left.value) return falseConstant;
final Constant right = evaluate(node.right);
if (right is BoolConstant) {
return right;
}
throw new ConstantEvaluationError(
'"$right" is not bool constant and is disallowed with "&&".');
}
throw new ConstantEvaluationError(
'"$left" is not bool constant and is disallowed with "&&".');
case '??':
return (left is! NullConstant) ? left : evaluate(node.right);
default:
throw new ConstantEvaluationError(
'No support for logical operator ${node.operator}.');
}
}
visitConditionalExpression(ConditionalExpression node) {
final Constant constant = evaluate(node.condition);
if (constant == trueConstant) {
return evaluate(node.then);
} else if (constant == falseConstant) {
return evaluate(node.otherwise);
} else {
throw new ConstantEvaluationError(
'Cannot use $constant as condition in a conditional expression.');
}
}
visitPropertyGet(PropertyGet node) {
if (node.receiver is ThisExpression) {
// Access "this" during instance creation.
for (final Field field in instanceBuilder.fields.keys) {
if (field.name == node.name) {
return instanceBuilder.fields[field];
}
}
throw 'Could not evaluate field get ${node.name} on incomplete instance';
}
final Constant receiver = evaluate(node.receiver);
if (receiver is StringConstant && node.name.name == 'length') {
return canonicalize(new IntConstant(receiver.value.length));
} else if (receiver is InstanceConstant) {
for (final Reference fieldRef in receiver.fieldValues.keys) {
if (fieldRef.asField.name == node.name) {
return receiver.fieldValues[fieldRef];
}
}
}
throw 'Could not evaluate property get on $receiver.';
}
visitLet(Let node) {
env.addVariableValue(node.variable, evaluate(node.variable.initializer));
return node.body.accept(this);
}
visitVariableGet(VariableGet node) {
return env.lookupVariable(node.variable);
}
visitStaticGet(StaticGet node) {
return withNewEnvironment(() {
final Member target = node.target;
if (target is Field && target.isConst) {
return evaluate(target.initializer);
} else if (target is Procedure) {
return canonicalize(new TearOffConstant(target));
}
throw 'Could not handle static get of $target.';
});
}
visitStringConcatenation(StringConcatenation node) {
final String value = node.expressions.map((Expression node) {
final Constant constant = node.accept(this);
if (constant is NullConstant) {
return 'null';
} else if (constant is BoolConstant) {
return constant.value ? 'true' : 'false';
} else if (constant is IntConstant) {
return constant.value.toString();
} else if (constant is DoubleConstant) {
return constant.value.toString();
} else if (constant is StringConstant) {
return constant.value;
} else {
throw new ConstantEvaluationError(
'Only null/bool/int/double/String values are allowed as string '
'interpolation expressions during constant evaluation.');
}
}).join('');
return canonicalize(new StringConstant(value));
}
visitStaticInvocation(StaticInvocation node) {
final Member target = node.target;
if (target is Procedure) {
if (target.kind == ProcedureKind.Factory) {
final String nativeName = findNativeName(target);
if (nativeName != null) {
final Constant constant = backend.buildConstantForNative(
nativeName,
evaluateTypeArguments(node.arguments),
evaluatePositionalArguments(node.arguments),
evaluateNamedArguments(node.arguments));
assert(constant != null);
return canonicalize(constant);
}
} else if (target.name.name == 'identical') {
// Ensure the "identical()" function comes from dart:core.
final parent = target.parent;
if (parent is Library && parent == coreTypes.coreLibrary) {
final positionalArguments =
evaluatePositionalArguments(node.arguments);
final Constant left = positionalArguments[0];
// TODO(http://dartbug.com/31799): Re-enable these checks.
//ensurePrimitiveConstant(left);
final Constant right = positionalArguments[1];
// TODO(http://dartbug.com/31799): Re-enable these checks.
//ensurePrimitiveConstant(right);
// Since we canonicalize constants during the evaluation, we can use
// identical here.
assert(left == right);
return identical(left, right) ? trueConstant : falseConstant;
}
}
}
throw new ConstantEvaluationError(
'Calling "$target" during constant evaluation is disallowed.');
}
visitAsExpression(AsExpression node) {
final Constant constant = node.operand.accept(this);
ensureIsSubtype(constant, evaluateDartType(node.type));
return constant;
}
visitNot(Not node) {
final Constant constant = node.operand.accept(this);
if (constant is BoolConstant) {
return constant == trueConstant ? falseConstant : trueConstant;
}
throw new ConstantEvaluationError(
'A not expression must have a boolean operand.');
}
visitSymbolLiteral(SymbolLiteral node) {
final value = canonicalize(new StringConstant(node.value));
return canonicalize(backend.buildSymbolConstant(value));
}
// Helper methods:
void ensureIsSubtype(Constant constant, DartType type) {
DartType constantType;
if (constant is NullConstant) {
constantType = new InterfaceType(coreTypes.nullClass);
} else if (constant is BoolConstant) {
constantType = new InterfaceType(coreTypes.boolClass);
} else if (constant is IntConstant) {
constantType = new InterfaceType(coreTypes.intClass);
} else if (constant is DoubleConstant) {
constantType = new InterfaceType(coreTypes.doubleClass);
} else if (constant is StringConstant) {
constantType = new InterfaceType(coreTypes.stringClass);
} else if (constant is MapConstant) {
constantType = new InterfaceType(
coreTypes.mapClass, <DartType>[constant.keyType, constant.valueType]);
} else if (constant is ListConstant) {
constantType = new InterfaceType(
coreTypes.stringClass, <DartType>[constant.typeArgument]);
} else if (constant is InstanceConstant) {
constantType = new InterfaceType(constant.klass, constant.typeArguments);
} else if (constant is TearOffConstant) {
constantType = constant.procedure.function.functionType;
} else if (constant is TypeLiteralConstant) {
constantType = new InterfaceType(coreTypes.typeClass);
} else {
throw new ConstantEvaluationError(
'No support for obtaining the type of $constant.');
}
if (!typeEnvironment.isSubtypeOf(constantType, type)) {
throw new ConstantEvaluationError(
'Constant $constant is not a subtype of ${type}.');
}
}
List<DartType> evaluateTypeArguments(Arguments arguments) {
return evaluateDartTypes(arguments.types);
}
List<DartType> evaluateSuperTypeArguments(Supertype type) {
return evaluateDartTypes(type.typeArguments);
}
List<DartType> evaluateDartTypes(List<DartType> types) {
if (env.isEmpty) return types;
return types.map(evaluateDartType).toList();
}
DartType evaluateDartType(DartType type) {
return env.subsituteType(type);
}
List<Constant> evaluatePositionalArguments(Arguments arguments) {
return arguments.positional.map((Expression node) {
return node.accept(this);
}).toList();
}
Map<String, Constant> evaluateNamedArguments(Arguments arguments) {
if (arguments.named.isEmpty) return const <String, Constant>{};
final Map<String, Constant> named = {};
arguments.named.forEach((NamedExpression pair) {
named[pair.name] = pair.value.accept(this);
});
return named;
}
canonicalize(Constant constant) {
return canonicalizationCache.putIfAbsent(constant, () => constant);
}
withNewInstanceBuilder(Class klass, List<DartType> typeArguments, fn()) {
InstanceBuilder old = instanceBuilder;
instanceBuilder = new InstanceBuilder(klass, typeArguments);
final result = fn();
instanceBuilder = old;
return result;
}
withNewEnvironment(fn()) {
final EvaluationEnvironment oldEnv = env;
env = new EvaluationEnvironment();
final result = fn();
env = oldEnv;
return result;
}
ensurePrimitiveConstant(Constant value) {
if (value is! NullConstant &&
value is! BoolConstant &&
value is! IntConstant &&
value is! DoubleConstant &&
value is! StringConstant) {
throw new ConstantEvaluationError(
'"$value" is not a primitive constant (null/bool/int/double/string) '
' and is disallowed in this context.');
}
}
evaluateBinaryNumericOperation(String op, num a, num b) {
num result;
switch (op) {
case '+':
result = a + b;
break;
case '-':
result = a - b;
break;
case '*':
result = a * b;
break;
case '/':
result = a / b;
break;
case '~/':
result = a ~/ b;
break;
case '%':
result = a % b;
break;
}
if (result != null) {
return canonicalize(result is int
? new IntConstant(_wrapAroundInteger(result))
: new DoubleConstant(result as double));
}
switch (op) {
case '<':
return a < b ? trueConstant : falseConstant;
case '<=':
return a <= b ? trueConstant : falseConstant;
case '>=':
return a >= b ? trueConstant : falseConstant;
case '>':
return a > b ? trueConstant : falseConstant;
}
throw new ConstantEvaluationError(
'Binary operation "$op" on num is disallowed.');
}
int _wrapAroundInteger(int value) {
if (strongMode) {
return value.toSigned(64);
}
return value;
}
static const kMaxInt64 = (1 << 63) - 1;
static const kMinInt64 = -(1 << 63);
}
/// Holds the necessary information for a constant object, namely
/// * the [klass] being instantiated
/// * the [typeArguments] used for the instantiation
/// * the [fields] the instance will obtain (all fields from the
/// instantiated [klass] up to the [Object] klass).
class InstanceBuilder {
/// The class of the new instance.
final Class klass;
/// The values of the type parameters of the new instance.
final List<DartType> typeArguments;
/// The field values of the new instance.
final Map<Field, Constant> fields = <Field, Constant>{};
InstanceBuilder(this.klass, this.typeArguments);
void setFieldValue(Field field, Constant constant) {
fields[field] = constant;
}
InstanceConstant buildInstance() {
final Map<Reference, Constant> fieldValues = <Reference, Constant>{};
fields.forEach((Field field, Constant value) {
fieldValues[field.reference] = value;
});
return new InstanceConstant(klass.reference, typeArguments, fieldValues);
}
}
/// Holds an environment of type parameters, parameters and variables.
class EvaluationEnvironment {
/// The values of the type parameters in scope.
final Map<TypeParameter, DartType> _typeVariables =
<TypeParameter, DartType>{};
/// The values of the parameters/variables in scope.
final Map<VariableDeclaration, Constant> _variables =
<VariableDeclaration, Constant>{};
/// Whether the current environment is empty.
bool get isEmpty => _typeVariables.isEmpty && _variables.isEmpty;
void addTypeParameterValue(TypeParameter parameter, DartType value) {
assert(!_typeVariables.containsKey(parameter));
_typeVariables[parameter] = value;
}
void addVariableValue(VariableDeclaration variable, Constant value) {
assert(!_variables.containsKey(variable));
_variables[variable] = value;
}
DartType lookupParameterValue(TypeParameter parameter) {
final DartType value = _typeVariables[parameter];
assert(value != null);
return value;
}
Constant lookupVariable(VariableDeclaration variable) {
final Constant value = _variables[variable];
assert(value != null);
return value;
}
DartType subsituteType(DartType type) {
if (_typeVariables.isEmpty) return type;
return substitute(type, _typeVariables);
}
}
abstract class ConstantsBackend {
Constant buildConstantForNative(
String nativeName,
List<DartType> typeArguments,
List<Constant> positionalArguments,
Map<String, Constant> namedArguments);
Constant buildSymbolConstant(StringConstant value);
Constant lowerListConstant(ListConstant constant);
Constant lowerMapConstant(MapConstant constant);
}
/// Represents a compile-time error reported during constant evaluation.
class ConstantEvaluationError {
final String message;
ConstantEvaluationError(this.message);
String toString() => 'Error during constant evaluation: $message';
}