blob: 35a2f6778783ec52e59637cf6cad5f10fb2fda82 [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.
// @dart = 2.9
/// 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
/// validation 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 fasta.constant_evaluator;
import 'dart:io' as io;
import 'package:kernel/ast.dart';
import 'package:kernel/class_hierarchy.dart';
import 'package:kernel/clone.dart';
import 'package:kernel/core_types.dart';
import 'package:kernel/kernel.dart';
import 'package:kernel/src/const_canonical_type.dart';
import 'package:kernel/src/legacy_erasure.dart';
import 'package:kernel/src/norm.dart';
import 'package:kernel/src/printer.dart' show AstPrinter, AstTextStrategy;
import 'package:kernel/type_algebra.dart';
import 'package:kernel/type_environment.dart';
import 'package:kernel/target/targets.dart';
import '../fasta_codes.dart'
show
LocatedMessage,
Message,
messageConstEvalCircularity,
messageConstEvalContext,
messageConstEvalExtension,
messageConstEvalFailedAssertion,
messageConstEvalNotListOrSetInSpread,
messageConstEvalNotMapInSpread,
messageConstEvalNonNull,
messageConstEvalNullValue,
messageConstEvalStartingPoint,
messageConstEvalUnevaluated,
messageNonAgnosticConstant,
messageNotAConstantExpression,
noLength,
templateConstEvalCaseImplementsEqual,
templateConstEvalDeferredLibrary,
templateConstEvalDuplicateElement,
templateConstEvalDuplicateKey,
templateConstEvalElementImplementsEqual,
templateConstEvalFailedAssertionWithMessage,
templateConstEvalFreeTypeParameter,
templateConstEvalGetterNotFound,
templateConstEvalInvalidType,
templateConstEvalInvalidBinaryOperandType,
templateConstEvalInvalidEqualsOperandType,
templateConstEvalInvalidMethodInvocation,
templateConstEvalInvalidPropertyGet,
templateConstEvalInvalidStaticInvocation,
templateConstEvalInvalidStringInterpolationOperand,
templateConstEvalInvalidSymbolName,
templateConstEvalKeyImplementsEqual,
templateConstEvalNonConstantVariableGet,
templateConstEvalUnhandledCoreException,
templateConstEvalUnhandledException,
templateConstEvalZeroDivisor;
import 'constant_int_folder.dart';
part 'constant_collection_builders.dart';
Component transformComponent(
Component component,
ConstantsBackend backend,
Map<String, String> environmentDefines,
ErrorReporter errorReporter,
EvaluationMode evaluationMode,
{bool evaluateAnnotations,
bool desugarSets,
bool enableTripleShift,
bool enableConstFunctions,
bool errorOnUnevaluatedConstant,
CoreTypes coreTypes,
ClassHierarchy hierarchy}) {
assert(evaluateAnnotations != null);
assert(desugarSets != null);
assert(enableTripleShift != null);
assert(enableConstFunctions != null);
assert(errorOnUnevaluatedConstant != null);
coreTypes ??= new CoreTypes(component);
hierarchy ??= new ClassHierarchy(component, coreTypes);
final TypeEnvironment typeEnvironment =
new TypeEnvironment(coreTypes, hierarchy);
transformLibraries(component.libraries, backend, environmentDefines,
typeEnvironment, errorReporter, evaluationMode,
enableTripleShift: enableTripleShift,
enableConstFunctions: enableConstFunctions,
errorOnUnevaluatedConstant: errorOnUnevaluatedConstant,
evaluateAnnotations: evaluateAnnotations);
return component;
}
ConstantCoverage transformLibraries(
List<Library> libraries,
ConstantsBackend backend,
Map<String, String> environmentDefines,
TypeEnvironment typeEnvironment,
ErrorReporter errorReporter,
EvaluationMode evaluationMode,
{bool evaluateAnnotations,
bool enableTripleShift,
bool enableConstFunctions,
bool errorOnUnevaluatedConstant}) {
assert(evaluateAnnotations != null);
assert(enableTripleShift != null);
assert(enableConstFunctions != null);
assert(errorOnUnevaluatedConstant != null);
final ConstantsTransformer constantsTransformer = new ConstantsTransformer(
backend,
environmentDefines,
evaluateAnnotations,
enableTripleShift,
enableConstFunctions,
errorOnUnevaluatedConstant,
typeEnvironment,
errorReporter,
evaluationMode);
for (final Library library in libraries) {
constantsTransformer.convertLibrary(library);
}
return constantsTransformer.constantEvaluator.getConstantCoverage();
}
void transformProcedure(
Procedure procedure,
ConstantsBackend backend,
Map<String, String> environmentDefines,
TypeEnvironment typeEnvironment,
ErrorReporter errorReporter,
EvaluationMode evaluationMode,
{bool evaluateAnnotations: true,
bool enableTripleShift: false,
bool enableConstFunctions: false,
bool errorOnUnevaluatedConstant: false}) {
assert(evaluateAnnotations != null);
assert(enableTripleShift != null);
assert(enableConstFunctions != null);
assert(errorOnUnevaluatedConstant != null);
final ConstantsTransformer constantsTransformer = new ConstantsTransformer(
backend,
environmentDefines,
evaluateAnnotations,
enableTripleShift,
enableConstFunctions,
errorOnUnevaluatedConstant,
typeEnvironment,
errorReporter,
evaluationMode);
constantsTransformer.visitProcedure(procedure, null);
}
enum EvaluationMode {
weak,
agnostic,
strong,
}
class ConstantWeakener extends ComputeOnceConstantVisitor<Constant> {
ConstantEvaluator _evaluator;
ConstantWeakener(this._evaluator);
Constant processValue(Constant node, Constant value) {
if (value != null) {
value = _evaluator.canonicalize(value);
}
return value;
}
@override
Constant defaultConstant(Constant node) => throw new UnsupportedError(
"Unhandled constant ${node} (${node.runtimeType})");
@override
Constant visitNullConstant(NullConstant node) => null;
@override
Constant visitBoolConstant(BoolConstant node) => null;
@override
Constant visitIntConstant(IntConstant node) => null;
@override
Constant visitDoubleConstant(DoubleConstant node) => null;
@override
Constant visitStringConstant(StringConstant node) => null;
@override
Constant visitSymbolConstant(SymbolConstant node) => null;
@override
Constant visitMapConstant(MapConstant node) {
DartType keyType = computeConstCanonicalType(
node.keyType, _evaluator.coreTypes,
isNonNullableByDefault: _evaluator.isNonNullableByDefault);
DartType valueType = computeConstCanonicalType(
node.valueType, _evaluator.coreTypes,
isNonNullableByDefault: _evaluator.isNonNullableByDefault);
List<ConstantMapEntry> entries;
for (int index = 0; index < node.entries.length; index++) {
ConstantMapEntry entry = node.entries[index];
Constant key = visitConstant(entry.key);
Constant value = visitConstant(entry.value);
if (key != null || value != null) {
entries ??= node.entries.toList(growable: false);
entries[index] =
new ConstantMapEntry(key ?? entry.key, value ?? entry.value);
}
}
if (keyType != null || valueType != null || entries != null) {
return new MapConstant(keyType ?? node.keyType,
valueType ?? node.valueType, entries ?? node.entries);
}
return null;
}
@override
Constant visitListConstant(ListConstant node) {
DartType typeArgument = computeConstCanonicalType(
node.typeArgument, _evaluator.coreTypes,
isNonNullableByDefault: _evaluator.isNonNullableByDefault);
List<Constant> entries;
for (int index = 0; index < node.entries.length; index++) {
Constant entry = visitConstant(node.entries[index]);
if (entry != null) {
entries ??= node.entries.toList(growable: false);
entries[index] = entry;
}
}
if (typeArgument != null || entries != null) {
return new ListConstant(
typeArgument ?? node.typeArgument, entries ?? node.entries);
}
return null;
}
@override
Constant visitSetConstant(SetConstant node) {
DartType typeArgument = computeConstCanonicalType(
node.typeArgument, _evaluator.coreTypes,
isNonNullableByDefault: _evaluator.isNonNullableByDefault);
List<Constant> entries;
for (int index = 0; index < node.entries.length; index++) {
Constant entry = visitConstant(node.entries[index]);
if (entry != null) {
entries ??= node.entries.toList(growable: false);
entries[index] = entry;
}
}
if (typeArgument != null || entries != null) {
return new SetConstant(
typeArgument ?? node.typeArgument, entries ?? node.entries);
}
return null;
}
@override
Constant visitInstanceConstant(InstanceConstant node) {
List<DartType> typeArguments;
for (int index = 0; index < node.typeArguments.length; index++) {
DartType typeArgument = computeConstCanonicalType(
node.typeArguments[index], _evaluator.coreTypes,
isNonNullableByDefault: _evaluator.isNonNullableByDefault);
if (typeArgument != null) {
typeArguments ??= node.typeArguments.toList(growable: false);
typeArguments[index] = typeArgument;
}
}
Map<Reference, Constant> fieldValues;
for (Reference reference in node.fieldValues.keys) {
Constant value = visitConstant(node.fieldValues[reference]);
if (value != null) {
fieldValues ??= new Map<Reference, Constant>.from(node.fieldValues);
fieldValues[reference] = value;
}
}
if (typeArguments != null || fieldValues != null) {
return new InstanceConstant(node.classReference,
typeArguments ?? node.typeArguments, fieldValues ?? node.fieldValues);
}
return null;
}
@override
Constant visitPartialInstantiationConstant(
PartialInstantiationConstant node) {
List<DartType> types;
for (int index = 0; index < node.types.length; index++) {
DartType type = computeConstCanonicalType(
node.types[index], _evaluator.coreTypes,
isNonNullableByDefault: _evaluator.isNonNullableByDefault);
if (type != null) {
types ??= node.types.toList(growable: false);
types[index] = type;
}
}
if (types != null) {
return new PartialInstantiationConstant(node.tearOffConstant, types);
}
return null;
}
@override
Constant visitTearOffConstant(TearOffConstant node) => null;
@override
Constant visitTypeLiteralConstant(TypeLiteralConstant node) {
DartType type = computeConstCanonicalType(node.type, _evaluator.coreTypes,
isNonNullableByDefault: _evaluator.isNonNullableByDefault);
if (type != null) {
return new TypeLiteralConstant(type);
}
return null;
}
@override
Constant visitUnevaluatedConstant(UnevaluatedConstant node) => null;
}
class ConstantsTransformer extends RemovingTransformer {
final ConstantsBackend backend;
final ConstantEvaluator constantEvaluator;
final TypeEnvironment typeEnvironment;
StaticTypeContext _staticTypeContext;
final bool evaluateAnnotations;
final bool enableTripleShift;
final bool enableConstFunctions;
final bool errorOnUnevaluatedConstant;
ConstantsTransformer(
this.backend,
Map<String, String> environmentDefines,
this.evaluateAnnotations,
this.enableTripleShift,
this.enableConstFunctions,
this.errorOnUnevaluatedConstant,
this.typeEnvironment,
ErrorReporter errorReporter,
EvaluationMode evaluationMode)
: constantEvaluator = new ConstantEvaluator(
backend, environmentDefines, typeEnvironment, errorReporter,
enableTripleShift: enableTripleShift,
enableConstFunctions: enableConstFunctions,
errorOnUnevaluatedConstant: errorOnUnevaluatedConstant,
evaluationMode: evaluationMode);
/// Whether to preserve constant [Field]s. All use-sites will be rewritten.
bool get keepFields => backend.keepFields;
/// Whether to preserve constant [VariableDeclaration]s. All use-sites will be
/// rewritten.
bool get keepLocals => backend.keepLocals;
// Transform the library/class members:
void convertLibrary(Library library) {
_staticTypeContext =
new StaticTypeContext.forAnnotations(library, typeEnvironment);
transformAnnotations(library.annotations, library);
transformLibraryDependencyList(library.dependencies, library);
transformLibraryPartList(library.parts, library);
transformTypedefList(library.typedefs, library);
transformClassList(library.classes, library);
transformExtensionList(library.extensions, library);
transformProcedureList(library.procedures, library);
transformFieldList(library.fields, library);
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.node is Field && reference.canonicalName == null;
});
}
_staticTypeContext = null;
}
@override
LibraryPart visitLibraryPart(LibraryPart node, TreeNode removalSentinel) {
constantEvaluator.withNewEnvironment(() {
transformAnnotations(node.annotations, node);
});
return node;
}
@override
LibraryDependency visitLibraryDependency(
LibraryDependency node, TreeNode removalSentinel) {
constantEvaluator.withNewEnvironment(() {
transformAnnotations(node.annotations, node);
});
return node;
}
@override
Class visitClass(Class node, TreeNode removalSentinel) {
StaticTypeContext oldStaticTypeContext = _staticTypeContext;
_staticTypeContext = new StaticTypeContext.forAnnotations(
node.enclosingLibrary, typeEnvironment);
constantEvaluator.withNewEnvironment(() {
transformAnnotations(node.annotations, node);
transformFieldList(node.fields, node);
transformTypeParameterList(node.typeParameters, node);
transformConstructorList(node.constructors, node);
transformProcedureList(node.procedures, node);
transformRedirectingFactoryConstructorList(
node.redirectingFactoryConstructors, node);
});
_staticTypeContext = oldStaticTypeContext;
return node;
}
@override
Extension visitExtension(Extension node, TreeNode removalSentinel) {
StaticTypeContext oldStaticTypeContext = _staticTypeContext;
_staticTypeContext = new StaticTypeContext.forAnnotations(
node.enclosingLibrary, typeEnvironment);
constantEvaluator.withNewEnvironment(() {
transformAnnotations(node.annotations, node);
transformTypeParameterList(node.typeParameters, node);
});
_staticTypeContext = oldStaticTypeContext;
return node;
}
@override
Procedure visitProcedure(Procedure node, TreeNode removalSentinel) {
StaticTypeContext oldStaticTypeContext = _staticTypeContext;
_staticTypeContext = new StaticTypeContext(node, typeEnvironment);
constantEvaluator.withNewEnvironment(() {
transformAnnotations(node.annotations, node);
node.function = transform(node.function)..parent = node;
});
_staticTypeContext = oldStaticTypeContext;
return node;
}
@override
Constructor visitConstructor(Constructor node, TreeNode removalSentinel) {
StaticTypeContext oldStaticTypeContext = _staticTypeContext;
_staticTypeContext = new StaticTypeContext(node, typeEnvironment);
constantEvaluator.withNewEnvironment(() {
transformAnnotations(node.annotations, node);
transformInitializerList(node.initializers, node);
node.function = transform(node.function)..parent = node;
});
_staticTypeContext = oldStaticTypeContext;
return node;
}
@override
Typedef visitTypedef(Typedef node, TreeNode removalSentinel) {
constantEvaluator.withNewEnvironment(() {
transformAnnotations(node.annotations, node);
transformTypeParameterList(node.typeParameters, node);
transformTypeParameterList(node.typeParametersOfFunctionType, node);
transformVariableDeclarationList(node.positionalParameters, node);
transformVariableDeclarationList(node.namedParameters, node);
});
return node;
}
@override
RedirectingFactoryConstructor visitRedirectingFactoryConstructor(
RedirectingFactoryConstructor node, TreeNode removalSentinel) {
// Currently unreachable as the compiler doesn't produce
// RedirectingFactoryConstructor.
StaticTypeContext oldStaticTypeContext = _staticTypeContext;
_staticTypeContext = new StaticTypeContext(node, typeEnvironment);
constantEvaluator.withNewEnvironment(() {
transformAnnotations(node.annotations, node);
transformTypeParameterList(node.typeParameters, node);
transformVariableDeclarationList(node.positionalParameters, node);
transformVariableDeclarationList(node.namedParameters, node);
});
_staticTypeContext = oldStaticTypeContext;
return node;
}
@override
TypeParameter visitTypeParameter(
TypeParameter node, TreeNode removalSentinel) {
transformAnnotations(node.annotations, node);
return node;
}
void transformAnnotations(List<Expression> nodes, TreeNode parent) {
if (evaluateAnnotations && nodes.length > 0) {
transformExpressions(nodes, parent);
}
}
void transformExpressions(List<Expression> nodes, TreeNode parent) {
constantEvaluator.withNewEnvironment(() {
for (int i = 0; i < nodes.length; ++i) {
nodes[i] = evaluateAndTransformWithContext(parent, nodes[i])
..parent = parent;
}
});
}
// Handle definition of constants:
@override
FunctionNode visitFunctionNode(FunctionNode node, TreeNode removalSentinel) {
transformTypeParameterList(node.typeParameters, node);
final int positionalParameterCount = node.positionalParameters.length;
for (int i = 0; i < positionalParameterCount; ++i) {
final VariableDeclaration variable = node.positionalParameters[i];
transformAnnotations(variable.annotations, variable);
if (variable.initializer != null) {
variable.initializer =
evaluateAndTransformWithContext(variable, variable.initializer)
..parent = variable;
}
}
for (final VariableDeclaration variable in node.namedParameters) {
transformAnnotations(variable.annotations, variable);
if (variable.initializer != null) {
variable.initializer =
evaluateAndTransformWithContext(variable, variable.initializer)
..parent = variable;
}
}
if (node.body != null) {
node.body = transform(node.body)..parent = node;
}
return node;
}
@override
Statement visitFunctionDeclaration(
FunctionDeclaration node, TreeNode removalSentinel) {
if (enableConstFunctions) {
if (node.function != null) {
node.function = transform(node.function)..parent = node;
}
constantEvaluator.env.addVariableValue(
node.variable, new FunctionValue(node.function, null));
} else {
return super.visitFunctionDeclaration(node, removalSentinel);
}
return node;
}
@override
Statement visitVariableDeclaration(
VariableDeclaration node, TreeNode removalSentinel) {
transformAnnotations(node.annotations, node);
if (node.initializer != null) {
if (node.isConst) {
final Constant constant = evaluateWithContext(node, node.initializer);
constantEvaluator.env.addVariableValue(node, constant);
node.initializer = makeConstantExpression(constant, node.initializer)
..parent = node;
// If this constant is inlined, remove it.
if (!keepLocals && shouldInline(node.initializer)) {
if (constant is! UnevaluatedConstant) {
// If the constant is unevaluated we need to keep the expression,
// so that, in the case the constant contains error but the local
// is unused, the error will still be reported.
return removalSentinel /*!*/ ?? node;
}
}
} else {
node.initializer = transform(node.initializer)..parent = node;
}
}
return node;
}
@override
Field visitField(Field node, TreeNode removalSentinel) {
StaticTypeContext oldStaticTypeContext = _staticTypeContext;
_staticTypeContext = new StaticTypeContext(node, typeEnvironment);
Field field = constantEvaluator.withNewEnvironment(() {
if (node.isConst) {
transformAnnotations(node.annotations, node);
node.initializer =
evaluateAndTransformWithContext(node, node.initializer)
..parent = node;
// If this constant is inlined, remove it.
if (!keepFields && shouldInline(node.initializer)) {
return removalSentinel;
}
} else {
transformAnnotations(node.annotations, node);
if (node.initializer != null) {
node.initializer = transform(node.initializer)..parent = node;
}
}
return node;
});
_staticTypeContext = oldStaticTypeContext;
return field;
}
// Handle use-sites of constants (and "inline" constant expressions):
@override
Expression visitSymbolLiteral(SymbolLiteral node, TreeNode removalSentinel) {
return makeConstantExpression(
constantEvaluator.evaluate(_staticTypeContext, node), node);
}
bool _isNull(Expression node) {
return node is NullLiteral ||
node is ConstantExpression && node.constant is NullConstant;
}
@override
Expression visitEqualsCall(EqualsCall node, TreeNode removalSentinel) {
Expression left = transform(node.left);
Expression right = transform(node.right);
if (_isNull(left)) {
return new EqualsNull(right)..fileOffset = node.fileOffset;
} else if (_isNull(right)) {
return new EqualsNull(left)..fileOffset = node.fileOffset;
}
node.left = left..parent = node;
node.right = right..parent = node;
return node;
}
@override
Expression visitStaticGet(StaticGet node, TreeNode removalSentinel) {
final Member target = node.target;
if (target is Field && target.isConst) {
// Make sure the initializer is evaluated first.
StaticTypeContext oldStaticTypeContext = _staticTypeContext;
_staticTypeContext = new StaticTypeContext(target, typeEnvironment);
target.initializer =
evaluateAndTransformWithContext(target, target.initializer)
..parent = target;
_staticTypeContext = oldStaticTypeContext;
if (shouldInline(target.initializer)) {
return evaluateAndTransformWithContext(node, node);
}
} else if (target is Procedure && target.kind == ProcedureKind.Method) {
return evaluateAndTransformWithContext(node, node);
}
return super.visitStaticGet(node, removalSentinel);
}
@override
Expression visitStaticTearOff(StaticTearOff node, TreeNode removalSentinel) {
final Member target = node.target;
if (target is Procedure && target.kind == ProcedureKind.Method) {
return evaluateAndTransformWithContext(node, node);
}
return super.visitStaticTearOff(node, removalSentinel);
}
@override
SwitchCase visitSwitchCase(SwitchCase node, TreeNode removalSentinel) {
transformExpressions(node.expressions, node);
return super.visitSwitchCase(node, removalSentinel);
}
@override
SwitchStatement visitSwitchStatement(
SwitchStatement node, TreeNode removalSentinel) {
SwitchStatement result = super.visitSwitchStatement(node, removalSentinel);
Library library = constantEvaluator.libraryOf(node);
if (library != null && library.isNonNullableByDefault) {
for (SwitchCase switchCase in node.cases) {
for (Expression caseExpression in switchCase.expressions) {
if (caseExpression is ConstantExpression) {
if (!constantEvaluator.hasPrimitiveEqual(caseExpression.constant)) {
Uri uri = constantEvaluator.getFileUri(caseExpression);
int offset = constantEvaluator.getFileOffset(uri, caseExpression);
constantEvaluator.errorReporter.report(
templateConstEvalCaseImplementsEqual
.withArguments(caseExpression.constant,
constantEvaluator.isNonNullableByDefault)
.withLocation(uri, offset, noLength),
null);
}
} else {
// If caseExpression is not ConstantExpression, an error is reported
// elsewhere.
}
}
}
}
return result;
}
@override
Expression visitVariableGet(VariableGet node, TreeNode removalSentinel) {
final VariableDeclaration variable = node.variable;
if (variable.isConst) {
variable.initializer =
evaluateAndTransformWithContext(variable, variable.initializer)
..parent = variable;
if (shouldInline(variable.initializer)) {
return evaluateAndTransformWithContext(node, node);
}
}
return super.visitVariableGet(node, removalSentinel);
}
@override
Expression visitListLiteral(ListLiteral node, TreeNode removalSentinel) {
if (node.isConst) {
return evaluateAndTransformWithContext(node, node);
}
return super.visitListLiteral(node, removalSentinel);
}
@override
Expression visitListConcatenation(
ListConcatenation node, TreeNode removalSentinel) {
return evaluateAndTransformWithContext(node, node);
}
@override
Expression visitSetLiteral(SetLiteral node, TreeNode removalSentinel) {
if (node.isConst) {
return evaluateAndTransformWithContext(node, node);
}
return super.visitSetLiteral(node, removalSentinel);
}
@override
Expression visitSetConcatenation(
SetConcatenation node, TreeNode removalSentinel) {
return evaluateAndTransformWithContext(node, node);
}
@override
Expression visitMapLiteral(MapLiteral node, TreeNode removalSentinel) {
if (node.isConst) {
return evaluateAndTransformWithContext(node, node);
}
return super.visitMapLiteral(node, removalSentinel);
}
@override
Expression visitTypeLiteral(TypeLiteral node, TreeNode removalSentinel) {
if (!containsFreeTypeVariables(node.type)) {
return evaluateAndTransformWithContext(node, node);
}
return super.visitTypeLiteral(node, removalSentinel);
}
@override
Expression visitMapConcatenation(
MapConcatenation node, TreeNode removalSentinel) {
return evaluateAndTransformWithContext(node, node);
}
@override
Expression visitConstructorInvocation(
ConstructorInvocation node, TreeNode removalSentinel) {
if (node.isConst) {
return evaluateAndTransformWithContext(node, node);
}
return super.visitConstructorInvocation(node, removalSentinel);
}
@override
Expression visitStaticInvocation(
StaticInvocation node, TreeNode removalSentinel) {
if (node.isConst) {
return evaluateAndTransformWithContext(node, node);
}
return super.visitStaticInvocation(node, removalSentinel);
}
@override
Expression visitConstantExpression(
ConstantExpression node, TreeNode removalSentinel) {
Constant constant = node.constant;
if (constant is UnevaluatedConstant) {
Expression expression = constant.expression;
return evaluateAndTransformWithContext(expression, expression);
} else {
node.constant = constantEvaluator.canonicalize(constant);
return node;
}
}
Expression evaluateAndTransformWithContext(
TreeNode treeContext, Expression node) {
return makeConstantExpression(evaluateWithContext(treeContext, node), node);
}
Constant evaluateWithContext(TreeNode treeContext, Expression node) {
if (treeContext == node) {
return constantEvaluator.evaluate(_staticTypeContext, node);
}
return constantEvaluator.evaluate(_staticTypeContext, node,
contextNode: treeContext);
}
Expression makeConstantExpression(Constant constant, Expression node) {
if (constant is UnevaluatedConstant &&
constant.expression is InvalidExpression) {
return constant.expression;
}
return new ConstantExpression(
constant, node.getStaticType(_staticTypeContext))
..fileOffset = node.fileOffset;
}
bool shouldInline(Expression initializer) {
if (initializer is ConstantExpression) {
return backend.shouldInlineConstant(initializer);
}
return true;
}
}
class ConstantEvaluator implements ExpressionVisitor<Constant> {
final ConstantsBackend backend;
final NumberSemantics numberSemantics;
ConstantIntFolder intFolder;
Map<String, String> environmentDefines;
final bool errorOnUnevaluatedConstant;
final CoreTypes coreTypes;
final TypeEnvironment typeEnvironment;
StaticTypeContext _staticTypeContext;
final ErrorReporter errorReporter;
final EvaluationMode evaluationMode;
final bool enableTripleShift;
final bool enableConstFunctions;
final bool Function(DartType) isInstantiated =
new IsInstantiatedVisitor().isInstantiated;
final Map<Constant, Constant> canonicalizationCache;
final Map<Node, Object> nodeCache;
final CloneVisitorNotMembers cloner = new CloneVisitorNotMembers();
Map<Class, bool> primitiveEqualCache;
final NullConstant nullConstant = new NullConstant();
final BoolConstant trueConstant = new BoolConstant(true);
final BoolConstant falseConstant = new BoolConstant(false);
InstanceBuilder instanceBuilder;
EvaluationEnvironment env;
Set<Expression> replacementNodes = new Set<Expression>.identity();
Map<Constant, Constant> lowered = new Map<Constant, Constant>.identity();
bool seenUnevaluatedChild; // Any children that were left unevaluated?
int lazyDepth; // Current nesting depth of lazy regions.
bool get shouldBeUnevaluated => seenUnevaluatedChild || lazyDepth != 0;
bool get targetingJavaScript => numberSemantics == NumberSemantics.js;
bool get isNonNullableByDefault =>
_staticTypeContext.nonNullable == Nullability.nonNullable;
ConstantWeakener _weakener;
ConstantEvaluator(this.backend, this.environmentDefines, this.typeEnvironment,
this.errorReporter,
{this.enableTripleShift = false,
this.enableConstFunctions = false,
this.errorOnUnevaluatedConstant = false,
this.evaluationMode: EvaluationMode.weak})
: numberSemantics = backend.numberSemantics,
coreTypes = typeEnvironment.coreTypes,
canonicalizationCache = <Constant, Constant>{},
nodeCache = <Node, Constant>{},
env = new EvaluationEnvironment() {
if (environmentDefines == null && !backend.supportsUnevaluatedConstants) {
throw new ArgumentError(
"No 'environmentDefines' passed to the constant evaluator but the "
"ConstantsBackend does not support unevaluated constants.");
}
intFolder = new ConstantIntFolder.forSemantics(this, numberSemantics);
primitiveEqualCache = <Class, bool>{
coreTypes.boolClass: true,
coreTypes.doubleClass: false,
coreTypes.intClass: true,
coreTypes.internalSymbolClass: true,
coreTypes.listClass: true,
coreTypes.mapClass: true,
coreTypes.objectClass: true,
coreTypes.setClass: true,
coreTypes.stringClass: true,
coreTypes.symbolClass: true,
coreTypes.typeClass: true,
};
_weakener = new ConstantWeakener(this);
}
DartType convertType(DartType type) {
switch (evaluationMode) {
case EvaluationMode.strong:
case EvaluationMode.agnostic:
return norm(coreTypes, type);
case EvaluationMode.weak:
return computeConstCanonicalType(norm(coreTypes, type), coreTypes,
isNonNullableByDefault: isNonNullableByDefault);
}
throw new UnsupportedError(
"Unexpected evaluation mode: ${evaluationMode}.");
}
List<DartType> convertTypes(List<DartType> types) {
switch (evaluationMode) {
case EvaluationMode.strong:
case EvaluationMode.agnostic:
return types.map((DartType type) => norm(coreTypes, type)).toList();
case EvaluationMode.weak:
return types
.map((DartType type) => computeConstCanonicalType(
norm(coreTypes, type), coreTypes,
isNonNullableByDefault: isNonNullableByDefault))
.toList();
}
throw new UnsupportedError(
"Unexpected evaluation mode: ${evaluationMode}.");
}
Uri getFileUri(TreeNode node) {
while (node != null && node is! FileUriNode) {
node = node.parent;
}
return (node as FileUriNode)?.fileUri;
}
int getFileOffset(Uri uri, TreeNode node) {
if (uri == null) return TreeNode.noOffset;
while (node != null && node.fileOffset == TreeNode.noOffset) {
node = node.parent;
}
return node == null ? TreeNode.noOffset : node.fileOffset;
}
/// Evaluate [node] and possibly cache the evaluation result.
/// Returns UnevaluatedConstant if the constant could not be evaluated.
/// If the expression in the UnevaluatedConstant is an InvalidExpression,
/// an error occurred during constant evaluation.
Constant evaluate(StaticTypeContext context, Expression node,
{TreeNode contextNode}) {
_staticTypeContext = context;
seenUnevaluatedChild = false;
lazyDepth = 0;
Constant result = _evaluateSubexpression(node);
if (result is AbortConstant) {
if (result is _AbortDueToErrorConstant) {
final Uri uri = getFileUri(result.node);
final int fileOffset = getFileOffset(uri, result.node);
final LocatedMessage locatedMessageActualError =
result.message.withLocation(uri, fileOffset, noLength);
final List<LocatedMessage> contextMessages = <LocatedMessage>[
locatedMessageActualError
];
if (result.context != null) contextMessages.addAll(result.context);
if (contextNode != null && contextNode != result.node) {
final Uri uri = getFileUri(contextNode);
final int fileOffset = getFileOffset(uri, contextNode);
contextMessages.add(
messageConstEvalContext.withLocation(uri, fileOffset, noLength));
}
{
final Uri uri = getFileUri(node);
final int fileOffset = getFileOffset(uri, node);
final LocatedMessage locatedMessage = messageConstEvalStartingPoint
.withLocation(uri, fileOffset, noLength);
errorReporter.report(locatedMessage, contextMessages);
}
return new UnevaluatedConstant(
new InvalidExpression(result.message.message));
}
if (result is _AbortDueToInvalidExpressionConstant) {
InvalidExpression invalid = new InvalidExpression(result.message)
..fileOffset = node.fileOffset;
errorReporter.reportInvalidExpression(invalid);
return new UnevaluatedConstant(invalid);
} else if (result is _AbortDueToThrowConstant) {
final Object value = result.throwValue;
Message message;
if (value is Constant) {
message = templateConstEvalUnhandledException.withArguments(
value, isNonNullableByDefault);
} else if (value is Error) {
message = templateConstEvalUnhandledCoreException
.withArguments(value.toString());
}
assert(message != null);
final Uri uri = getFileUri(result.node);
final int fileOffset = getFileOffset(uri, result.node);
final LocatedMessage locatedMessageActualError =
message.withLocation(uri, fileOffset, noLength);
final List<LocatedMessage> contextMessages = <LocatedMessage>[
locatedMessageActualError
];
{
final Uri uri = getFileUri(node);
final int fileOffset = getFileOffset(uri, node);
final LocatedMessage locatedMessage = messageConstEvalStartingPoint
.withLocation(uri, fileOffset, noLength);
errorReporter.report(locatedMessage, contextMessages);
}
return new UnevaluatedConstant(new InvalidExpression(message.message));
}
throw "Unexpected error constant";
}
if (result is UnevaluatedConstant) {
if (errorOnUnevaluatedConstant) {
return createErrorConstant(node, messageConstEvalUnevaluated);
}
return canonicalize(new UnevaluatedConstant(
removeRedundantFileUriExpressions(result.expression)));
}
return result;
}
/// Execute a function body using the [StatementConstantEvaluator].
Constant executeBody(Statement statement) {
StatementConstantEvaluator statementEvaluator =
new StatementConstantEvaluator(this);
ExecutionStatus status = statement.accept(statementEvaluator);
if (status is ReturnStatus) {
if (status.value == null) {
// Void return type from executing the function body.
return new NullConstant();
}
return status.value;
} else if (status is AbortStatus) {
return status.error;
} else if (status is ProceedStatus) {
// No return statement in function body with void return type.
return new NullConstant();
}
return createInvalidExpressionConstant(statement,
'No valid constant returned from the execution of $statement.');
}
/// Returns [null] on success and an error-"constant" on failure, as such the
/// return value should be checked.
AbortConstant executeConstructorBody(Constructor constructor) {
final Statement body = constructor.function.body;
StatementConstantEvaluator statementEvaluator =
new StatementConstantEvaluator(this);
ExecutionStatus status = body.accept(statementEvaluator);
if (status is AbortStatus) {
return status.error;
} else if (status is ReturnStatus) {
if (status.value == null) return null;
// Should not be reachable.
return createInvalidExpressionConstant(
constructor, "Constructors can't have a return value.");
} else if (status is! ProceedStatus) {
return createInvalidExpressionConstant(
constructor, "Invalid execution status of constructor body.");
}
return null;
}
/// Create an error-constant indicating that an error has been detected during
/// constant evaluation.
AbortConstant createErrorConstant(TreeNode node, Message message,
{List<LocatedMessage> context}) {
return new _AbortDueToErrorConstant(node, message, context: context);
}
/// Create an error-constant indicating a construct that should not occur
/// inside a potentially constant expression.
/// It is assumed that an error has already been reported.
AbortConstant createInvalidExpressionConstant(TreeNode node, String message) {
return new _AbortDueToInvalidExpressionConstant(node, message);
}
/// Produce an unevaluated constant node for an expression.
Constant unevaluated(Expression original, Expression replacement) {
replacement.fileOffset = original.fileOffset;
return new UnevaluatedConstant(
new FileUriExpression(replacement, getFileUri(original))
..fileOffset = original.fileOffset);
}
Expression removeRedundantFileUriExpressions(Expression node) {
return node.accept(new RedundantFileUriExpressionRemover()) as Expression;
}
/// Extract an expression from a (possibly unevaluated) constant to become
/// part of the expression tree of another unevaluated constant.
/// Makes sure a particular expression occurs only once in the tree by
/// cloning further instances.
Expression extract(Constant constant) {
Expression expression = constant.asExpression();
if (!replacementNodes.add(expression)) {
expression = cloner.clone(expression);
replacementNodes.add(expression);
}
return expression;
}
/// Enter a region of lazy evaluation. All leaf nodes are evaluated normally
/// (to ensure inlining of referenced local variables), but composite nodes
/// always treat their children as unevaluated, resulting in a partially
/// evaluated clone of the original expression tree.
/// Lazy evaluation is used for the subtrees of lazy operations with
/// unevaluated conditions to ensure no errors are reported for problems
/// in the subtree as long as the subtree is potentially constant.
void enterLazy() => lazyDepth++;
/// Leave a (possibly nested) region of lazy evaluation.
void leaveLazy() => lazyDepth--;
Constant lower(Constant original, Constant replacement) {
if (!identical(original, replacement)) {
original = canonicalize(original);
replacement = canonicalize(replacement);
lowered[replacement] = original;
return replacement;
}
return canonicalize(replacement);
}
Constant unlower(Constant constant) {
return lowered[constant] ?? constant;
}
Constant lowerListConstant(ListConstant constant) {
if (shouldBeUnevaluated) return constant;
return lower(constant, backend.lowerListConstant(constant));
}
Constant lowerSetConstant(SetConstant constant) {
if (shouldBeUnevaluated) return constant;
return lower(constant, backend.lowerSetConstant(constant));
}
Constant lowerMapConstant(MapConstant constant) {
if (shouldBeUnevaluated) return constant;
return lower(constant, backend.lowerMapConstant(constant));
}
Map<Uri, Set<Reference>> _constructorCoverage = {};
ConstantCoverage getConstantCoverage() {
return new ConstantCoverage(_constructorCoverage);
}
void _recordConstructorCoverage(Constructor constructor, TreeNode caller) {
Uri currentUri = getFileUri(caller);
Set<Reference> uriCoverage = _constructorCoverage[currentUri] ??= {};
uriCoverage.add(constructor.reference);
}
/// Evaluate [node] and possibly cache the evaluation result.
///
/// Returns [_AbortDueToErrorConstant] or
/// [_AbortDueToInvalidExpressionConstant] (both of which is an
/// [AbortConstant]) if the expression can't be evaluated.
/// As such the return value should be checked (e.g. `is AbortConstant`)
/// before further use.
Constant _evaluateSubexpression(Expression node) {
if (node is ConstantExpression) {
if (node.constant is! UnevaluatedConstant) {
// ConstantExpressions just pointing to an actual constant can be
// short-circuited. Note that it's accepted instead of just returned to
// get canonicalization.
return node.accept(this);
}
} else if (node is BasicLiteral) {
// Basic literals (string literals, int literals, double literals,
// bool literals and null literals) can be short-circuited too.
return node.accept(this);
}
bool wasUnevaluated = seenUnevaluatedChild;
seenUnevaluatedChild = false;
Constant result;
if (env.isEmpty) {
// We only try to evaluate the same [node] *once* within an empty
// environment.
// For const functions, recompute getters instead of using the cached
// value.
bool isGetter = node is InstanceGet || node is PropertyGet;
if (nodeCache.containsKey(node) && !(enableConstFunctions && isGetter)) {
result = nodeCache[node];
if (result == null) {
// [null] is a sentinel value only used when still evaluating the same
// node.
return createErrorConstant(node, messageConstEvalCircularity);
}
} else {
nodeCache[node] = null;
result = node.accept(this);
if (result is AbortConstant) {
nodeCache.remove(node);
return result;
} else {
nodeCache[node] = result;
}
}
} else {
bool sentinelInserted = false;
if (nodeCache.containsKey(node)) {
bool isRecursiveFunctionCall =
node is MethodInvocation || node is StaticInvocation;
if (nodeCache[node] == null &&
!(enableConstFunctions && isRecursiveFunctionCall)) {
// recursive call
return createErrorConstant(node, messageConstEvalCircularity);
}
// else we've seen the node before and come to a result -> we won't
// go into an infinite loop here either.
} else {
// We haven't seen this node before. Risk of loop.
nodeCache[node] = null;
sentinelInserted = true;
}
result = node.accept(this);
if (sentinelInserted) {
nodeCache.remove(node);
}
if (result is AbortConstant) {
return result;
}
}
seenUnevaluatedChild = wasUnevaluated || result is UnevaluatedConstant;
return result;
}
Constant _evaluateNullableSubexpression(Expression node) {
if (node == null) return nullConstant;
return _evaluateSubexpression(node);
}
@override
Constant defaultExpression(Expression node) {
// Only a subset of the expression language is valid for constant
// evaluation.
return createInvalidExpressionConstant(
node, 'Constant evaluation has no support for ${node.runtimeType}!');
}
@override
Constant visitFileUriExpression(FileUriExpression node) {
return _evaluateSubexpression(node.expression);
}
@override
Constant visitNullLiteral(NullLiteral node) => nullConstant;
@override
Constant visitBoolLiteral(BoolLiteral node) {
return makeBoolConstant(node.value);
}
@override
Constant visitIntLiteral(IntLiteral node) {
// The frontend ensures that integer literals are valid according to the
// target representation.
return canonicalize(intFolder.makeIntConstant(node.value, unsigned: true));
}
@override
Constant visitDoubleLiteral(DoubleLiteral node) {
return canonicalize(new DoubleConstant(node.value));
}
@override
Constant visitStringLiteral(StringLiteral node) {
return canonicalize(new StringConstant(node.value));
}
@override
Constant visitTypeLiteral(TypeLiteral node) {
DartType type = _evaluateDartType(node, node.type);
if (type != null) {
type = convertType(type);
}
if (type == null && _gotError != null) {
AbortConstant error = _gotError;
_gotError = null;
return error;
}
assert(_gotError == null);
assert(type != null);
return canonicalize(new TypeLiteralConstant(type));
}
@override
Constant visitConstantExpression(ConstantExpression node) {
Constant constant = node.constant;
Constant result = constant;
if (constant is UnevaluatedConstant) {
if (environmentDefines != null) {
result = _evaluateSubexpression(constant.expression);
if (result is AbortConstant) return result;
} else {
// Still no environment. Doing anything is just wasted time.
result = constant;
}
}
// 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(result);
}
@override
Constant visitListLiteral(ListLiteral node) {
if (!node.isConst) {
return createInvalidExpressionConstant(node, "Non-constant list literal");
}
final ListConstantBuilder builder =
new ListConstantBuilder(node, convertType(node.typeArgument), this);
// These expressions are at the same level, so one of them being
// unevaluated doesn't mean a sibling is or has an unevaluated child.
// We therefore reset it before each call, combine it and set it correctly
// at the end.
bool wasOrBecameUnevaluated = seenUnevaluatedChild;
for (Expression element in node.expressions) {
seenUnevaluatedChild = false;
AbortConstant error = builder.add(element);
wasOrBecameUnevaluated |= seenUnevaluatedChild;
if (error != null) return error;
}
seenUnevaluatedChild = wasOrBecameUnevaluated;
return builder.build();
}
@override
Constant visitListConcatenation(ListConcatenation node) {
final ListConstantBuilder builder =
new ListConstantBuilder(node, convertType(node.typeArgument), this);
for (Expression list in node.lists) {
AbortConstant error = builder.addSpread(list);
if (error != null) return error;
}
return builder.build();
}
@override
Constant visitSetLiteral(SetLiteral node) {
if (!node.isConst) {
return createInvalidExpressionConstant(node, "Non-constant set literal");
}
final SetConstantBuilder builder =
new SetConstantBuilder(node, convertType(node.typeArgument), this);
// These expressions are at the same level, so one of them being
// unevaluated doesn't mean a sibling is or has an unevaluated child.
// We therefore reset it before each call, combine it and set it correctly
// at the end.
bool wasOrBecameUnevaluated = seenUnevaluatedChild;
for (Expression element in node.expressions) {
seenUnevaluatedChild = false;
AbortConstant error = builder.add(element);
wasOrBecameUnevaluated |= seenUnevaluatedChild;
if (error != null) return error;
}
seenUnevaluatedChild = wasOrBecameUnevaluated;
return builder.build();
}
@override
Constant visitSetConcatenation(SetConcatenation node) {
final SetConstantBuilder builder =
new SetConstantBuilder(node, convertType(node.typeArgument), this);
for (Expression set_ in node.sets) {
AbortConstant error = builder.addSpread(set_);
if (error != null) return error;
}
return builder.build();
}
@override
Constant visitMapLiteral(MapLiteral node) {
if (!node.isConst) {
return createInvalidExpressionConstant(node, "Non-constant map literal");
}
final MapConstantBuilder builder = new MapConstantBuilder(
node, convertType(node.keyType), convertType(node.valueType), this);
// These expressions are at the same level, so one of them being
// unevaluated doesn't mean a sibling is or has an unevaluated child.
// We therefore reset it before each call, combine it and set it correctly
// at the end.
bool wasOrBecameUnevaluated = seenUnevaluatedChild;
for (MapEntry element in node.entries) {
seenUnevaluatedChild = false;
AbortConstant error = builder.add(element);
wasOrBecameUnevaluated |= seenUnevaluatedChild;
if (error != null) return error;
}
seenUnevaluatedChild = wasOrBecameUnevaluated;
return builder.build();
}
@override
Constant visitMapConcatenation(MapConcatenation node) {
final MapConstantBuilder builder = new MapConstantBuilder(
node, convertType(node.keyType), convertType(node.valueType), this);
for (Expression map in node.maps) {
AbortConstant error = builder.addSpread(map);
if (error != null) return error;
}
return builder.build();
}
@override
Constant visitFunctionExpression(FunctionExpression node) {
if (enableConstFunctions) {
return new FunctionValue(node.function, env);
}
return createInvalidExpressionConstant(node, "Function literal");
}
@override
Constant visitConstructorInvocation(ConstructorInvocation node) {
if (!node.isConst) {
return createInvalidExpressionConstant(
node, 'Non-constant constructor invocation "$node".');
}
final Constructor constructor = node.target;
AbortConstant error = checkConstructorConst(node, constructor);
if (error != null) return error;
final Class klass = constructor.enclosingClass;
if (klass.isAbstract) {
// Probably unreachable.
return createInvalidExpressionConstant(
node, 'Constructor "$node" belongs to abstract class "${klass}".');
}
final List<Constant> positionals =
_evaluatePositionalArguments(node.arguments);
if (positionals == null && _gotError != null) {
AbortConstant error = _gotError;
_gotError = null;
return error;
}
assert(_gotError == null);
assert(positionals != null);
final Map<String, Constant> named = _evaluateNamedArguments(node.arguments);
if (named == null && _gotError != null) {
AbortConstant error = _gotError;
_gotError = null;
return error;
}
assert(_gotError == null);
assert(named != null);
bool isSymbol = klass == coreTypes.internalSymbolClass;
if (isSymbol && shouldBeUnevaluated) {
return unevaluated(
node,
new ConstructorInvocation(constructor,
unevaluatedArguments(positionals, named, node.arguments.types),
isConst: true));
}
// Special case the dart:core's Symbol class here and convert it to a
// [SymbolConstant]. For invalid values we report a compile-time error.
if (isSymbol) {
final Constant nameValue = positionals.single;
if (nameValue is StringConstant && isValidSymbolName(nameValue.value)) {
return canonicalize(new SymbolConstant(nameValue.value, null));
}
return createErrorConstant(
node.arguments.positional.first,
templateConstEvalInvalidSymbolName.withArguments(
nameValue, isNonNullableByDefault));
}
List<DartType> types = _evaluateTypeArguments(node, node.arguments);
if (types == null && _gotError != null) {
AbortConstant error = _gotError;
_gotError = null;
return error;
}
assert(_gotError == null);
assert(types != null);
final List<DartType> typeArguments = convertTypes(types);
// Fill in any missing type arguments with "dynamic".
for (int i = typeArguments.length; i < klass.typeParameters.length; i++) {
// Probably unreachable.
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.
if (shouldBeUnevaluated) {
enterLazy();
AbortConstant error = handleConstructorInvocation(
constructor, typeArguments, positionals, named, node);
if (error != null) return error;
leaveLazy();
return unevaluated(node, instanceBuilder.buildUnevaluatedInstance());
}
AbortConstant error = handleConstructorInvocation(
constructor, typeArguments, positionals, named, node);
if (error != null) return error;
if (shouldBeUnevaluated) {
return unevaluated(node, instanceBuilder.buildUnevaluatedInstance());
}
return canonicalize(instanceBuilder.buildInstance());
});
}
/// Returns [null] on success and an error-"constant" on failure, as such the
/// return value should be checked.
AbortConstant checkConstructorConst(TreeNode node, Constructor constructor) {
if (!constructor.isConst) {
return createInvalidExpressionConstant(
node, 'Non-const constructor invocation.');
}
if (constructor.function.body != null &&
constructor.function.body is! EmptyStatement &&
!enableConstFunctions) {
// Probably unreachable.
return createInvalidExpressionConstant(
node,
'Constructor "$node" has non-trivial body '
'"${constructor.function.body.runtimeType}".');
}
return null;
}
@override
Constant visitInstanceCreation(InstanceCreation node) {
return withNewInstanceBuilder(
node.classNode, convertTypes(node.typeArguments), () {
for (AssertStatement statement in node.asserts) {
AbortConstant error = checkAssert(statement);
if (error != null) return error;
}
AbortConstant error;
node.fieldValues.forEach((Reference fieldRef, Expression value) {
if (error != null) return;
Constant constant = _evaluateSubexpression(value);
if (constant is AbortConstant) {
error ??= constant;
return;
}
instanceBuilder.setFieldValue(fieldRef.asField, constant);
});
if (error != null) return error;
for (Expression value in node.unusedArguments) {
if (error != null) return error;
Constant constant = _evaluateSubexpression(value);
if (constant is AbortConstant) {
error ??= constant;
return error;
}
if (constant is UnevaluatedConstant) {
instanceBuilder.unusedArguments.add(extract(constant));
}
}
if (error != null) return error;
if (shouldBeUnevaluated) {
return unevaluated(node, instanceBuilder.buildUnevaluatedInstance());
}
// We can get here when re-evaluating a previously unevaluated constant.
return canonicalize(instanceBuilder.buildInstance());
});
}
bool isValidSymbolName(String name) {
// See https://api.dartlang.org/stable/2.0.0/dart-core/Symbol/Symbol.html:
//
// A qualified name is a valid name preceded by a public identifier name
// and a '.', e.g., foo.bar.baz= is a qualified version of baz=.
//
// That means that the content of the name String must be either
// - a valid public Dart identifier (that is, an identifier not
// starting with "_"),
// - such an identifier followed by "=" (a setter name),
// - the name of a declarable operator,
// - any of the above preceded by any number of qualifiers, where a
// qualifier is a non-private identifier followed by '.',
// - or the empty string (the default name of a library with no library
// name declaration).
const Set<String> operatorNames = const <String>{
'+',
'-',
'*',
'/',
'%',
'~/',
'&',
'|',
'^',
'~',
'<<',
'>>',
'>>>',
'<',
'<=',
'>',
'>=',
'==',
'[]',
'[]=',
'unary-'
};
if (name == null) return false;
if (name == '') return true;
final List<String> parts = name.split('.');
// Each qualifier must be a public identifier.
for (int i = 0; i < parts.length - 1; ++i) {
if (!isValidPublicIdentifier(parts[i])) return false;
}
String last = parts.last;
if (operatorNames.contains(last)) {
return enableTripleShift || last != '>>>';
}
if (last.endsWith('=')) {
last = last.substring(0, last.length - 1);
}
if (!isValidPublicIdentifier(last)) return false;
return true;
}
/// From the Dart Language specification:
///
/// IDENTIFIER:
/// IDENTIFIER_START IDENTIFIER_PART*
///
/// IDENTIFIER_START:
/// IDENTIFIER_START_NO_DOLLAR | ‘$’
///
/// IDENTIFIER_PART:
/// IDENTIFIER_START | DIGIT
///
/// IDENTIFIER_NO_DOLLAR:
/// IDENTIFIER_START_NO_DOLLAR IDENTIFIER_PART_NO_DOLLAR*
///
/// IDENTIFIER_START_NO_DOLLAR:
/// LETTER | '_'
///
/// IDENTIFIER_PART_NO_DOLLAR:
/// IDENTIFIER_START_NO_DOLLAR | DIGIT
///
static final RegExp publicIdentifierRegExp =
new RegExp(r'^[a-zA-Z$][a-zA-Z0-9_$]*$');
static const Set<String> nonUsableKeywords = const <String>{
'assert',
'break',
'case',
'catch',
'class',
'const',
'continue',
'default',
'do',
'else',
'enum',
'extends',
'false',
'final',
'finally',
'for',
'if',
'in',
'is',
'new',
'null',
'rethrow',
'return',
'super',
'switch',
'this',
'throw',
'true',
'try',
'var',
'while',
'with',
};
bool isValidPublicIdentifier(String name) {
return publicIdentifierRegExp.hasMatch(name) &&
!nonUsableKeywords.contains(name);
}
/// Returns [null] on success and an error-"constant" on failure, as such the
/// return value should be checked.
AbortConstant handleConstructorInvocation(
Constructor constructor,
List<DartType> typeArguments,
List<Constant> positionalArguments,
Map<String, Constant> namedArguments,
TreeNode caller) {
return withNewEnvironment(() {
final Class klass = constructor.enclosingClass;
final FunctionNode function = constructor.function;
// Mark in file of the caller that we evaluate this specific constructor.
_recordConstructorCoverage(constructor, caller);
// 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]
// TODO(johnniwinther): This should call [_evaluateSubexpression].
: _evaluateNullableSubexpression(parameter.initializer);
if (value is AbortConstant) return value;
env.addVariableValue(parameter, value);
}
for (final VariableDeclaration parameter in function.namedParameters) {
final Constant value = namedArguments[parameter.name] ??
// TODO(johnniwinther): This should call [_evaluateSubexpression].
_evaluateNullableSubexpression(parameter.initializer);
if (value is AbortConstant) return value;
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) {
Constant constant = _evaluateNullableSubexpression(field.initializer);
if (constant is AbortConstant) return constant;
instanceBuilder.setFieldValue(field, constant);
}
}
for (final Initializer init in constructor.initializers) {
if (init is FieldInitializer) {
Constant constant = _evaluateSubexpression(init.value);
if (constant is AbortConstant) return constant;
instanceBuilder.setFieldValue(init.field, constant);
} else if (init is LocalInitializer) {
final VariableDeclaration variable = init.variable;
Constant constant = _evaluateSubexpression(variable.initializer);
if (constant is AbortConstant) return constant;
env.addVariableValue(variable, constant);
} else if (init is SuperInitializer) {
AbortConstant error = checkConstructorConst(init, constructor);
if (error != null) return error;
List<DartType> types = _evaluateSuperTypeArguments(
init, constructor.enclosingClass.supertype);
if (types == null && _gotError != null) {
AbortConstant error = _gotError;
_gotError = null;
return error;
}
assert(_gotError == null);
assert(types != null);
List<Constant> positionalArguments =
_evaluatePositionalArguments(init.arguments);
if (positionalArguments == null && _gotError != null) {
AbortConstant error = _gotError;
_gotError = null;
return error;
}
assert(_gotError == null);
assert(positionalArguments != null);
Map<String, Constant> namedArguments =
_evaluateNamedArguments(init.arguments);
if (namedArguments == null && _gotError != null) {
AbortConstant error = _gotError;
_gotError = null;
return error;
}
assert(_gotError == null);
assert(namedArguments != null);
error = handleConstructorInvocation(init.target, types,
positionalArguments, namedArguments, constructor);
if (error != null) return error;
} else if (init is RedirectingInitializer) {
// Since a redirecting constructor targets a constructor of the same
// class, we pass the same [typeArguments].
AbortConstant error = checkConstructorConst(init, constructor);
if (error != null) return error;
List<Constant> positionalArguments =
_evaluatePositionalArguments(init.arguments);
if (positionalArguments == null && _gotError != null) {
AbortConstant error = _gotError;
_gotError = null;
return error;
}
assert(_gotError == null);
assert(positionalArguments != null);
Map<String, Constant> namedArguments =
_evaluateNamedArguments(init.arguments);
if (namedArguments == null && _gotError != null) {
AbortConstant error = _gotError;
_gotError = null;
return error;
}
assert(_gotError == null);
assert(namedArguments != null);
error = handleConstructorInvocation(init.target, typeArguments,
positionalArguments, namedArguments, constructor);
if (error != null) return error;
} else if (init is AssertInitializer) {
AbortConstant error = checkAssert(init.statement);
if (error != null) return error;
} else {
// InvalidInitializer or new Initializers.
// Probably unreachable. InvalidInitializer is (currently) only
// created for classes with no constructors that doesn't have a
// super that takes no arguments. It thus cannot be const.
// Explicit constructors with incorrect super calls will get a
// ShadowInvalidInitializer which is actually a LocalInitializer.
return createInvalidExpressionConstant(
constructor,
'No support for handling initializer of type '
'"${init.runtimeType}".');
}
}
for (UnevaluatedConstant constant in env.unevaluatedUnreadConstants) {
instanceBuilder.unusedArguments.add(extract(constant));
}
if (enableConstFunctions && constructor.function != null) {
AbortConstant error = executeConstructorBody(constructor);
if (error != null) return error;
}
return null;
});
}
/// Returns [null] on success and an error-"constant" on failure, as such the
/// return value should be checked.
AbortConstant checkAssert(AssertStatement statement) {
final Constant condition = _evaluateSubexpression(statement.condition);
if (condition is AbortConstant) return condition;
if (shouldBeUnevaluated) {
Expression message = null;
if (statement.message != null) {
enterLazy();
Constant constant = _evaluateSubexpression(statement.message);
if (constant is AbortConstant) return constant;
message = extract(constant);
leaveLazy();
}
instanceBuilder.asserts.add(new AssertStatement(extract(condition),
message: message,
conditionStartOffset: statement.conditionStartOffset,
conditionEndOffset: statement.conditionEndOffset));
} else if (condition is BoolConstant) {
if (!condition.value) {
if (statement.message == null) {
return createErrorConstant(
statement.condition, messageConstEvalFailedAssertion);
}
final Constant message = _evaluateSubexpression(statement.message);
if (message is AbortConstant) return message;
if (shouldBeUnevaluated) {
instanceBuilder.asserts.add(new AssertStatement(extract(condition),
message: extract(message),
conditionStartOffset: statement.conditionStartOffset,
conditionEndOffset: statement.conditionEndOffset));
} else if (message is StringConstant) {
return createErrorConstant(
statement.condition,
templateConstEvalFailedAssertionWithMessage
.withArguments(message.value));
} else {
return createErrorConstant(
statement.message,
templateConstEvalInvalidType.withArguments(
message,
typeEnvironment.coreTypes.stringLegacyRawType,
message.getType(_staticTypeContext),
isNonNullableByDefault));
}
}
} else {
return createErrorConstant(
statement.condition,
templateConstEvalInvalidType.withArguments(
condition,
typeEnvironment.coreTypes.boolLegacyRawType,
condition.getType(_staticTypeContext),
isNonNullableByDefault));
}
return null;
}
@override
Constant visitInvalidExpression(InvalidExpression node) {
return createInvalidExpressionConstant(node, node.message);
}
@override
Constant visitDynamicInvocation(DynamicInvocation node) {
// We have no support for generic method invocation at the moment.
if (node.arguments.types.isNotEmpty) {
return createInvalidExpressionConstant(node, "generic method invocation");
}
// We have no support for method invocation with named arguments at the
// moment.
if (node.arguments.named.isNotEmpty) {
return createInvalidExpressionConstant(
node, "method invocation with named arguments");
}
final Constant receiver = _evaluateSubexpression(node.receiver);
if (receiver is AbortConstant) return receiver;
final List<Constant> arguments =
_evaluatePositionalArguments(node.arguments);
if (arguments == null && _gotError != null) {
AbortConstant error = _gotError;
_gotError = null;
return error;
}
assert(_gotError == null);
assert(arguments != null);
if (shouldBeUnevaluated) {
return unevaluated(
node,
new DynamicInvocation(node.kind, extract(receiver), node.name,
unevaluatedArguments(arguments, {}, node.arguments.types))
..fileOffset = node.fileOffset);
}
return _handleInvocation(node, node.name, receiver, arguments);
}
@override
Constant visitInstanceInvocation(InstanceInvocation node) {
// We have no support for generic method invocation at the moment.
if (node.arguments.types.isNotEmpty) {
return createInvalidExpressionConstant(node, "generic method invocation");
}
// We have no support for method invocation with named arguments at the
// moment.
if (node.arguments.named.isNotEmpty) {
return createInvalidExpressionConstant(
node, "method invocation with named arguments");
}
final Constant receiver = _evaluateSubexpression(node.receiver);
if (receiver is AbortConstant) return receiver;
final List<Constant> arguments =
_evaluatePositionalArguments(node.arguments);
if (arguments == null && _gotError != null) {
AbortConstant error = _gotError;
_gotError = null;
return error;
}
assert(_gotError == null);
assert(arguments != null);
if (shouldBeUnevaluated) {
return unevaluated(
node,
new InstanceInvocation(node.kind, extract(receiver), node.name,
unevaluatedArguments(arguments, {}, node.arguments.types),
functionType: node.functionType,
interfaceTarget: node.interfaceTarget)
..fileOffset = node.fileOffset
..flags = node.flags);
}
return _handleInvocation(node, node.name, receiver, arguments);
}
@override
Constant visitFunctionInvocation(FunctionInvocation node) {
return createInvalidExpressionConstant(node, "function invocation");
}
@override
Constant visitLocalFunctionInvocation(LocalFunctionInvocation node) {
return createInvalidExpressionConstant(node, "local function invocation");
}
@override
Constant visitEqualsCall(EqualsCall node) {
final Constant left = _evaluateSubexpression(node.left);
if (left is AbortConstant) return left;
final Constant right = _evaluateSubexpression(node.right);
if (right is AbortConstant) return right;
if (shouldBeUnevaluated) {
return unevaluated(
node,
new EqualsCall(extract(left), extract(right),
functionType: node.functionType,
interfaceTarget: node.interfaceTarget)
..fileOffset = node.fileOffset);
}
return _handleEquals(node, left, right);
}
@override
Constant visitEqualsNull(EqualsNull node) {
final Constant expression = _evaluateSubexpression(node.expression);
if (expression is AbortConstant) return expression;
if (shouldBeUnevaluated) {
return unevaluated(node,
new EqualsNull(extract(expression))..fileOffset = node.fileOffset);
}
return _handleEquals(node, expression, nullConstant);
}
Constant _handleEquals(Expression node, Constant left, Constant right) {
if (left is NullConstant ||
left is BoolConstant ||
left is IntConstant ||
left is DoubleConstant ||
left is StringConstant ||
right is NullConstant) {
// [DoubleConstant] uses [identical] to determine equality, so we need
// to take the special cases into account.
return doubleSpecialCases(left, right) ?? makeBoolConstant(left == right);
} else {
return createErrorConstant(
node,
templateConstEvalInvalidEqualsOperandType.withArguments(
left, left.getType(_staticTypeContext), isNonNullableByDefault));
}
}
/// Converts integer constant types to an int value.
///
/// Returns [null] on unmatched [intFolder] and [value] type.
int _convertToInt(Constant value) {
if (intFolder is JsConstantIntFolder && value is DoubleConstant) {
return value.value.toInt();
} else if (intFolder is VmConstantIntFolder && value is IntConstant) {
return value.value;
}
return null;
}
Constant _handleInvocation(
Expression node, Name name, Constant receiver, List<Constant> arguments,
{List<DartType> typeArguments, Map<String, Constant> namedArguments}) {
final String op = name.text;
// TODO(kallentu): Handle all constant toString methods.
if (receiver is PrimitiveConstant &&
op == 'toString' &&
enableConstFunctions) {
return new StringConstant(receiver.value.toString());
}
// Handle == and != first (it's common between all types). Since `a != b` is
// parsed as `!(a == b)` it is handled implicitly through ==.
if (arguments.length == 1 && op == '==') {
final Constant right = arguments[0];
return _handleEquals(node, receiver, right);
}
// This is a white-listed set of methods we need to support on constants.
if (receiver is StringConstant) {
if (arguments.length == 1) {
final Constant other = arguments[0];
switch (op) {
case '+':
if (other is StringConstant) {
return canonicalize(
new StringConstant(receiver.value + other.value));
}
return createErrorConstant(
node,
templateConstEvalInvalidBinaryOperandType.withArguments(
'+',
receiver,
typeEnvironment.coreTypes.stringLegacyRawType,
other.getType(_staticTypeContext),
isNonNullableByDefault));
case '[]':
if (enableConstFunctions) {
if (intFolder.isInt(other)) {
int index = _convertToInt(other);
assert(index != null);
if (index < 0 || index >= receiver.value.length) {
return new _AbortDueToThrowConstant(
node, new RangeError.index(index, receiver.value));
}
return canonicalize(new StringConstant(receiver.value[index]));
}
return createErrorConstant(
node,
templateConstEvalInvalidBinaryOperandType.withArguments(
'[]',
receiver,
typeEnvironment.coreTypes.intNonNullableRawType,
other.getType(_staticTypeContext),
isNonNullableByDefault));
}
}
}
} else if (intFolder.isInt(receiver)) {
if (arguments.length == 0) {
return canonicalize(intFolder.foldUnaryOperator(node, op, receiver));
} else if (arguments.length == 1) {
final Constant other = arguments[0];
if (intFolder.isInt(other)) {
return canonicalize(
intFolder.foldBinaryOperator(node, op, receiver, other));
} else if (other is DoubleConstant) {
if ((op == '|' || op == '&' || op == '^') ||
(op == '<<' || op == '>>' || op == '>>>')) {
return createErrorConstant(
node,
templateConstEvalInvalidBinaryOperandType.withArguments(
op,
other,
typeEnvironment.coreTypes.intLegacyRawType,
other.getType(_staticTypeContext),
isNonNullableByDefault));
}
num receiverValue = (receiver as PrimitiveConstant<num>).value;
return canonicalize(evaluateBinaryNumericOperation(
op, receiverValue, other.value, node));
}
return createErrorConstant(
node,
templateConstEvalInvalidBinaryOperandType.withArguments(
op,
receiver,
typeEnvironment.coreTypes.numLegacyRawType,
other.getType(_staticTypeContext),
isNonNullableByDefault));
}
} else if (receiver is DoubleConstant) {
if ((op == '|' || op == '&' || op == '^') ||
(op == '<<' || op == '>>' || op == '>>>')) {
return createErrorConstant(
node,
templateConstEvalInvalidBinaryOperandType.withArguments(
op,
receiver,
typeEnvironment.coreTypes.intLegacyRawType,
receiver.getType(_staticTypeContext),
isNonNullableByDefault));
}
if (arguments.length == 0) {
switch (op) {
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 as PrimitiveConstant<num>).value;
return canonicalize(
evaluateBinaryNumericOperation(op, receiver.value, value, node));
}
return createErrorConstant(
node,
templateConstEvalInvalidBinaryOperandType.withArguments(
op,
receiver,
typeEnvironment.coreTypes.numLegacyRawType,
other.getType(_staticTypeContext),
isNonNullableByDefault));
}
} else if (receiver is BoolConstant) {
if (arguments.length == 1) {
final Constant other = arguments[0];
if (other is BoolConstant) {
switch (op) {
case '|':
return canonicalize(
new BoolConstant(receiver.value || other.value));
case '&':
return canonicalize(
new BoolConstant(receiver.value && other.value));
case '^':
return canonicalize(
new BoolConstant(receiver.value != other.value));
}
}
}
} else if (receiver is NullConstant) {
return createErrorConstant(node, messageConstEvalNullValue);
} else if (receiver is ListConstant && enableConstFunctions) {
if (arguments.length == 1) {
final Constant other = arguments[0];
switch (op) {
case '[]':
if (intFolder.isInt(other)) {
int index = _convertToInt(other);
assert(index != null);
if (index < 0 || index >= receiver.entries.length) {
return new _AbortDueToThrowConstant(
node, new RangeError.index(index, receiver.entries));
}
return receiver.entries[index];
}
return createErrorConstant(
node,
templateConstEvalInvalidBinaryOperandType.withArguments(
'[]',
receiver,
typeEnvironment.coreTypes.intNonNullableRawType,
other.getType(_staticTypeContext),
isNonNullableByDefault));
}
}
} else if (receiver is MapConstant && enableConstFunctions) {
if (arguments.length == 1) {
final Constant other = arguments[0];
switch (op) {
case '[]':
final ConstantMapEntry mapEntry = receiver.entries
.firstWhere((entry) => entry.key == other, orElse: () => null);
// Null value if key is not in the map.
return mapEntry?.value ?? new NullConstant();
}
}
} else if (receiver is InstanceConstant && enableConstFunctions) {
final Class instanceClass = receiver.classNode;
assert(typeEnvironment.hierarchy is ClassHierarchy);
final Member member = (typeEnvironment.hierarchy as ClassHierarchy)
.getDispatchTarget(instanceClass, name);
final FunctionNode function = member.function;
// TODO(kallentu): Implement [Object] class methods which have backend
// specific functions that cannot be run by the constant evaluator.
final bool isObjectMember = member.enclosingClass != null &&
member.enclosingClass.name == "Object";
if (function != null && !isObjectMember) {
return withNewInstanceBuilder(instanceClass, typeArguments, () {
final EvaluationEnvironment newEnv = new EvaluationEnvironment();
for (int i = 0; i < instanceClass.typeParameters.length; i++) {
newEnv.addTypeParameterValue(
instanceClass.typeParameters[i], receiver.typeArguments[i]);
}
// Ensure that fields are visible for instance access.
receiver.fieldValues.forEach((Reference fieldRef, Constant value) =>
instanceBuilder.setFieldValue(fieldRef.asField, value));
return _handleFunctionInvocation(
function, receiver.typeArguments, arguments, namedArguments,
functionEnvironment: newEnv);
});
}
switch (op) {
case 'toString':
// Default value for toString() of instances.
return new StringConstant(
"Instance of '${receiver.classReference.toStringInternal()}'");
}
}
return createErrorConstant(
node,
templateConstEvalInvalidMethodInvocation.withArguments(
op, receiver, isNonNullableByDefault));
}
@override
Constant visitMethodInvocation(MethodInvocation node) {
// We have no support for generic method invocation at the moment.
if (node.arguments.types.isNotEmpty && !enableConstFunctions) {
return createInvalidExpressionConstant(node, "generic method invocation");
}
// We have no support for method invocation with named arguments at the
// moment.
if (node.arguments.named.isNotEmpty && !enableConstFunctions) {
return createInvalidExpressionConstant(
node, "method invocation with named arguments");
}
final Constant receiver = _evaluateSubexpression(node.receiver);
if (receiver is AbortConstant) return receiver;
final List<Constant> arguments =
_evaluatePositionalArguments(node.arguments);
if (arguments == null && _gotError != null) {
AbortConstant error = _gotError;
_gotError = null;
return error;
}
assert(_gotError == null);
assert(arguments != null);
if (enableConstFunctions) {
// Evaluate type arguments of the method invoked.
List<DartType> types = _evaluateTypeArguments(node, node.arguments);
if (types == null && _gotError != null) {
AbortConstant error = _gotError;
_gotError = null;
return error;
}
assert(_gotError == null);
assert(types != null);
// Evaluate named arguments of the method invoked.
final Map<String, Constant> named =
_evaluateNamedArguments(node.arguments);
if (named == null && _gotError != null) {
AbortConstant error = _gotError;
_gotError = null;
return error;
}
assert(_gotError == null);
assert(named != null);
if (receiver is FunctionValue) {
return _handleFunctionInvocation(
receiver.function, types, arguments, named,
functionEnvironment: receiver.environment);
}
return _handleInvocation(node, node.name, receiver, arguments,
typeArguments: types, namedArguments: named);
}
if (shouldBeUnevaluated) {
return unevaluated(
node,
new MethodInvocation(
extract(receiver),
node.name,
unevaluatedArguments(arguments, {}, node.arguments.types),
node.interfaceTarget)
..fileOffset = node.fileOffset
..flags = node.flags);
}
return _handleInvocation(node, node.name, receiver, arguments);
}
@override
Constant visitLogicalExpression(LogicalExpression node) {
final Constant left = _evaluateSubexpression(node.left);
if (left is AbortConstant) return left;
if (shouldBeUnevaluated) {
enterLazy();
Constant right = _evaluateSubexpression(node.right);
if (right is AbortConstant) return right;
leaveLazy();
return unevaluated(
node,
new LogicalExpression(
extract(left), node.operatorEnum, extract(right)));
}
switch (node.operatorEnum) {
case LogicalExpressionOperator.OR:
if (left is BoolConstant) {
if (left.value) return trueConstant;
final Constant right = _evaluateSubexpression(node.right);
if (right is AbortConstant) return right;
if (right is BoolConstant || right is UnevaluatedConstant) {
return right;
}
return createErrorConstant(
node,
templateConstEvalInvalidBinaryOperandType.withArguments(
logicalExpressionOperatorToString(node.operatorEnum),
left,
typeEnvironment.coreTypes.boolLegacyRawType,
right.getType(_staticTypeContext),
isNonNullableByDefault));
}
return createErrorConstant(
node,
templateConstEvalInvalidMethodInvocation.withArguments(
logicalExpressionOperatorToString(node.operatorEnum),
left,
isNonNullableByDefault));
case LogicalExpressionOperator.AND:
if (left is BoolConstant) {
if (!left.value) return falseConstant;
final Constant right = _evaluateSubexpression(node.right);
if (right is AbortConstant) return right;
if (right is BoolConstant || right is UnevaluatedConstant) {
return right;
}
return createErrorConstant(
node,
templateConstEvalInvalidBinaryOperandType.withArguments(
logicalExpressionOperatorToString(node.operatorEnum),
left,
typeEnvironment.coreTypes.boolLegacyRawType,
right.getType(_staticTypeContext),
isNonNullableByDefault));
}
return createErrorConstant(
node,
templateConstEvalInvalidMethodInvocation.withArguments(
logicalExpressionOperatorToString(node.operatorEnum),
left,
isNonNullableByDefault));
default:
// Probably unreachable.
return createErrorConstant(
node,
templateConstEvalInvalidMethodInvocation.withArguments(
logicalExpressionOperatorToString(node.operatorEnum),
left,
isNonNullableByDefault));
}
}
@override
Constant visitConditionalExpression(ConditionalExpression node) {
final Constant condition = _evaluateSubexpression(node.condition);
if (condition is AbortConstant) return condition;
if (condition == trueConstant) {
return _evaluateSubexpression(node.then);
} else if (condition == falseConstant) {
return _evaluateSubexpression(node.otherwise);
} else if (shouldBeUnevaluated) {
enterLazy();
Constant then = _evaluateSubexpression(node.then);
if (then is AbortConstant) return then;
Constant otherwise = _evaluateSubexpression(node.otherwise);
if (otherwise is AbortConstant) return otherwise;
leaveLazy();
return unevaluated(
node,
new ConditionalExpression(extract(condition), extract(then),
extract(otherwise), node.staticType));
} else {
return createErrorConstant(
node.condition,
templateConstEvalInvalidType.withArguments(
condition,
typeEnvironment.coreTypes.boolLegacyRawType,
condition.getType(_staticTypeContext),
isNonNullableByDefault));
}
}
@override
Constant visitInstanceGet(InstanceGet node) {
if (node.receiver is ThisExpression) {
// Probably unreachable unless trying to evaluate non-const stuff as
// const.
// Access "this" during instance creation.
if (instanceBuilder == null) {
return createErrorConstant(node, messageNotAConstantExpression);
}
for (final Field field in instanceBuilder.fields.keys) {
if (field.name == node.name) {
return instanceBuilder.fields[field];
}
}
// Meant as a "stable backstop for situations where Fasta fails to
// rewrite various erroneous constructs into invalid expressions".
// Probably unreachable.
return createInvalidExpressionConstant(node,
'Could not evaluate field get ${node.name} on incomplete instance');
}
final Constant receiver = _evaluateSubexpression(node.receiver);
if (receiver is AbortConstant) return receiver;
if (receiver is StringConstant && node.name.text == 'length') {
return canonicalize(intFolder.makeIntConstant(receiver.value.length));
} else if (shouldBeUnevaluated) {
return unevaluated(
node,
new InstanceGet(node.kind, extract(receiver), node.name,
resultType: node.resultType,
interfaceTarget: node.interfaceTarget));
} else if (receiver is NullConstant) {
return createErrorConstant(node, messageConstEvalNullValue);
} else if (receiver is InstanceConstant && enableConstFunctions) {
for (final Reference fieldRef in receiver.fieldValues.keys) {
final Field field = fieldRef.asField;
if (field.name == node.name) {
return receiver.fieldValues[fieldRef];
}
}
}
return createErrorConstant(
node,
templateConstEvalInvalidPropertyGet.withArguments(
node.name.text, receiver, isNonNullableByDefault));
}
@override
Constant visitDynamicGet(DynamicGet node) {
final Constant receiver = _evaluateSubexpression(node.receiver);
if (receiver is AbortConstant) return receiver;
if (receiver is StringConstant && node.name.text == 'length') {
return canonicalize(intFolder.makeIntConstant(receiver.value.length));
} else if (shouldBeUnevaluated) {
return unevaluated(
node, new DynamicGet(node.kind, extract(receiver), node.name));
} else if (receiver is NullConstant) {
return createErrorConstant(node, messageConstEvalNullValue);
}
return createErrorConstant(
node,
templateConstEvalInvalidPropertyGet.withArguments(
node.name.text, receiver, isNonNullableByDefault));
}
@override
Constant visitInstanceTearOff(InstanceTearOff node) {
final Constant receiver = _evaluateSubexpression(node.receiver);
if (receiver is AbortConstant) return receiver;
return createErrorConstant(
node,
templateConstEvalInvalidPropertyGet.withArguments(
node.name.text, receiver, isNonNullableByDefault));
}
@override
Constant visitFunctionTearOff(FunctionTearOff node) {
final Constant receiver = _evaluateSubexpression(node.receiver);
if (receiver is AbortConstant) return receiver;
return createErrorConstant(
node,
templateConstEvalInvalidPropertyGet.withArguments(
Name.callName.text, receiver, isNonNullableByDefault));
}
@override
Constant visitPropertyGet(PropertyGet node) {
if (node.receiver is ThisExpression) {
// Probably unreachable unless trying to evaluate non-const stuff as
// const.
// Access "this" during instance creation.
if (instanceBuilder == null) {
return createErrorConstant(node, messageNotAConstantExpression);
}
for (final Field field in instanceBuilder.fields.keys) {
if (field.name == node.name) {
return instanceBuilder.fields[field];
}
}
// Meant as a "stable backstop for situations where Fasta fails to
// rewrite various erroneous constructs into invalid expressions".
// Probably unreachable.
return createInvalidExpressionConstant(node,
'Could not evaluate field get ${node.name} on incomplete instance');
}
final Constant receiver = _evaluateSubexpression(node.receiver);
if (receiver is AbortConstant) return receiver;
if (receiver is StringConstant && node.name.text == 'length') {
return canonicalize(intFolder.makeIntConstant(receiver.value.length));
} else if (shouldBeUnevaluated) {
return unevaluated(node,
new PropertyGet(extract(receiver), node.name, node.interfaceTarget));
} else if (receiver is NullConstant) {
return createErrorConstant(node, messageConstEvalNullValue);
} else if (receiver is ListConstant && enableConstFunctions) {
switch (node.name.text) {
case 'first':
if (receiver.entries.isEmpty) {
return new _AbortDueToThrowConstant(
node, new StateError('No element'));
}
return receiver.entries.first;
case 'hashCode':
return new IntConstant(receiver.entries.hashCode);
case 'isEmpty':
return new BoolConstant(receiver.entries.isEmpty);
case 'isNotEmpty':
return new BoolConstant(receiver.entries.isNotEmpty);
// TODO(kallentu): case 'iterator'
case 'last':
if (receiver.entries.isEmpty) {
return new _AbortDueToThrowConstant(
node, new StateError('No element'));
}
return receiver.entries.last;
case 'length':
return new IntConstant(receiver.entries.length);
// TODO(kallentu): case 'reversed'
case 'runtimeType':
return new TypeLiteralConstant(receiver.typeArgument);
case 'single':
if (receiver.entries.isEmpty) {
return new _AbortDueToThrowConstant(
node, new StateError('No element'));
} else if (receiver.entries.length > 1) {
return new _AbortDueToThrowConstant(
node, new StateError('Too many elements'));
}
return receiver.entries.single;
}
} else if (receiver is InstanceConstant && enableConstFunctions) {
for (final Reference fieldRef in receiver.fieldValues.keys) {
final Field field = fieldRef.asField;
if (field.name == node.name) {
return receiver.fieldValues[fieldRef];
}
}
}
return createErrorConstant(
node,
templateConstEvalInvalidPropertyGet.withArguments(
node.name.text, receiver, isNonNullableByDefault));
}
@override
Constant visitLet(Let node) {
Constant value = _evaluateSubexpression(node.variable.initializer);
if (value is AbortConstant) return value;
env.addVariableValue(node.variable, value);
return _evaluateSubexpression(node.body);
}
@override
Constant visitVariableGet(VariableGet node) {
// Not every variable which a [VariableGet] refers to must be marked as
// constant. For example function parameters as well as constructs
// desugared to [Let] expressions are ok.
//
// TODO(kustermann): The heuristic of allowing all [VariableGet]s on [Let]
// variables might allow more than it should.
final VariableDeclaration variable = node.variable;
if (enableConstFunctions) {
return env.lookupVariable(variable) ??
createErrorConstant(node,
templateConstEvalGetterNotFound.withArguments(variable.name));
} else {
if (variable.parent is Let || _isFormalParameter(variable)) {
return env.lookupVariable(node.variable) ??
createErrorConstant(
node,
templateConstEvalNonConstantVariableGet
.withArguments(variable.name));
}
if (variable.isConst) {
return _evaluateSubexpression(variable.initializer);
}
}
return createInvalidExpressionConstant(
node, 'Variable get of a non-const variable.');
}
@override
Constant visitVariableSet(VariableSet node) {
if (enableConstFunctions) {
final VariableDeclaration variable = node.variable;
Constant value = _evaluateSubexpression(node.value);
if (value is AbortConstant) return value;
return env.updateVariableValue(variable, value) ??
createInvalidExpressionConstant(
node, 'Variable set of an unknown value.');
}
return defaultExpression(node);
}
/// Computes the constant for [expression] defined in the context of [member].
///
/// This compute the constant as seen in the current evaluation mode even when
/// the constant is defined in a library compiled with the agnostic evaluation
/// mode.
Constant _evaluateExpressionInContext(Member member, Expression expression) {
StaticTypeContext oldStaticTypeContext = _staticTypeContext;
_staticTypeContext = new StaticTypeContext(member, typeEnvironment);
Constant constant = _evaluateSubexpression(expression);
if (constant is! AbortConstant) {
if (_staticTypeContext.nonNullableByDefaultCompiledMode ==
NonNullableByDefaultCompiledMode.Agnostic &&
evaluationMode == EvaluationMode.weak) {
constant = _weakener.visitConstant(constant) ?? constant;
}
}
_staticTypeContext = oldStaticTypeContext;
return constant;
}
@override
Constant visitStaticGet(StaticGet node) {
return withNewEnvironment(() {
final Member target = node.target;
if (target is Field) {
if (target.isConst) {
return _evaluateExpressionInContext(target, target.initializer);
}
return createErrorConstant(
node,
templateConstEvalInvalidStaticInvocation
.withArguments(target.name.text));
} else if (target is Procedure) {
if (target.kind == ProcedureKind.Method) {
return canonicalize(new TearOffConstant(target));
}
return createErrorConstant(
node,
templateConstEvalInvalidStaticInvocation
.withArguments(target.name.text));
} else {
return createInvalidExpressionConstant(
node, 'No support for ${target.runtimeType} in a static-get.');
}
});
}
@override
Constant visitStaticTearOff(StaticTearOff node) {
return withNewEnvironment(() {
final Member target = node.target;
if (target is Procedure) {
if (target.kind == ProcedureKind.Method) {
return canonicalize(new TearOffConstant(target));
}
return createErrorConstant(
node,
templateConstEvalInvalidStaticInvocation
.withArguments(target.name.text));
} else {
return createInvalidExpressionConstant(
node, 'No support for ${target.runtimeType} in a static tear-off.');
}
});
}
@override
Constant visitStringConcatenation(StringConcatenation node) {
final List<Object> concatenated = <Object>[new StringBuffer()];
for (int i = 0; i < node.expressions.length; i++) {
Constant constant = _evaluateSubexpression(node.expressions[i]);
if (constant is AbortConstant) return constant;
if (constant is PrimitiveConstant<Object>) {
String value;
if (constant is DoubleConstant && intFolder.isInt(constant)) {
value = new BigInt.from(constant.value).toString();
} else {
value = constant.value.toString();
}
Object last = concatenated.last;
if (last is StringBuffer) {
last.write(value);
} else {
concatenated.add(new StringBuffer(value));
}
} else if (shouldBeUnevaluated) {
// The constant is either unevaluated or a non-primitive in an
// unevaluated context. In both cases we defer the evaluation and/or
// error reporting till later.
concatenated.add(constant);
} else {
return createErrorConstant(
node,
templateConstEvalInvalidStringInterpolationOperand.withArguments(
constant, isNonNullableByDefault));
}
}
if (concatenated.length > 1) {
final List<Expression> expressions =
new List<Expression>.filled(concatenated.length, null);
for (int i = 0; i < concatenated.length; i++) {
Object value = concatenated[i];
if (value is StringBuffer) {
expressions[i] = new ConstantExpression(
canonicalize(new StringConstant(value.toString())));
} else {
// The value is either unevaluated constant or a non-primitive
// constant in an unevaluated expression.
expressions[i] = extract(value);
}
}
return unevaluated(node, new StringConcatenation(expressions));
}
return canonicalize(new StringConstant(concatenated.single.toString()));
}
Constant _getFromEnvironmentDefaultValue(Procedure target) {
VariableDeclaration variable = target.function.namedParameters
.singleWhere((v) => v.name == 'defaultValue');
return variable.initializer != null
? _evaluateExpressionInContext(target, variable.initializer)
:
// Not reachable unless a defaultValue in fromEnvironment in dart:core
// becomes null.
nullConstant;
}
Constant _handleFromEnvironment(
Procedure target, StringConstant name, Map<String, Constant> named) {
String value = environmentDefines[name.value];
Constant defaultValue = named["defaultValue"];
if (target.enclosingClass == coreTypes.boolClass) {
Constant boolConstant;
if (value == "true") {
boolConstant = trueConstant;
} else if (value == "false") {
boolConstant = falseConstant;
} else if (defaultValue != null) {
if (defaultValue is BoolConstant) {
boolConstant = makeBoolConstant(defaultValue.value);
} else if (defaultValue is NullConstant) {
boolConstant = nullConstant;
} else {
// Probably unreachable.
boolConstant = falseConstant;
}
} else {
boolConstant = _getFromEnvironmentDefaultValue(target);
}
return boolConstant;
} else if (target.enclosingClass == coreTypes.intClass) {
int intValue = value != null ? int.tryParse(value)