blob: 388009a4bc363042d1abad7336b3b7771d6fa5a0 [file] [log] [blame]
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
/// This library implements a kernel2kernel constant evaluation transformation.
///
/// Even though it is expected that the frontend does not emit kernel AST which
/// contains compile-time errors, this transformation still performs some
/// 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,
{required bool evaluateAnnotations,
required bool desugarSets,
required bool enableTripleShift,
required bool enableConstFunctions,
required bool errorOnUnevaluatedConstant,
CoreTypes? coreTypes,
ClassHierarchy? hierarchy}) {
// ignore: unnecessary_null_comparison
assert(evaluateAnnotations != null);
// ignore: unnecessary_null_comparison
assert(desugarSets != null);
// ignore: unnecessary_null_comparison
assert(enableTripleShift != null);
// ignore: unnecessary_null_comparison
assert(enableConstFunctions != null);
// ignore: unnecessary_null_comparison
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,
{required bool evaluateAnnotations,
required bool enableTripleShift,
required bool enableConstFunctions,
required bool errorOnUnevaluatedConstant}) {
// ignore: unnecessary_null_comparison
assert(evaluateAnnotations != null);
// ignore: unnecessary_null_comparison
assert(enableTripleShift != null);
// ignore: unnecessary_null_comparison
assert(enableConstFunctions != null);
// ignore: unnecessary_null_comparison
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}) {
// ignore: unnecessary_null_comparison
assert(evaluateAnnotations != null);
// ignore: unnecessary_null_comparison
assert(enableTripleShift != null);
// ignore: unnecessary_null_comparison
assert(enableConstFunctions != null);
// ignore: unnecessary_null_comparison
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 (MapEntry<Reference, Constant> entry in node.fieldValues.entries) {
Reference reference = entry.key;
Constant? value = visitConstant(entry.value);
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);
Expression? initializer = variable.initializer;
if (initializer != null) {
variable.initializer =
evaluateAndTransformWithContext(variable, initializer)
..parent = variable;
}
}
for (final VariableDeclaration variable in node.namedParameters) {
transformAnnotations(variable.annotations, variable);
Expression? initializer = variable.initializer;
if (initializer != null) {
variable.initializer =
evaluateAndTransformWithContext(variable, initializer)
..parent = variable;
}
}
if (node.body != null) {
node.body = transform(node.body!)..parent = node;
}
return node;
}
@override
TreeNode visitFunctionDeclaration(
FunctionDeclaration node, TreeNode? removalSentinel) {
if (enableConstFunctions) {
// ignore: unnecessary_null_comparison
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
TreeNode visitVariableDeclaration(
VariableDeclaration node, TreeNode? removalSentinel) {
transformAnnotations(node.annotations, node);
Expression? initializer = node.initializer;
if (initializer != null) {
if (node.isConst) {
final Constant constant = evaluateWithContext(node, initializer);
constantEvaluator.env.addVariableValue(node, constant);
initializer = node.initializer =
makeConstantExpression(constant, initializer)..parent = node;
// If this constant is inlined, remove it.
if (!keepLocals && shouldInline(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(initializer)..parent = node;
}
}
return node;
}
@override
TreeNode visitField(Field node, TreeNode? removalSentinel) {
StaticTypeContext? oldStaticTypeContext = _staticTypeContext;
_staticTypeContext = new StaticTypeContext(node, typeEnvironment);
TreeNode result = constantEvaluator.withNewEnvironment(() {
Expression? initializer = node.initializer;
if (node.isConst) {
transformAnnotations(node.annotations, node);
initializer = node.initializer =
evaluateAndTransformWithContext(node, initializer!)..parent = node;
// If this constant is inlined, remove it.
if (!keepFields && shouldInline(initializer)) {
return removalSentinel!;
}
} else {
transformAnnotations(node.annotations, node);
if (initializer != null) {
node.initializer = transform(initializer)..parent = node;
}
}
return node;
});
_staticTypeContext = oldStaticTypeContext;
return result;
}
// Handle use-sites of constants (and "inline" constant expressions):
@override
TreeNode 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
TreeNode 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
TreeNode 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
TreeNode 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
TreeNode visitSwitchCase(SwitchCase node, TreeNode? removalSentinel) {
transformExpressions(node.expressions, node);
return super.visitSwitchCase(node, removalSentinel);
}
@override
TreeNode visitSwitchStatement(
SwitchStatement node, TreeNode? removalSentinel) {
TreeNode result = super.visitSwitchStatement(node, removalSentinel);
Library library = constantEvaluator.libraryOf(node);
// ignore: unnecessary_null_comparison
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)) {
constantEvaluator.errorReporter.report(
constantEvaluator.createLocatedMessage(
caseExpression,
templateConstEvalCaseImplementsEqual.withArguments(
caseExpression.constant,
constantEvaluator.isNonNullableByDefault)),
null);
}
} else {
// If caseExpression is not ConstantExpression, an error is reported
// elsewhere.
}
}
}
}
return result;
}
@override
TreeNode 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
TreeNode visitListLiteral(ListLiteral node, TreeNode? removalSentinel) {
if (node.isConst) {
return evaluateAndTransformWithContext(node, node);
}
return super.visitListLiteral(node, removalSentinel);
}
@override
TreeNode visitListConcatenation(
ListConcatenation node, TreeNode? removalSentinel) {
return evaluateAndTransformWithContext(node, node);
}
@override
TreeNode visitSetLiteral(SetLiteral node, TreeNode? removalSentinel) {
if (node.isConst) {
return evaluateAndTransformWithContext(node, node);
}
return super.visitSetLiteral(node, removalSentinel);
}
@override
TreeNode visitSetConcatenation(
SetConcatenation node, TreeNode? removalSentinel) {
return evaluateAndTransformWithContext(node, node);
}
@override
TreeNode visitMapLiteral(MapLiteral node, TreeNode? removalSentinel) {
if (node.isConst) {
return evaluateAndTransformWithContext(node, node);
}
return super.visitMapLiteral(node, removalSentinel);
}
@override
TreeNode visitTypeLiteral(TypeLiteral node, TreeNode? removalSentinel) {
if (!containsFreeTypeVariables(node.type)) {
return evaluateAndTransformWithContext(node, node);
}
return super.visitTypeLiteral(node, removalSentinel);
}
@override
TreeNode visitMapConcatenation(
MapConcatenation node, TreeNode? removalSentinel) {
return evaluateAndTransformWithContext(node, node);
}
@override
TreeNode visitConstructorInvocation(
ConstructorInvocation node, TreeNode? removalSentinel) {
if (node.isConst) {
return evaluateAndTransformWithContext(node, node);
}
return super.visitConstructorInvocation(node, removalSentinel);
}
@override
TreeNode visitStaticInvocation(
StaticInvocation node, TreeNode? removalSentinel) {
if (node.isConst) {
return evaluateAndTransformWithContext(node, node);
}
return super.visitStaticInvocation(node, removalSentinel);
}
@override
TreeNode 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;
late 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, Constant?> nodeCache;
final CloneVisitorNotMembers cloner = new CloneVisitorNotMembers();
late 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 = false; // Any children that were left unevaluated?
int lazyDepth = -1; // 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;
late 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:
type = norm(coreTypes, type);
return computeConstCanonicalType(type, coreTypes,
isNonNullableByDefault: isNonNullableByDefault) ??
type;
}
}
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) {
type = norm(coreTypes, type);
return computeConstCanonicalType(type, coreTypes,
isNonNullableByDefault: isNonNullableByDefault) ??
type;
}).toList();
}
}
LocatedMessage createLocatedMessage(TreeNode? node, Message message) {
Uri? uri = getFileUri(node);
if (uri == null) {
// TODO(johnniwinther): Ensure that we always have a uri.
return message.withoutLocation();
}
int offset = getFileOffset(uri, node);
return message.withLocation(uri, offset, noLength);
}
// TODO(johnniwinther): Avoid this by adding a current file uri field.
Uri? getFileUri(TreeNode? node) {
while (node != null) {
if (node is FileUriNode) {
return node.fileUri;
}
node = node.parent;
}
return null;
}
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 LocatedMessage locatedMessageActualError =
createLocatedMessage(result.node, result.message);
final List<LocatedMessage> contextMessages = <LocatedMessage>[
locatedMessageActualError
];
if (result.context != null) contextMessages.addAll(result.context!);
if (contextNode != null && contextNode != result.node) {
contextMessages
.add(createLocatedMessage(contextNode, messageConstEvalContext));
}
{
final LocatedMessage locatedMessage =
createLocatedMessage(node, messageConstEvalStartingPoint);
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 LocatedMessage locatedMessageActualError =
createLocatedMessage(result.node, message!);
final List<LocatedMessage> contextMessages = <LocatedMessage>[
locatedMessageActualError
];
{
final LocatedMessage locatedMessage =
createLocatedMessage(node, messageConstEvalStartingPoint);
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) {
Constant? value = status.value;
if (value == null) {
// Void return type from executing the function body.
return new NullConstant();
}
return 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)) {
Constant? cachedResult = nodeCache[node];
if (cachedResult == null) {
// [null] is a sentinel value only used when still evaluating the same
// node.
return createErrorConstant(node, messageConstEvalCircularity);
}
result = cachedResult;
} else {
nodeCache[node] = null;
Constant evaluatedResult = node.accept(this);
if (evaluatedResult is AbortConstant) {
nodeCache.remove(node);
return evaluatedResult;
} else {
nodeCache[node] = evaluatedResult;
}
result = evaluatedResult;
}
} else {
bool sentinelInserted = false;
if (nodeCache.containsKey(node)) {
bool isRecursiveFunctionCall = node is MethodInvocation ||
node is InstanceInvocation ||
node is FunctionInvocation ||
node is LocalFunctionInvocation ||
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;
}
Constant evaluatedResult = node.accept(this);
if (sentinelInserted) {
nodeCache.remove(node);
}
if (evaluatedResult is AbortConstant) {
return evaluatedResult;
}
result = evaluatedResult;
}
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) {
AbortConstant error = _gotError!;
_gotError = null;
return error;
}
assert(_gotError == null);
// ignore: unnecessary_null_comparison
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 && !enableConstFunctions) {
return createInvalidExpressionConstant(node, "Non-constant list literal");
}
final ListConstantBuilder builder = new ListConstantBuilder(
node, convertType(node.typeArgument), this,
isMutable: !node.isConst);
// 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 (MapLiteralEntry 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 && !enableConstFunctions) {
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) {
AbortConstant error = _gotError!;
_gotError = null;
return error;
}
assert(_gotError == null);
// ignore: unnecessary_null_comparison
assert(positionals != null);
final Map<String, Constant>? named =
_evaluateNamedArguments(node.arguments);
if (named == null) {
AbortConstant error = _gotError!;
_gotError = null;
return error;
}
assert(_gotError == null);
// ignore: unnecessary_null_comparison
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) {
AbortConstant error = _gotError!;
_gotError = null;
return error;
}
assert(_gotError == null);
// ignore: unnecessary_null_comparison
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;
for (MapEntry<Reference, Expression> entry in node.fieldValues.entries) {
Reference fieldRef = entry.key;
Expression value = entry.value;
Constant constant = _evaluateSubexpression(value);
if (constant is AbortConstant) {
error = constant;
break;
}
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-'
};
// ignore: unnecessary_null_comparison
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) {
AbortConstant error = _gotError!;
_gotError = null;
return error;
}
assert(_gotError == null);
// ignore: unnecessary_null_comparison
assert(types != null);
List<Constant>? positionalArguments =
_evaluatePositionalArguments(init.arguments);
if (positionalArguments == null) {
AbortConstant error = _gotError!;
_gotError = null;
return error;
}
assert(_gotError == null);
// ignore: unnecessary_null_comparison
assert(positionalArguments != null);
Map<String, Constant>? namedArguments =
_evaluateNamedArguments(init.arguments);
if (namedArguments == null) {
AbortConstant error = _gotError!;
_gotError = null;
return error;
}
assert(_gotError == null);
// ignore: unnecessary_null_comparison
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) {
AbortConstant error = _gotError!;
_gotError = null;
return error;
}
assert(_gotError == null);
// ignore: unnecessary_null_comparison
assert(positionalArguments != null);
Map<String, Constant>? namedArguments =
_evaluateNamedArguments(init.arguments);
if (namedArguments == null) {
AbortConstant error = _gotError!;
_gotError = null;
return error;
}
assert(_gotError == null);
// ignore: unnecessary_null_comparison
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));
}
// ignore: unnecessary_null_comparison
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>? positionalArguments =
_evaluatePositionalArguments(node.arguments);
if (positionalArguments == null) {
AbortConstant error = _gotError!;
_gotError = null;
return error;
}
assert(_gotError == null);
// ignore: unnecessary_null_comparison
assert(positionalArguments != null);
if (shouldBeUnevaluated) {
return unevaluated(
node,
new DynamicInvocation(
node.kind,
extract(receiver),
node.name,
unevaluatedArguments(
positionalArguments, {}, node.arguments.types))
..fileOffset = node.fileOffset);
}
return _handleInvocation(node, node.name, receiver, positionalArguments,
arguments: node.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>? positionalArguments =
_evaluatePositionalArguments(node.arguments);
if (positionalArguments == null) {
AbortConstant error = _gotError!;
_gotError = null;
return error;
}
assert(_gotError == null);
// ignore: unnecessary_null_comparison
assert(positionalArguments != null);
if (shouldBeUnevaluated) {
return unevaluated(
node,
new InstanceInvocation(
node.kind,
extract(receiver),
node.name,
unevaluatedArguments(
positionalArguments, {}, node.arguments.types),
functionType: node.functionType,
interfaceTarget: node.interfaceTarget)
..fileOffset = node.fileOffset
..flags = node.flags);
}
return _handleInvocation(node, node.name, receiver, positionalArguments,
arguments: node.arguments);
}
@override
Constant visitFunctionInvocation(FunctionInvocation node) {
if (!enableConstFunctions) {
return createInvalidExpressionConstant(node, "function invocation");
}
final Constant receiver = _evaluateSubexpression(node.receiver);
if (receiver is AbortConstant) return receiver;
return _evaluateFunctionInvocation(node, receiver, node.arguments);
}
@override
Constant visitLocalFunctionInvocation(LocalFunctionInvocation node) {
if (!enableConstFunctions) {
return createInvalidExpressionConstant(node, "local function invocation");
}
final Constant receiver = env.lookupVariable(node.variable)!;
// ignore: unnecessary_null_comparison
assert(receiver != null);
if (receiver is AbortConstant) return receiver;
return _evaluateFunctionInvocation(node, receiver, node.arguments);
}
Constant _evaluateFunctionInvocation(
TreeNode node, Constant receiver, Arguments argumentsNode) {
final List<Constant>? arguments =
_evaluatePositionalArguments(argumentsNode);
if (arguments == null) {
AbortConstant error = _gotError!;
_gotError = null;
return error;
}
assert(_gotError == null);
// ignore: unnecessary_null_comparison
assert(arguments != null);
// Evaluate type arguments of the function invoked.
List<DartType>? types = _evaluateTypeArguments(node, argumentsNode);
if (types == null) {
AbortConstant error = _gotError!;
_gotError = null;
return error;
}
assert(_gotError == null);
// ignore: unnecessary_null_comparison
assert(types != null);
// Evaluate named arguments of the function invoked.
final Map<String, Constant>? named = _evaluateNamedArguments(argumentsNode);
if (named == null) {
AbortConstant error = _gotError!;
_gotError = null;
return error;
}
assert(_gotError == null);
// ignore: unnecessary_null_comparison
assert(named != null);
if (receiver is FunctionValue) {
return _handleFunctionInvocation(
receiver.function, types, arguments, named,
functionEnvironment: receiver.environment);
} else {
return createInvalidExpressionConstant(
node, "function invocation with invalid receiver");
}
}
@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));
}
}
Constant _handleInvocation(Expression node, Name name, Constant receiver,
List<Constant> positionalArguments,
{required Arguments arguments}) {
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 (positionalArguments.length == 1 && op == '==') {
final Constant right = positionalArguments[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 (positionalArguments.length == 1) {
final Constant other = positionalArguments[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) {
int? index = intFolder.asInt(other);
if (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 (positionalArguments.length == 0) {
return canonicalize(intFolder.foldUnaryOperator(node, op, receiver));
} else if (positionalArguments.length == 1) {
final Constant other = positionalArguments[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 (positionalArguments.length == 0) {
switch (op) {
case 'unary-':
return canonicalize(new DoubleConstant(-receiver.value));
}
} else if (positionalArguments.length == 1) {
final Constant other = positionalArguments[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 (positionalArguments.length == 1) {
final Constant other = positionalArguments[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 (positionalArguments.length == 1) {
final Constant other = positionalArguments[0];
switch (op) {
case '[]':
int? index = intFolder.asInt(other);
if (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));
case 'add':
if (receiver is MutableListConstant) {
receiver.entries.add(other);
return receiver;
}
return new _AbortDueToThrowConstant(node, new UnsupportedError(op));
}
}
} else if (receiver is MapConstant && enableConstFunctions) {
if (positionalArguments.length == 1) {
final Constant other = positionalArguments[0];
switch (op) {
case '[]':
for (ConstantMapEntry entry in receiver.entries) {
if (entry.key == other) {
return entry.value;
}
}
return new NullConstant();
}
}
} else if (enableConstFunctions) {
// Evaluate type arguments of the method invoked.
List<DartType>? typeArguments = _evaluateTypeArguments(node, arguments);
if (typeArguments == null) {
AbortConstant error = _gotError!;
_gotError = null;
return error;
}
assert(_gotError == null);
// ignore: unnecessary_null_comparison
assert(typeArguments != null);
// Evaluate named arguments of the method invoked.
final Map<String, Constant>? namedArguments =
_evaluateNamedArguments(arguments);
if (namedArguments == null) {
AbortConstant error = _gotError!;
_gotError = null;
return error;
}
assert(_gotError == null);
// ignore: unnecessary_null_comparison
assert(namedArguments != null);
if (receiver is FunctionValue && name == Name.callName) {
return _handleFunctionInvocation(receiver.function, typeArguments,
positionalArguments, namedArguments,
functionEnvironment: receiver.environment);
} else if (receiver is InstanceConstant) {
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) {
// TODO(johnniwinther): Make [typeArguments] and [namedArguments]
// required and non-nullable.
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,
positionalArguments, 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>? positionalArguments =
_evaluatePositionalArguments(node.arguments);
if (positionalArguments == null) {
AbortConstant error = _gotError!;
_gotError = null;
return error;
}
assert(_gotError == null);
// ignore: unnecessary_null_comparison
assert(positionalArguments != null);
if (shouldBeUnevaluated) {
return unevaluated(
node,
new MethodInvocation(
extract(receiver),
node.name,
unevaluatedArguments(
positionalArguments, {}, node.arguments.types),
node.interfaceTarget)
..fileOffset = node.fileOffset
..flags = node.flags);
}
return _handleInvocation(node, node.name, receiver, positionalArguments,
arguments: node.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 MapEntry<Field, Constant> entry
in instanceBuilder!.fields.entries) {
final Field field = entry.key;
if (field.name == node.name) {
return entry.value;
}
}
// 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 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 '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 '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 MapEntry<Reference, Constant> entry
in receiver.fieldValues.entries) {
final Field field = entry.key.asField;
if (field.name == node.name) {
return entry.value;
}
}
}
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 MapEntry<Field, Constant> entry
in instanceBuilder!.fields.entries) {
final Field field = entry.key;
if (field.name == node.name) {
return entry.value;
}
}
// 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 '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 '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 MapEntry<Reference, Constant> entry
in receiver.fieldValues.entries) {
final Field field = entry.key.asField;
if (field.name == node.name) {
return entry.value;
}
}
}
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 = n