4d1adf42d3
See https://github.com/dart-lang/sdk/issues/31842 Change-Id: I019909831a91a4e06138dc3a6bf32f766bca1ae6 Reviewed-on: https://dart-review.googlesource.com/34880 Commit-Queue: Peter von der Ahé <ahe@google.com> Reviewed-by: Jens Johansen <jensj@google.com>
980 lines
34 KiB
Dart
980 lines
34 KiB
Dart
// 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;
|
|
|
|
Program transformProgram(Program program, ConstantsBackend backend,
|
|
{bool keepFields: false,
|
|
bool strongMode: false,
|
|
bool enableAsserts: false,
|
|
CoreTypes coreTypes,
|
|
ClassHierarchy hierarchy}) {
|
|
coreTypes ??= new CoreTypes(program);
|
|
hierarchy ??= new ClassHierarchy(program);
|
|
|
|
final typeEnvironment =
|
|
new TypeEnvironment(coreTypes, hierarchy, strongMode: strongMode);
|
|
|
|
transformLibraries(program.libraries, backend, coreTypes, typeEnvironment,
|
|
keepFields: keepFields,
|
|
strongMode: strongMode,
|
|
enableAsserts: enableAsserts);
|
|
return program;
|
|
}
|
|
|
|
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';
|
|
}
|