blob: 58f6cf1f2d9048ef07727b708b77686c63a39529 [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 = 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>.generate(concatenated.length, (int i) {
Object value = concatenated[i];
if (value is StringBuffer) {
return new ConstantExpression(
canonicalize(new StringConstant(value.toString())));
} else {
// The value is either unevaluated constant or a non-primitive
// constant in an unevaluated expression.
return extract(value as Constant);
}
}, growable: false);
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) : null;
Constant intConstant;
if (intValue != null) {
bool negated = value!.startsWith('-');
intConstant = intFolder.makeIntConstant(intValue, unsigned: !negated);
} else if (defaultValue != null) {
if (intFolder.isInt(defaultValue)) {
intConstant = defaultValue;
} else {
intConstant = nullConstant;
}
} else {
intConstant = _getFromEnvironmentDefaultValue(target);
}
return canonicalize(intConstant);
} else if (target.enclosingClass == coreTypes.stringClass) {
Constant stringConstant;
if (value != null) {
stringConstant = canonicalize(new StringConstant(value));
} else if (defaultValue != null) {
if (defaultValue is StringConstant) {
stringConstant = defaultValue;
} else {
stringConstant = nullConstant;
}
} else {
stringConstant = _getFromEnvironmentDefaultValue(target);
}
return stringConstant;
}
// Unreachable until fromEnvironment is added to other classes in dart:core
// than bool, int and String.
throw new UnsupportedError(
'Unexpected fromEnvironment constructor: $target');
}
Constant _handleHasEnvironment(StringConstant name) {
return environmentDefines!.containsKey(name.value)
? trueConstant
: falseConstant;
}
@override
Constant visitStaticInvocation(StaticInvocation node) {
final Procedure target = node.target;
final Arguments arguments = node.arguments;
List<DartType>? types = _evaluateTypeArguments(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);
final List<Constant>? positionals = _evaluatePositionalArguments(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(arguments);
if (named == null) {
AbortConstant error = _gotError!;
_gotError = null;
return error;
}
assert(_gotError == null);
// ignore: unnecessary_null_comparison
assert(named != null);
if (shouldBeUnevaluated) {
return unevaluated(
node,
new StaticInvocation(
target, unevaluatedArguments(positionals, named, arguments.types),
isConst: true));
}
if (target.kind == ProcedureKind.Factory) {
if (target.isConst &&
target.enclosingLibrary == coreTypes.coreLibrary &&
positionals.length == 1 &&
(target.name.text == "fromEnvironment" ||
target.name.text == "hasEnvironment")) {
if (environmentDefines != null) {
// Evaluate environment constant.
Constant name = positionals.single;
if (name is StringConstant) {
if (target.name.text == "fromEnvironment") {
return _handleFromEnvironment(target, name, named);
} else {
return _handleHasEnvironment(name);
}
} else if (name is NullConstant) {
return createErrorConstant(node, messageConstEvalNullValue);
}
} else {
// Leave environment constant unevaluated.
return unevaluated(
node,
new StaticInvocation(target,
unevaluatedArguments(positionals, named, arguments.types),
isConst: true));
}
}
} else if (target.name.text == 'identical') {
// Ensure the "identical()" function comes from dart:core.
final TreeNode? parent = target.parent;
if (parent is Library && parent == coreTypes.coreLibrary) {
final Constant left = positionals[0];
final Constant right = positionals[1];
Constant evaluateIdentical() {
// Since we canonicalize constants during the evaluation, we can use
// identical here.
Constant result = makeBoolConstant(identical(left, right));
if (evaluationMode == EvaluationMode.agnostic) {
Constant? weakLeft = _weakener.visitConstant(left);
Constant? weakRight = _weakener.visitConstant(right);
if (weakLeft != null || weakRight != null) {
Constant weakResult = makeBoolConstant(
identical(weakLeft ?? left, weakRight ?? right));
if (!identical(result, weakResult)) {
return createErrorConstant(node, messageNonAgnosticConstant);
}
}
}
return result;
}
if (targetingJavaScript) {
// In JavaScript, we lower [identical] to `===`, so we need to take
// the double special cases into account.
return doubleSpecialCases(left, right) ?? evaluateIdentical();
}
return evaluateIdentical();
}
} else if (target.isExtensionMember) {
return createErrorConstant(node, messageConstEvalExtension);
} else if (enableConstFunctions && target.kind == ProcedureKind.Method) {
return _handleFunctionInvocation(
node.target.function, typeArguments, positionals, named);
}
String name = target.name.text;
if (target is Procedure && target.isFactory) {
if (name.isEmpty) {
name = target.enclosingClass!.name;
} else {
name = '${target.enclosingClass!.name}.${name}';
}
if (enableConstFunctions) {
return _handleFunctionInvocation(
node.target.function, typeArguments, positionals, named);
}
}
return createInvalidExpressionConstant(node, "Invocation of $name");
}
Constant _handleFunctionInvocation(
FunctionNode function,
List<DartType> typeArguments,
List<Constant> positionalArguments,
Map<String, Constant> namedArguments,
{EvaluationEnvironment? functionEnvironment}) {
Constant executeFunction() {
// Map arguments from caller to callee.
for (int i = 0; i < function.typeParameters.length; i++) {
env.addTypeParameterValue(function.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);
}
final Constant result = executeBody(function.body!);
if (result is NullConstant &&
function.returnType.nullability == Nullability.nonNullable) {
// Ensure that the evaluated constant returned is not null if the
// function has a non-nullable return type.
return createErrorConstant(
function,
templateConstEvalInvalidType.withArguments(
result,
function.returnType,
result.getType(_staticTypeContext!),
isNonNullableByDefault));
}
return result;
}
if (functionEnvironment != null) {
return withEnvironment(functionEnvironment, executeFunction);
}
return withNewEnvironment(executeFunction);
}
@override
Constant visitAsExpression(AsExpression node) {
final Constant constant = _evaluateSubexpression(node.operand);
if (constant is AbortConstant) return constant;
if (shouldBeUnevaluated) {
return unevaluated(
node,
new AsExpression(extract(constant), env.substituteType(node.type))
..isForNonNullableByDefault =
_staticTypeContext!.isNonNullableByDefault);
}
DartType? type = _evaluateDartType(node, node.type);
if (type == null) {
AbortConstant error = _gotError!;
_gotError = null;
return error;
}
assert(_gotError == null);
// ignore: unnecessary_null_comparison
assert(type != null);
return ensureIsSubtype(constant, type, node);
}
@override
Constant visitIsExpression(IsExpression node) {
final Constant constant = _evaluateSubexpression(node.operand);
if (constant is AbortConstant) return constant;
if (shouldBeUnevaluated) {
return unevaluated(
node,
new IsExpression(extract(constant), node.type)
..fileOffset = node.fileOffset
..flags = node.flags);
}
DartType? type = _evaluateDartType(node, node.type);
if (type == null) {
AbortConstant error = _gotError!;
_gotError = null;
return error;
}
assert(_gotError == null);
// ignore: unnecessary_null_comparison
assert(type != null);
bool performIs(Constant constant, {required bool strongMode}) {
// ignore: unnecessary_null_comparison
assert(strongMode != null);
if (strongMode) {
return isSubtype(constant, type, SubtypeCheckMode.withNullabilities);
} else {
// In weak checking mode: if e evaluates to a value v and v has runtime
// type S, an instance check e is T occurring in a legacy library or an
// opted-in library is evaluated as follows:
//
// If v is null and T is a legacy type,
// return LEGACY_SUBTYPE(T, NULL) || LEGACY_SUBTYPE(Object, T)
// If v is null and T is not a legacy type,
// return NNBD_SUBTYPE(NULL, T)
// Otherwise return LEGACY_SUBTYPE(S, T)
if (constant is NullConstant) {
if (type.nullability == Nullability.legacy) {
// `null is Null` is handled below.
return typeEnvironment.isSubtypeOf(type, const NullType(),
SubtypeCheckMode.ignoringNullabilities) ||
typeEnvironment.isSubtypeOf(typeEnvironment.objectLegacyRawType,
type, SubtypeCheckMode.ignoringNullabilities);
} else {
return typeEnvironment.isSubtypeOf(
const NullType(), type, SubtypeCheckMode.withNullabilities);
}
}
return isSubtype(
constant, type, SubtypeCheckMode.ignoringNullabilities);
}
}
switch (evaluationMode) {
case EvaluationMode.strong:
return makeBoolConstant(performIs(constant, strongMode: true));
case EvaluationMode.agnostic:
bool strongResult = performIs(constant, strongMode: true);
Constant weakConstant = _weakener.visitConstant(constant) ?? constant;
bool weakResult = performIs(weakConstant, strongMode: false);
if (strongResult != weakResult) {
return createErrorConstant(node, messageNonAgnosticConstant);
}
return makeBoolConstant(strongResult);
case EvaluationMode.weak:
return makeBoolConstant(performIs(constant, strongMode: false));
}
}
@override
Constant visitNot(Not node) {
final Constant constant = _evaluateSubexpression(node.operand);
if (constant is AbortConstant) return constant;
if (constant is BoolConstant) {
return makeBoolConstant(constant != trueConstant);
}
if (shouldBeUnevaluated) {
return unevaluated(node, new Not(extract(constant)));
}
return createErrorConstant(
node,
templateConstEvalInvalidType.withArguments(
constant,
typeEnvironment.coreTypes.boolLegacyRawType,
constant.getType(_staticTypeContext!),
isNonNullableByDefault));
}
@override
Constant visitNullCheck(NullCheck node) {
final Constant constant = _evaluateSubexpression(node.operand);
if (constant is AbortConstant) return constant;
if (constant is NullConstant) {
return createErrorConstant(node, messageConstEvalNonNull);
}
if (shouldBeUnevaluated) {
return unevaluated(node, new NullCheck(extract(constant)));
}
return constant;
}
@override
Constant visitSymbolLiteral(SymbolLiteral node) {
final Reference? libraryReference =
node.value.startsWith('_') ? libraryOf(node).reference : null;
return canonicalize(new SymbolConstant(node.value, libraryReference));
}
@override
Constant visitThrow(Throw node) {
if (enableConstFunctions) {
final Constant value = _evaluateSubexpression(node.expression);
if (value is AbortConstant) return value;
return new _AbortDueToThrowConstant(node, value);
}
return defaultExpression(node);
}
@override
Constant visitInstantiation(Instantiation node) {
final Constant constant = _evaluateSubexpression(node.expression);
if (constant is AbortConstant) return constant;
if (shouldBeUnevaluated) {
return unevaluated(
node,
new Instantiation(extract(constant),
node.typeArguments.map((t) => env.substituteType(t)).toList()));
}
if (constant is TearOffConstant) {
if (node.typeArguments.length ==
constant.procedure.function.typeParameters.length) {
List<DartType>? types = _evaluateDartTypes(node, node.typeArguments);
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);
return canonicalize(
new PartialInstantiationConstant(constant, typeArguments));
}
// Probably unreachable.
return createInvalidExpressionConstant(
node,
'The number of type arguments supplied in the partial instantiation '
'does not match the number of type arguments of the $constant.');
}
// The inner expression in an instantiation can never be null, since
// instantiations are only inferred on direct references to declarations.
// Probably unreachable.
return createInvalidExpressionConstant(
node, 'Only tear-off constants can be partially instantiated.');
}
@override
Constant visitConstructorTearOff(ConstructorTearOff node) {
return defaultExpression(node);
}
@override
Constant visitCheckLibraryIsLoaded(CheckLibraryIsLoaded node) {
return createErrorConstant(node,
templateConstEvalDeferredLibrary.withArguments(node.import.name!));
}
// Helper methods:
/// If both constants are DoubleConstant whose values would give different
/// results from == and [identical], return the result of ==. Otherwise
/// return null.
Constant? doubleSpecialCases(Constant a, Constant b) {
if (a is DoubleConstant && b is DoubleConstant) {
if (a.value.isNaN && b.value.isNaN) return falseConstant;
if (a.value == 0.0 && b.value == 0.0) return trueConstant;
}
if (a is DoubleConstant && b is IntConstant) {
return makeBoolConstant(a.value == b.value);
}
if (a is IntConstant && b is DoubleConstant) {
return makeBoolConstant(a.value == b.value);
}
return null;
}
bool hasPrimitiveEqual(Constant constant) {
if (intFolder.isInt(constant)) return true;
DartType type = constant.getType(_staticTypeContext!);
return !(type is InterfaceType && !classHasPrimitiveEqual(type.classNode));
}
bool classHasPrimitiveEqual(Class klass) {
bool? cached = primitiveEqualCache[klass];
if (cached != null) return cached;
for (Procedure procedure in klass.procedures) {
if (procedure.kind == ProcedureKind.Operator &&
procedure.name.text == '==' &&
!procedure.isAbstract &&
!procedure.isForwardingStub) {
return primitiveEqualCache[klass] = false;
}
}
if (klass.supertype == null) return true; // To be on the safe side
return primitiveEqualCache[klass] =
classHasPrimitiveEqual(klass.supertype!.classNode);
}
BoolConstant makeBoolConstant(bool value) =>
value ? trueConstant : falseConstant;
bool isSubtype(Constant constant, DartType type, SubtypeCheckMode mode) {
DartType constantType = constant.getType(_staticTypeContext!);
if (mode == SubtypeCheckMode.ignoringNullabilities) {
constantType = rawLegacyErasure(constantType) ?? constantType;
}
bool result = typeEnvironment.isSubtypeOf(constantType, type, mode);
if (targetingJavaScript && !result) {
if (constantType is InterfaceType &&
constantType.classNode == typeEnvironment.coreTypes.intClass) {
// Probably unreachable.
// With JS semantics, an integer is also a double.
result = typeEnvironment.isSubtypeOf(
new InterfaceType(typeEnvironment.coreTypes.doubleClass,
constantType.nullability, const <DartType>[]),
type,
mode);
} else if (intFolder.isInt(constant)) {
// With JS semantics, an integer valued double is also an int.
result = typeEnvironment.isSubtypeOf(
new InterfaceType(typeEnvironment.coreTypes.intClass,
constantType.nullability, const <DartType>[]),
type,
mode);
}
}
return result;
}
/// Note that this returns an error-constant on error and as such the
/// return value should be checked.
Constant ensureIsSubtype(Constant constant, DartType type, TreeNode node) {
bool result;
switch (evaluationMode) {
case EvaluationMode.strong:
result = isSubtype(constant, type, SubtypeCheckMode.withNullabilities);
break;
case EvaluationMode.agnostic:
bool strongResult =
isSubtype(constant, type, SubtypeCheckMode.withNullabilities);
Constant weakConstant = _weakener.visitConstant(constant) ?? constant;
bool weakResult = isSubtype(
weakConstant, type, SubtypeCheckMode.ignoringNullabilities);
if (strongResult != weakResult) {
return createErrorConstant(node, messageNonAgnosticConstant);
}
result = strongResult;
break;
case EvaluationMode.weak:
result =
isSubtype(constant, type, SubtypeCheckMode.ignoringNullabilities);
break;
}
if (!result) {
return createErrorConstant(
node,
templateConstEvalInvalidType.withArguments(constant, type,
constant.getType(_staticTypeContext!), isNonNullableByDefault));
}
return constant;
}
/// Returns the types on success and null on failure.
/// Note that on failure an errorConstant is saved in [_gotError].
List<DartType>? _evaluateTypeArguments(TreeNode node, Arguments arguments) {
return _evaluateDartTypes(node, arguments.types);
}
/// Returns the types on success and null on failure.
/// Note that on failure an errorConstant is saved in [_gotError].
List<DartType>? _evaluateSuperTypeArguments(TreeNode node, Supertype type) {
return _evaluateDartTypes(node, type.typeArguments);
}
/// Upon failure in certain procedure calls (e.g. [_evaluateDartTypes]) the
/// "error"-constant is saved here. Normally this should be null.
/// Once a caller calls such a procedure and it gives an error here,
/// the caller should fetch it an null-out this variable.
AbortConstant? _gotError;
/// Returns the types on success and null on failure.
/// Note that on failure an errorConstant is saved in [_gotError].
List<DartType>? _evaluateDartTypes(TreeNode node, List<DartType> types) {
// TODO: Once the frontend guarantees that there are no free type variables
// left over after substitution, we can enable this shortcut again:
// if (env.isEmpty) return types;
List<DartType> result =
new List<DartType>.filled(types.length, dummyDartType, growable: true);
for (int i = 0; i < types.length; i++) {
DartType? type = _evaluateDartType(node, types[i]);
if (type == null) {
return null;
}
assert(_gotError == null);
// ignore: unnecessary_null_comparison
assert(type != null);
result[i] = type;
}
return result;
}
/// Returns the type on success and null on failure.
/// Note that on failure an errorConstant is saved in [_gotError].
DartType? _evaluateDartType(TreeNode node, DartType type) {
final DartType result = env.substituteType(type);
if (!isInstantiated(result)) {
_gotError = createErrorConstant(
node,
templateConstEvalFreeTypeParameter.withArguments(
type, isNonNullableByDefault));
return null;
}
return result;
}
/// Returns the types on success and null on failure.
/// Note that on failure an errorConstant is saved in [_gotError].
List<Constant>? _evaluatePositionalArguments(Arguments arguments) {
List<Constant> result = new List<Constant>.filled(
arguments.positional.length, dummyConstant,
growable: true);
for (int i = 0; i < arguments.positional.length; i++) {
Constant constant = _evaluateSubexpression(arguments.positional[i]);
if (constant is AbortConstant) {
_gotError = constant;
return null;
}
result[i] = constant;
}
return result;
}
/// Returns the arguments on success and null on failure.
/// Note that on failure an errorConstant is saved in [_gotError].
Map<String, Constant>? _evaluateNamedArguments(Arguments arguments) {
if (arguments.named.isEmpty) return const <String, Constant>{};
final Map<String, Constant> named = {};
for (NamedExpression pair in arguments.named) {
if (_gotError != null) return null;
Constant constant = _evaluateSubexpression(pair.value);
if (constant is AbortConstant) {
_gotError = constant;
return null;
}
named[pair.name] = constant;
}
if (_gotError != null) return null;
return named;
}
Arguments unevaluatedArguments(List<Constant> positionalArgs,
Map<String, Constant> namedArgs, List<DartType> types) {
final List<Expression> positional =
new List<Expression>.filled(positionalArgs.length, dummyExpression);
final List<NamedExpression> named = new List<NamedExpression>.filled(
namedArgs.length, dummyNamedExpression);
for (int i = 0; i < positionalArgs.length; ++i) {
positional[i] = extract(positionalArgs[i]);
}
int i = 0;
namedArgs.forEach((String name, Constant value) {
named[i++] = new NamedExpression(name, extract(value));
});
return new Arguments(positional, named: named, types: types);
}
Constant canonicalize(Constant constant) {
// Don't use putIfAbsent to avoid the context allocation needed
// for the closure.
return canonicalizationCache[constant] ??= constant;
}
T withNewInstanceBuilder<T>(
Class klass, List<DartType> typeArguments, T fn()) {
InstanceBuilder? old = instanceBuilder;
instanceBuilder = new InstanceBuilder(this, klass, typeArguments);
T result = fn();
instanceBuilder = old;
return result;
}
T withNewEnvironment<T>(T fn()) {
final EvaluationEnvironment oldEnv = env;
if (enableConstFunctions) {
env = new EvaluationEnvironment.withParent(env);
} else {
env = new EvaluationEnvironment();
}
T result = fn();
env = oldEnv;
return result;
}
T withEnvironment<T>(EvaluationEnvironment newEnv, T fn()) {
final EvaluationEnvironment oldEnv = env;
env = newEnv;
T result = fn();
env = oldEnv;
return result;
}
/// Binary operation between two operands, at least one of which is a double.
Constant evaluateBinaryNumericOperation(
String op, num a, num b, Expression node) {
switch (op) {
case '+':
return new DoubleConstant((a + b) as double);
case '-':
return new DoubleConstant((a - b) as double);
case '*':
return new DoubleConstant((a * b) as double);
case '/':
return new DoubleConstant(a / b);
case '~/':
if (b == 0) {
return createErrorConstant(
node, templateConstEvalZeroDivisor.withArguments(op, '$a'));
}
return intFolder.truncatingDivide(node, a, b);
case '%':
return new DoubleConstant((a % b) as double);
}
switch (op) {
case '<':
return makeBoolConstant(a < b);
case '<=':
return makeBoolConstant(a <= b);
case '>=':
return makeBoolConstant(a >= b);
case '>':
return makeBoolConstant(a > b);
}
// Probably unreachable.
return createInvalidExpressionConstant(
node, "Unexpected binary numeric operation '$op'.");
}
// TODO(johnniwinther): Remove the need for this by adding a current library
// field.
Library libraryOf(TreeNode? node) {
// The tree structure of the kernel AST ensures we always have an enclosing
// library.
while (true) {
if (node is Library) return node;
node = node!.parent;
}
}
@override
Constant defaultBasicLiteral(BasicLiteral node) => defaultExpression(node);
@override
Constant visitAwaitExpression(AwaitExpression node) =>
defaultExpression(node);
@override
Constant visitBlockExpression(BlockExpression node) =>
defaultExpression(node);
@override
Constant visitDynamicSet(DynamicSet node) => defaultExpression(node);
@override
Constant visitInstanceGetterInvocation(InstanceGetterInvocation node) =>
defaultExpression(node);
@override
Constant visitInstanceSet(InstanceSet node) => defaultExpression(node);
@override
Constant visitLoadLibrary(LoadLibrary node) => defaultExpression(node);
@override
Constant visitPropertySet(PropertySet node) => defaultExpression(node);
@override
Constant visitRethrow(Rethrow node) => defaultExpression(node);
@override
Constant visitStaticSet(StaticSet node) => defaultExpression(node);
@override
Constant visitSuperMethodInvocation(SuperMethodInvocation node) =>
defaultExpression(node);
@override
Constant visitSuperPropertyGet(SuperPropertyGet node) =>
defaultExpression(node);
@override
Constant visitSuperPropertySet(SuperPropertySet node) =>
defaultExpression(node);
@override
Constant visitThisExpression(ThisExpression node) => defaultExpression(node);
}
class StatementConstantEvaluator extends StatementVisitor<ExecutionStatus> {
ConstantEvaluator exprEvaluator;
StatementConstantEvaluator(this.exprEvaluator) {
if (!exprEvaluator.enableConstFunctions) {
throw new UnsupportedError("Const functions feature is not enabled.");
}
}
/// Evaluate the expression using the [ConstantEvaluator].
Constant evaluate(Expression expr) => expr.accept(exprEvaluator);
@override
ExecutionStatus defaultStatement(Statement node) {
throw new UnsupportedError(
'Statement constant evaluation does not support ${node.runtimeType}.');
}
@override
ExecutionStatus visitAssertBlock(AssertBlock node) => defaultStatement(node);
@override
ExecutionStatus visitAssertStatement(AssertStatement node) {
AbortConstant? error = exprEvaluator.checkAssert(node);
if (error != null) return new AbortStatus(error);
return const ProceedStatus();
}
@override
ExecutionStatus visitBlock(Block node) {
return exprEvaluator.withNewEnvironment(() {
for (Statement statement in node.statements) {
final ExecutionStatus status = statement.accept(this);
if (status is! ProceedStatus) return status;
}
return const ProceedStatus();
});
}
@override
ExecutionStatus visitBreakStatement(BreakStatement node) =>
new BreakStatus(node.target);
@override
ExecutionStatus visitContinueSwitchStatement(ContinueSwitchStatement node) =>
node.target.body.accept(this);
@override
ExecutionStatus visitDoStatement(DoStatement node) {
Constant condition;
do {
ExecutionStatus status = node.body.accept(this);
if (status is! ProceedStatus) return status;
condition = evaluate(node.condition);
} while (condition is BoolConstant && condition.value);
if (condition is AbortConstant) {
return new AbortStatus(condition);
}
assert(condition is BoolConstant);
return const ProceedStatus();
}
@override
ExecutionStatus visitEmptyStatement(EmptyStatement node) =>
const ProceedStatus();
@override
ExecutionStatus visitFunctionDeclaration(FunctionDeclaration node) {
final EvaluationEnvironment newEnv =
new EvaluationEnvironment.withParent(exprEvaluator.env);
newEnv.addVariableValue(
node.variable, new FunctionValue(node.function, null));
final FunctionValue function = new FunctionValue(node.function, newEnv);
exprEvaluator.env.addVariableValue(node.variable, function);
return const ProceedStatus();
}
@override
ExecutionStatus visitIfStatement(IfStatement node) {
Constant condition = evaluate(node.condition);
if (condition is AbortConstant) return new AbortStatus(condition);
assert(condition is BoolConstant);
if ((condition as BoolConstant).value) {
return node.then.accept(this);
} else if (node.otherwise != null) {
return node.otherwise!.accept(this);
}
return const ProceedStatus();
}
@override
ExecutionStatus visitForStatement(ForStatement node) {
for (VariableDeclaration variable in node.variables) {
final ExecutionStatus status = variable.accept(this);
if (status is! ProceedStatus) return status;
}
Constant? condition =
node.condition != null ? evaluate(node.condition!) : null;
while (node.condition == null || condition is BoolConstant) {
if (condition is BoolConstant && !condition.value) break;
final ExecutionStatus status = node.body.accept(this);
if (status is! ProceedStatus) return status;
for (Expression update in node.updates) {
Constant updateConstant = evaluate(update);
if (updateConstant is AbortConstant) {
return new AbortStatus(updateConstant);
}
}
if (node.condition != null) {
condition = evaluate(node.condition!);
}
}
if (condition is AbortConstant) return new AbortStatus(condition);
assert(condition is BoolConstant);
return const ProceedStatus();
}
@override
ExecutionStatus visitExpressionStatement(ExpressionStatement node) {
Constant value = evaluate(node.expression);
if (value is AbortConstant) return new AbortStatus(value);
return const ProceedStatus();
}
@override
ExecutionStatus visitLabeledStatement(LabeledStatement node) {
final ExecutionStatus status = node.body.accept(this);
if (status is BreakStatus && status.target == node) {
return const ProceedStatus();
}
return status;
}
@override
ExecutionStatus visitReturnStatement(ReturnStatement node) {
Constant? result;
if (node.expression != null) {
result = evaluate(node.expression!);
if (result is AbortConstant) return new AbortStatus(result);
}
return new ReturnStatus(result);
}
@override
ExecutionStatus visitSwitchStatement(SwitchStatement node) {
final Constant value = evaluate(node.expression);
if (value is AbortConstant) return new AbortStatus(value);
for (SwitchCase switchCase in node.cases) {
if (switchCase.isDefault) return switchCase.body.accept(this);
for (Expression expr in switchCase.expressions) {
final Constant caseValue = evaluate(expr);
if (value == caseValue) return switchCase.body.accept(this);
}
}
return const ProceedStatus();
}
@override
ExecutionStatus visitTryCatch(TryCatch node) {
final ExecutionStatus tryStatus = node.body.accept(this);
if (tryStatus is AbortStatus) {
final Constant error = tryStatus.error;
if (error is _AbortDueToThrowConstant) {
final Object throwValue = error.throwValue;
final DartType defaultType =
exprEvaluator.typeEnvironment.coreTypes.objectNonNullableRawType;
DartType? throwType;
if (throwValue is Constant) {
throwType = throwValue.getType(exprEvaluator._staticTypeContext!);
} else if (throwValue is StateError) {
final Class stateErrorClass = exprEvaluator
.coreTypes.coreLibrary.classes
.firstWhere((Class klass) => klass.name == 'StateError');
throwType =
new InterfaceType(stateErrorClass, Nullability.nonNullable);
} else if (throwValue is RangeError) {
final Class rangeErrorClass = exprEvaluator
.coreTypes.coreLibrary.classes
.firstWhere((Class klass) => klass.name == 'RangeError');
throwType =
new InterfaceType(rangeErrorClass, Nullability.nonNullable);
}
assert(throwType != null);
for (Catch catchClause in node.catches) {
if (exprEvaluator.typeEnvironment.isSubtypeOf(throwType!,
catchClause.guard, SubtypeCheckMode.withNullabilities) ||
catchClause.guard == defaultType) {
return exprEvaluator.withNewEnvironment(() {
if (catchClause.exception != null) {
// TODO(kallentu): Store non-constant exceptions.
if (throwValue is Constant) {
exprEvaluator.env
.addVariableValue(catchClause.exception!, throwValue);
}
}
// TODO(kallentu): Store appropriate stack trace in environment.
return catchClause.body.accept(this);
});
}
}
}
}
return tryStatus;
}
@override
ExecutionStatus visitTryFinally(TryFinally node) {
final ExecutionStatus tryStatus = node.body.accept(this);
final ExecutionStatus finallyStatus = node.finalizer.accept(this);
if (finallyStatus is! ProceedStatus) return finallyStatus;
return tryStatus;
}
@override
ExecutionStatus visitVariableDeclaration(VariableDeclaration node) {
Constant value;
if (node.initializer != null) {
value = evaluate(node.initializer!);
if (value is AbortConstant) return new AbortStatus(value);
} else {
value = new NullConstant();
}
exprEvaluator.env.addVariableValue(node, value);
return const ProceedStatus();
}
@override
ExecutionStatus visitWhileStatement(WhileStatement node) {
Constant condition = evaluate(node.condition);
while (condition is BoolConstant && condition.value) {
final ExecutionStatus status = node.body.accept(this);
if (status is! ProceedStatus) return status;
condition = evaluate(node.condition);
}
if (condition is AbortConstant) return new AbortStatus(condition);
assert(condition is BoolConstant);
return const ProceedStatus();
}
}
class ConstantCoverage {
final Map<Uri, Set<Reference>> constructorCoverage;
ConstantCoverage(this.constructorCoverage);
}
/// Holds the necessary information for a constant object, namely
/// * the [klass] being instantiated
/// * the [typeArguments] used for the instantiation
/// * the [fields] the instance will obtain (all fields from the
/// instantiated [klass] up to the [Object] klass).
class InstanceBuilder {
ConstantEvaluator evaluator;
/// The class of the new instance.
final Class klass;
/// The values of the type parameters of the new instance.
final List<DartType> typeArguments;
/// The field values of the new instance.
final Map<Field, Constant> fields = <Field, Constant>{};
final List<AssertStatement> asserts = <AssertStatement>[];
final List<Expression> unusedArguments = <Expression>[];
InstanceBuilder(this.evaluator, this.klass, this.typeArguments);
void setFieldValue(Field field, Constant constant) {
fields[field] = constant;
}
InstanceConstant buildInstance() {
assert(asserts.isEmpty);
final Map<Reference, Constant> fieldValues = <Reference, Constant>{};
fields.forEach((Field field, Constant value) {
assert(value is! UnevaluatedConstant);
fieldValues[field.getterReference] = value;
});
assert(unusedArguments.isEmpty);
return new InstanceConstant(klass.reference, typeArguments, fieldValues);
}
InstanceCreation buildUnevaluatedInstance() {
final Map<Reference, Expression> fieldValues = <Reference, Expression>{};
fields.forEach((Field field, Constant value) {
fieldValues[field.getterReference] = evaluator.extract(value);
});
return new InstanceCreation(
klass.reference, typeArguments, fieldValues, asserts, unusedArguments);
}
}
/// Holds an environment of type parameters, parameters and variables.
class EvaluationEnvironment {
/// The values of the type parameters in scope.
final Map<TypeParameter, DartType> _typeVariables =
<TypeParameter, DartType>{};
/// The references to values of the parameters/variables in scope.
final Map<VariableDeclaration, EvaluationReference> _variables =
<VariableDeclaration, EvaluationReference>{};
/// The variables that hold unevaluated constants.
///
/// Variables are removed from this set when looked up, leaving only the
/// unread variables at the end.
final Set<VariableDeclaration> _unreadUnevaluatedVariables =
new Set<VariableDeclaration>();
final EvaluationEnvironment? _parent;
EvaluationEnvironment() : _parent = null;
EvaluationEnvironment.withParent(this._parent);
/// Whether the current environment is empty.
bool get isEmpty {
// Since we look up variables in enclosing environment, the environment
// is not empty if its parent is not empty.
if (_parent != null && !_parent!.isEmpty) return false;
return _typeVariables.isEmpty && _variables.isEmpty;
}
void addTypeParameterValue(TypeParameter parameter, DartType value) {
assert(!_typeVariables.containsKey(parameter));
_typeVariables[parameter] = value;
}
void addVariableValue(VariableDeclaration variable, Constant value) {
_variables[variable] = new EvaluationReference(value);
if (value is UnevaluatedConstant) {
_unreadUnevaluatedVariables.add(variable);
}
}
Constant? updateVariableValue(VariableDeclaration variable, Constant value) {
EvaluationReference? reference = _variables[variable];
if (reference != null) {
reference.value = value;
return value;
}
return _parent?.updateVariableValue(variable, value);
}
Constant? lookupVariable(VariableDeclaration variable) {
Constant? value = _variables[variable]?.value;
if (value is UnevaluatedConstant) {
_unreadUnevaluatedVariables.remove(variable);
} else if (value == null) {
return _parent?.lookupVariable(variable);
}
return value;
}
/// The unevaluated constants of variables that were never read.
Iterable<UnevaluatedConstant> get unevaluatedUnreadConstants {
if (_unreadUnevaluatedVariables.isEmpty) return const [];
return _unreadUnevaluatedVariables.map<UnevaluatedConstant>(
(VariableDeclaration variable) =>
_variables[variable]!.value as UnevaluatedConstant);
}
DartType substituteType(DartType type) {
if (_typeVariables.isEmpty) return _parent?.substituteType(type) ?? type;
final DartType substitutedType = substitute(type, _typeVariables);
if (identical(substitutedType, type) && _parent != null) {
// No distinct type created, substitute type in parent.
return _parent!.substituteType(type);
}
return substitutedType;
}
}
class RedundantFileUriExpressionRemover extends Transformer {
Uri? currentFileUri = null;
TreeNode visitFileUriExpression(FileUriExpression node) {
if (node.fileUri == currentFileUri) {
return node.expression.accept(this);
} else {
Uri? oldFileUri = currentFileUri;
currentFileUri = node.fileUri;
node.expression = transform(node.expression)..parent = node;
currentFileUri = oldFileUri;
return node;
}
}
}
/// Location that stores a value in the [ConstantEvaluator].
class EvaluationReference {
Constant value;
EvaluationReference(this.value);
}
/// Represents a status for statement execution.
abstract class ExecutionStatus {
const ExecutionStatus();
}
/// Status that the statement completed execution successfully.
class ProceedStatus extends ExecutionStatus {
const ProceedStatus();
}
/// Status that the statement returned a valid [Constant] value.
class ReturnStatus extends ExecutionStatus {
final Constant? value;
ReturnStatus(this.value);
}
/// Status with an exception or error that the statement has thrown.
class AbortStatus extends ExecutionStatus {
final AbortConstant error;
AbortStatus(this.error);
}
/// Status that the statement breaks out of an enclosing [LabeledStatement].
class BreakStatus extends ExecutionStatus {
final LabeledStatement target;
BreakStatus(this.target);
}
/// Mutable lists used within the [ConstantEvaluator].
class MutableListConstant extends ListConstant {
MutableListConstant(DartType typeArgument, List<Constant> entries)
: super(typeArgument, entries);
@override
String toString() => 'MutableListConstant(${toStringInternal()})';
}
/// An intermediate result that is used for invoking function nodes with their
/// respective environment within the [ConstantEvaluator].
class FunctionValue implements Constant {
final FunctionNode function;
final EvaluationEnvironment? environment;
FunctionValue(this.function, this.environment);
@override
R accept<R>(ConstantVisitor<R> v) {
throw new UnimplementedError();
}
@override
R acceptReference<R>(Visitor<R> v) {
throw new UnimplementedError();
}
@override
Expression asExpression() {
throw new UnimplementedError();
}
@override
DartType getType(StaticTypeContext context) {
throw new UnimplementedError();
}
@override
String leakingDebugToString() {
throw new UnimplementedError();
}
@override
String toString() {
throw new UnimplementedError();
}
@override
String toStringInternal() {
throw new UnimplementedError();
}
@override
String toText(AstTextStrategy strategy) {
throw new UnimplementedError();
}
@override
void toTextInternal(AstPrinter printer) {
throw new UnimplementedError();
}
@override
void visitChildren(Visitor<dynamic> v) {
throw new UnimplementedError();
}
}
abstract class AbortConstant implements Constant {}
class _AbortDueToErrorConstant extends AbortConstant {
final TreeNode node;
final Message message;
final List<LocatedMessage>? context;
_AbortDueToErrorConstant(this.node, this.message, {this.context});
@override
R accept<R>(ConstantVisitor<R> v) {
throw new UnimplementedError();
}
@override
R acceptReference<R>(Visitor<R> v) {
throw new UnimplementedError();
}
@override
Expression asExpression() {
throw new UnimplementedError();
}
@override
DartType getType(StaticTypeContext context) {
throw new UnimplementedError();
}
@override
String leakingDebugToString() {
throw new UnimplementedError();
}
@override
String toString() {
throw new UnimplementedError();
}
@override
String toStringInternal() {
throw new UnimplementedError();
}
@override
String toText(AstTextStrategy strategy) {
throw new UnimplementedError();
}
@override
void toTextInternal(AstPrinter printer) {
throw new UnimplementedError();
}
@override
void visitChildren(Visitor<dynamic> v) {
throw new UnimplementedError();
}
}
class _AbortDueToInvalidExpressionConstant extends AbortConstant {
final TreeNode node;
final String message;
_AbortDueToInvalidExpressionConstant(this.node, this.message);
@override
R accept<R>(ConstantVisitor<R> v) {
throw new UnimplementedError();
}
@override
R acceptReference<R>(Visitor<R> v) {
throw new UnimplementedError();
}
@override
Expression asExpression() {
throw new UnimplementedError();
}
@override
DartType getType(StaticTypeContext context) {
throw new UnimplementedError();
}
@override
String leakingDebugToString() {
throw new UnimplementedError();
}
@override
String toString() {
throw new UnimplementedError();
}
@override
String toStringInternal() {
throw new UnimplementedError();
}
@override
String toText(AstTextStrategy strategy) {
throw new UnimplementedError();
}
@override
void toTextInternal(AstPrinter printer) {
throw new UnimplementedError();
}
@override
void visitChildren(Visitor<dynamic> v) {
throw new UnimplementedError();
}
}
class _AbortDueToThrowConstant extends AbortConstant {
final TreeNode node;
final Object throwValue;
_AbortDueToThrowConstant(this.node, this.throwValue);
@override
R accept<R>(ConstantVisitor<R> v) {
throw new UnimplementedError();
}
@override
R acceptReference<R>(Visitor<R> v) {
throw new UnimplementedError();
}
@override
Expression asExpression() {
throw new UnimplementedError();
}
@override
DartType getType(StaticTypeContext context) {
throw new UnimplementedError();
}
@override
String leakingDebugToString() {
throw new UnimplementedError();
}
@override
String toString() {
throw new UnimplementedError();
}
@override
String toStringInternal() {
throw new UnimplementedError();
}
@override
String toText(AstTextStrategy strategy) {
throw new UnimplementedError();
}
@override
void toTextInternal(AstPrinter printer) {
throw new UnimplementedError();
}
@override
void visitChildren(Visitor<dynamic> v) {
throw new UnimplementedError();
}
}
abstract class ErrorReporter {
const ErrorReporter();
void report(LocatedMessage message, List<LocatedMessage>? context);
void reportInvalidExpression(InvalidExpression node);
}
class SimpleErrorReporter implements ErrorReporter {
const SimpleErrorReporter();
@override
void report(LocatedMessage message, List<LocatedMessage>? context) {
_report(message);
if (context != null) {
for (LocatedMessage contextMessage in context) {
_report(contextMessage);
}
}
}
@override
void reportInvalidExpression(InvalidExpression node) {
// Ignored
}
void _report(LocatedMessage message) {
reportMessage(message.uri, message.charOffset, message.message);
}
void reportMessage(Uri? uri, int offset, String message) {
io.exitCode = 42;
io.stderr.writeln('$uri:$offset Constant evaluation error: $message');
}
}
class IsInstantiatedVisitor extends DartTypeVisitor<bool> {
final _availableVariables = new Set<TypeParameter>();
bool isInstantiated(DartType type) {
return type.accept(this);
}
@override
bool defaultDartType(DartType node) {
// Probably unreachable.
throw 'A visitor method seems to be unimplemented!';
}
@override
bool visitInvalidType(InvalidType node) => true;
@override
bool visitDynamicType(DynamicType node) => true;
@override
bool visitVoidType(VoidType node) => true;
@override
bool visitNullType(NullType node) => true;
@override
bool visitTypeParameterType(TypeParameterType node) {
return _availableVariables.contains(node.parameter);
}
@override
bool visitInterfaceType(InterfaceType node) {
return node.typeArguments
.every((DartType typeArgument) => typeArgument.accept(this));
}
@override
bool visitFutureOrType(FutureOrType node) {
return node.typeArgument.accept(this);
}
@override
bool visitFunctionType(FunctionType node) {
final List<TypeParameter> parameters = node.typeParameters;
_availableVariables.addAll(parameters);
final bool result = node.returnType.accept(this) &&
node.positionalParameters.every((p) => p.accept(this)) &&
node.namedParameters.every((p) => p.type.accept(this));
_availableVariables.removeAll(parameters);
return result;
}
@override
bool visitTypedefType(TypedefType node) {
// Probably unreachable.
return node.unalias.accept(this);
}
@override
bool visitNeverType(NeverType node) => true;
}
bool _isFormalParameter(VariableDeclaration variable) {
final TreeNode? parent = variable.parent;
if (parent is FunctionNode) {
return parent.positionalParameters.contains(variable) ||
parent.namedParameters.contains(variable);
}
return false;
}