blob: bd6983c980023b7692e7171c9eb89d16eda2bc2a [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/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, defaultAstTextStrategy;
import 'package:kernel/type_algebra.dart';
import 'package:kernel/type_environment.dart';
import 'package:kernel/target/targets.dart';
import '../fasta_codes.dart';
import 'constant_int_folder.dart';
part 'constant_collection_builders.dart';
Component transformComponent(
Target target,
Component component,
Map<String, String> environmentDefines,
ErrorReporter errorReporter,
EvaluationMode evaluationMode,
{required bool evaluateAnnotations,
required bool desugarSets,
required bool enableTripleShift,
required bool enableConstFunctions,
required bool enableConstructorTearOff,
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);
// ignore: unnecessary_null_comparison
assert(enableConstructorTearOff != null);
coreTypes ??= new CoreTypes(component);
hierarchy ??= new ClassHierarchy(component, coreTypes);
final TypeEnvironment typeEnvironment =
new TypeEnvironment(coreTypes, hierarchy);
transformLibraries(component, component.libraries, target, environmentDefines,
typeEnvironment, errorReporter, evaluationMode,
enableTripleShift: enableTripleShift,
enableConstFunctions: enableConstFunctions,
errorOnUnevaluatedConstant: errorOnUnevaluatedConstant,
evaluateAnnotations: evaluateAnnotations,
enableConstructorTearOff: enableConstructorTearOff);
return component;
}
ConstantEvaluationData transformLibraries(
Component component,
List<Library> libraries,
Target target,
Map<String, String>? environmentDefines,
TypeEnvironment typeEnvironment,
ErrorReporter errorReporter,
EvaluationMode evaluationMode,
{required bool evaluateAnnotations,
required bool enableTripleShift,
required bool enableConstFunctions,
required bool errorOnUnevaluatedConstant,
required bool enableConstructorTearOff}) {
// 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);
// ignore: unnecessary_null_comparison
assert(enableConstructorTearOff != null);
final ConstantsTransformer constantsTransformer = new ConstantsTransformer(
target,
environmentDefines,
evaluateAnnotations,
enableTripleShift,
enableConstFunctions,
enableConstructorTearOff,
errorOnUnevaluatedConstant,
component,
typeEnvironment,
errorReporter,
evaluationMode);
for (final Library library in libraries) {
constantsTransformer.convertLibrary(library);
}
return new ConstantEvaluationData(
constantsTransformer.constantEvaluator.getConstantCoverage(),
constantsTransformer.constantEvaluator.visitedLibraries);
}
void transformProcedure(
Procedure procedure,
Target target,
Component component,
Map<String, String>? environmentDefines,
TypeEnvironment typeEnvironment,
ErrorReporter errorReporter,
EvaluationMode evaluationMode,
{required bool evaluateAnnotations,
required bool enableTripleShift,
required bool enableConstFunctions,
required bool enableConstructorTearOff,
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);
// ignore: unnecessary_null_comparison
assert(enableConstructorTearOff != null);
final ConstantsTransformer constantsTransformer = new ConstantsTransformer(
target,
environmentDefines,
evaluateAnnotations,
enableTripleShift,
enableConstFunctions,
enableConstructorTearOff,
errorOnUnevaluatedConstant,
component,
typeEnvironment,
errorReporter,
evaluationMode);
constantsTransformer.visitProcedure(procedure, null);
}
enum EvaluationMode {
weak,
agnostic,
strong,
}
class ConstantWeakener extends ComputeOnceConstantVisitor<Constant?> {
ConstantEvaluator _evaluator;
ConstantWeakener(this._evaluator);
@override
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>.of(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? visitInstantiationConstant(InstantiationConstant 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 InstantiationConstant(node.tearOffConstant, types);
}
return null;
}
@override
Constant? visitStaticTearOffConstant(StaticTearOffConstant 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 enableConstructorTearOff;
final bool errorOnUnevaluatedConstant;
ConstantsTransformer(
Target target,
Map<String, String>? environmentDefines,
this.evaluateAnnotations,
this.enableTripleShift,
this.enableConstFunctions,
this.enableConstructorTearOff,
this.errorOnUnevaluatedConstant,
Component component,
this.typeEnvironment,
ErrorReporter errorReporter,
EvaluationMode evaluationMode)
: this.backend = target.constantsBackend,
constantEvaluator = new ConstantEvaluator(
target.dartLibrarySupport,
target.constantsBackend,
component,
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);
transformRedirectingFactoryList(node.redirectingFactories, 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);
});
return node;
}
@override
RedirectingFactory visitRedirectingFactory(
RedirectingFactory 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);
node.function = transform(node.function)..parent = 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) {
return evaluateAndTransformWithContext(node, node);
}
@override
TreeNode visitConstructorTearOff(
ConstructorTearOff node, TreeNode? removalSentinel) {
return evaluateAndTransformWithContext(node, node);
}
@override
TreeNode visitRedirectingFactoryTearOff(
RedirectingFactoryTearOff node, TreeNode? removalSentinel) {
return evaluateAndTransformWithContext(node, node);
}
@override
TreeNode visitTypedefTearOff(TypedefTearOff node, TreeNode? removalSentinel) {
return evaluateAndTransformWithContext(node, node);
}
@override
TreeNode visitInstantiation(Instantiation node, TreeNode? removalSentinel) {
Instantiation result =
super.visitInstantiation(node, removalSentinel) as Instantiation;
Expression expression = result.expression;
if (expression is StaticGet && expression.target.isConst) {
// Handle [StaticGet] of constant fields also when these are not inlined.
expression = (expression.target as Field).initializer!;
} else if (expression is VariableGet && expression.variable.isConst) {
// Handle [VariableGet] of constant locals also when these are not
// inlined.
expression = expression.variable.initializer!;
}
if (expression is ConstantExpression) {
if (result.typeArguments.every(isInstantiated)) {
return evaluateAndTransformWithContext(node, result);
}
}
return node;
}
@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) {
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 (backend.alwaysInlineConstants) {
return true;
}
if (initializer is ConstantExpression) {
return backend.shouldInlineConstant(initializer);
}
return true;
}
}
class ConstantEvaluator implements ExpressionVisitor<Constant> {
final DartLibrarySupport dartLibrarySupport;
final ConstantsBackend backend;
final NumberSemantics numberSemantics;
late ConstantIntFolder intFolder;
Map<String, String>? _environmentDefines;
final bool errorOnUnevaluatedConstant;
final Component component;
final CoreTypes coreTypes;
final TypeEnvironment typeEnvironment;
StaticTypeContext? _staticTypeContext;
final ErrorReporter errorReporter;
final EvaluationMode evaluationMode;
final bool enableTripleShift;
final bool enableConstFunctions;
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);
final Set<Library> visitedLibraries = {};
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.dartLibrarySupport, this.backend, this.component,
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);
}
Map<String, String>? _supportedLibrariesCache;
Map<String, String> _computeSupportedLibraries() {
Map<String, String> map = {};
for (Library library in component.libraries) {
if (library.importUri.isScheme('dart')) {
map[library.importUri.path] =
DartLibrarySupport.getDartLibrarySupportValue(
library.importUri.path,
libraryExists: true,
isSynthetic: library.isSynthetic,
isUnsupported: library.isUnsupported,
dartLibrarySupport: dartLibrarySupport);
}
}
return map;
}
String? lookupEnvironment(String key) {
if (DartLibrarySupport.isDartLibraryQualifier(key)) {
String libraryName = DartLibrarySupport.getDartLibraryName(key);
String? value = (_supportedLibrariesCache ??=
_computeSupportedLibraries())[libraryName];
return value ?? "";
}
return _environmentDefines![key];
}
bool hasEnvironmentKey(String key) {
if (key.startsWith(DartLibrarySupport.dartLibraryPrefix)) {
return true;
}
return _environmentDefines!.containsKey(key);
}
bool get hasEnvironment => _environmentDefines != null;
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);
if (result.isEvaluationError) {
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);
}
} else {
errorReporter.report(locatedMessageActualError);
}
return new UnevaluatedConstant(
new InvalidExpression(result.message.problemMessage));
}
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.problemMessage));
}
if (result is _AbortDueToInvalidExpressionConstant) {
return new UnevaluatedConstant(
// Create a new [InvalidExpression] without the expression, which
// might now have lost the needed context. For instance references
// to variables no longer in scope.
new InvalidExpression(result.node.message));
}
throw "Unexpected error constant";
}
if (result is UnevaluatedConstant) {
if (errorOnUnevaluatedConstant) {
return createEvaluationErrorConstant(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 createEvaluationErrorConstant(
statement,
templateConstEvalError.withArguments(
'No valid constant returned from the execution of the '
'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 createEvaluationErrorConstant(
constructor,
templateConstEvalError
.withArguments("Constructors can't have a return value."));
} else if (status is! ProceedStatus) {
return createEvaluationErrorConstant(
constructor,
templateConstEvalError
.withArguments("Invalid execution status of constructor body."));
}
return null;
}
/// Create an error-constant indicating that an error has been detected during
/// constant evaluation.
AbortConstant createEvaluationErrorConstant(TreeNode node, Message message,
{List<LocatedMessage>? context}) {
return new _AbortDueToErrorConstant(node, message,
context: context, isEvaluationError: true);
}
/// Create an error-constant indicating that an non-constant expression has
/// been found.
AbortConstant createExpressionErrorConstant(TreeNode node, Message message,
{List<LocatedMessage>? context}) {
return new _AbortDueToErrorConstant(node, message,
context: context, isEvaluationError: false);
}
/// 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;
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 createEvaluationErrorConstant(
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 InstanceInvocation ||
node is FunctionInvocation ||
node is LocalFunctionInvocation ||
node is StaticInvocation;
if (nodeCache[node] == null &&
!(enableConstFunctions && isRecursiveFunctionCall)) {
// recursive call
return createEvaluationErrorConstant(
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);
}
// TODO(johnniwinther): Remove this and handle each expression directly.
@override
Constant defaultExpression(Expression node) {
// Only a subset of the expression language is valid for constant
// evaluation.
return createExpressionErrorConstant(node, messageNotAConstantExpression);
}
@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 (hasEnvironment) {
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 createExpressionErrorConstant(
node,
templateNotConstantExpression
.withArguments('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 createExpressionErrorConstant(
node,
templateNotConstantExpression
.withArguments('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 createExpressionErrorConstant(
node,
templateNotConstantExpression
.withArguments('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 createExpressionErrorConstant(node,
templateNotConstantExpression.withArguments('Function expression'));
}
@override
Constant visitConstructorInvocation(ConstructorInvocation node) {
if (!node.isConst && !enableConstFunctions) {
return createExpressionErrorConstant(
node, templateNotConstantExpression.withArguments('New expression'));
}
final Constructor constructor = node.target;
AbortConstant? error =
checkConstructorConst(node, constructor, messageNonConstConstructor);
if (error != null) return error;
final Class klass = constructor.enclosingClass;
if (klass.isAbstract) {
// Probably unreachable.
return createExpressionErrorConstant(
node, templateAbstractClassInstantiation.withArguments(klass.name));
}
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;
// For libraries with null safety Symbol constructor accepts arbitrary
// string as argument.
if (nameValue is StringConstant &&
(isNonNullableByDefault || isValidSymbolName(nameValue.value))) {
return canonicalize(new SymbolConstant(nameValue.value, null));
}
return createEvaluationErrorConstant(
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, Message messageIfNonConst) {
if (!constructor.isConst) {
return createExpressionErrorConstant(node, messageIfNonConst);
}
if (constructor.function.body != null &&
constructor.function.body is! EmptyStatement &&
!enableConstFunctions) {
// Probably unreachable.
return createExpressionErrorConstant(
node, messageConstConstructorWithBody);
} else if (constructor.isExternal) {
return createEvaluationErrorConstant(
node, messageConstEvalExternalConstructor);
}
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, init.target, messageConstConstructorWithNonConstSuper);
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, caller);
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, init.target, messageConstConstructorRedirectionToNonConst);
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, caller);
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.
assert(
false,
'No support for handling initializer of type '
'"${init.runtimeType}".');
return createEvaluationErrorConstant(
init, messageNotAConstantExpression);
}
}
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 createEvaluationErrorConstant(
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 createEvaluationErrorConstant(
statement.condition,
templateConstEvalFailedAssertionWithMessage
.withArguments(message.value));
} else if (message is NullConstant) {
return createEvaluationErrorConstant(
statement.condition, messageConstEvalFailedAssertion);
} else {
return createEvaluationErrorConstant(statement.message!,
messageConstEvalFailedAssertionWithNonStringMessage);
}
}
} else {
return createEvaluationErrorConstant(
statement.condition,
templateConstEvalInvalidType.withArguments(
condition,
typeEnvironment.coreTypes.boolLegacyRawType,
condition.getType(_staticTypeContext!),
isNonNullableByDefault));
}
return null;
}
@override
Constant visitInvalidExpression(InvalidExpression node) {
return new _AbortDueToInvalidExpressionConstant(node);
}
@override
Constant visitDynamicInvocation(DynamicInvocation node) {
// We have no support for generic method invocation at the moment.
if (node.arguments.types.isNotEmpty) {
return createExpressionErrorConstant(node,
templateNotConstantExpression.withArguments("Dynamic invocation"));
}
// We have no support for method invocation with named arguments at the
// moment.
if (node.arguments.named.isNotEmpty) {
return createExpressionErrorConstant(node,
templateNotConstantExpression.withArguments("Dynamic invocation"));
}
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 createExpressionErrorConstant(node,
templateNotConstantExpression.withArguments("Instance invocation"));
}
// We have no support for method invocation with named arguments at the
// moment.
if (node.arguments.named.isNotEmpty) {
return createExpressionErrorConstant(node,
templateNotConstantExpression.withArguments("Instance invocation"));
}
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 createExpressionErrorConstant(node,
templateNotConstantExpression.withArguments('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 createExpressionErrorConstant(
node,
templateNotConstantExpression
.withArguments('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 createEvaluationErrorConstant(
node,
templateConstEvalError
.withArguments('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 createEvaluationErrorConstant(
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 createEvaluationErrorConstant(
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 createEvaluationErrorConstant(
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 createEvaluationErrorConstant(
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 createEvaluationErrorConstant(
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 createEvaluationErrorConstant(
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 createEvaluationErrorConstant(
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 createEvaluationErrorConstant(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 createEvaluationErrorConstant(
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;
final Member member =
typeEnvironment.hierarchy.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.toText(defaultAstTextStrategy)}'");
}
}
}
return createEvaluationErrorConstant(
node,
templateConstEvalInvalidMethodInvocation.withArguments(
op, receiver, isNonNullableByDefault));
}
@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 createEvaluationErrorConstant(
node,
templateConstEvalInvalidBinaryOperandType.withArguments(
logicalExpressionOperatorToString(node.operatorEnum),
left,
typeEnvironment.coreTypes.boolLegacyRawType,
right.getType(_staticTypeContext!),
isNonNullableByDefault));
}
return createEvaluationErrorConstant(
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 createEvaluationErrorConstant(
node,
templateConstEvalInvalidBinaryOperandType.withArguments(
logicalExpressionOperatorToString(node.operatorEnum),
left,
typeEnvironment.coreTypes.boolLegacyRawType,
right.getType(_staticTypeContext!),
isNonNullableByDefault));
}
return createEvaluationErrorConstant(
node,
templateConstEvalInvalidMethodInvocation.withArguments(
logicalExpressionOperatorToString(node.operatorEnum),
left,
isNonNullableByDefault));
default:
// Probably unreachable.
return createEvaluationErrorConstant(
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 createEvaluationErrorConstant(
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 createEvaluationErrorConstant(
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 createEvaluationErrorConstant(
node,
templateConstEvalError.withArguments(
'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 createEvaluationErrorConstant(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 createEvaluationErrorConstant(
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 createEvaluationErrorConstant(node, messageConstEvalNullValue);
}
return createEvaluationErrorConstant(
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 createEvaluationErrorConstant(
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 createEvaluationErrorConstant(
node,
templateConstEvalInvalidPropertyGet.withArguments(
Name.callName.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) ??
createEvaluationErrorConstant(
node,
templateConstEvalGetterNotFound
.withArguments(variable.name ?? ''));
} else {
if (variable.parent is Let ||
variable.parent is LocalInitializer ||
_isFormalParameter(variable)) {
return env.lookupVariable(node.variable) ??
createEvaluationErrorConstant(
node,
templateConstEvalNonConstantVariableGet
.withArguments(variable.name ?? ''));
}
if (variable.isConst) {
return _evaluateSubexpression(variable.initializer!);
}
}
return createExpressionErrorConstant(
node,
templateNotConstantExpression
.withArguments('Read 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;
Constant? result = env.updateVariableValue(variable, value);
if (result != null) {
return result;
}
return createEvaluationErrorConstant(
node,
templateConstEvalError
.withArguments('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;
visitedLibraries.add(target.enclosingLibrary);
if (target is Field) {
if (target.isConst) {
return _evaluateExpressionInContext(target, target.initializer!);
}
return createEvaluationErrorConstant(
node,
templateConstEvalInvalidStaticInvocation
.withArguments(target.name.text));
} else if (target is Procedure && target.kind == ProcedureKind.Method) {
// TODO(johnniwinther): Remove this. This should never occur.
return canonicalize(new StaticTearOffConstant(target));
} else {
return createEvaluationErrorConstant(
node,
templateConstEvalInvalidStaticInvocation
.withArguments(target.name.text));
}
});
}
@override
Constant visitStaticTearOff(StaticTearOff node) {
return canonicalize(new StaticTearOffConstant(node.target));
}
@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) {
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 createEvaluationErrorConstant(
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 _evaluateExpressionInContext(target, variable.initializer!);
}
Constant _handleFromEnvironment(
Procedure target, StringConstant name, Map<String, Constant> named) {
String? value = lookupEnvironment(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 hasEnvironmentKey(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) {
if (target.enclosingLibrary == coreTypes.coreLibrary &&
positionals.length == 1 &&
(target.name.text == "fromEnvironment" ||
target.name.text == "hasEnvironment")) {
if (hasEnvironment) {
// 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 createEvaluationErrorConstant(
node, messageConstEvalNullValue);
}
} else {
// Leave environment constant unevaluated.
return unevaluated(
node,
new StaticInvocation(target,
unevaluatedArguments(positionals, named, arguments.types),
isConst: true));
}
} else if (target.isExternal) {
return createEvaluationErrorConstant(
node, messageConstEvalExternalFactory);
} else if (enableConstFunctions) {
return _handleFunctionInvocation(
node.target.function, typeArguments, positionals, named);
} else {
return createExpressionErrorConstant(
node,
templateNotConstantExpression
.withArguments('Non-redirecting const factory invocation'));
}
} else {
if (enableConstFunctions) {
return _handleFunctionInvocation(
node.target.function, typeArguments, positionals, named);
} else if (!node.isConst) {
return createExpressionErrorConstant(node,
templateNotConstantExpression.withArguments('New expression'));
} else {
return createEvaluationErrorConstant(
node,
templateNotConstantExpression
.withArguments('Non-const factory invocation'));
}
}
} 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 createEvaluationErrorConstant(
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 createEvaluationErrorConstant(node, messageConstEvalExtension);
} else if (enableConstFunctions && target.kind == ProcedureKind.Method) {
return _handleFunctionInvocation(
node.target.function, typeArguments, positionals, named);
}
return createExpressionErrorConstant(
node, templateNotConstantExpression.withArguments('Static invocation'));
}
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 createEvaluationErrorConstant(
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 createEvaluationErrorConstant(
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 createEvaluationErrorConstant(
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 createEvaluationErrorConstant(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) {
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()));
}
List<TypeParameter>? typeParameters;
if (constant is TearOffConstant) {
Member target = constant.target;
if (target is Procedure) {
typeParameters = target.function.typeParameters;
} else if (target is Constructor) {
typeParameters = target.enclosingClass.typeParameters;
}
} else if (constant is TypedefTearOffConstant) {
typeParameters = constant.parameters;
}
if (typeParameters != null) {
if (node.typeArguments.length == 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);
return canonicalize(
new InstantiationConstant(constant, convertTypes(types)));
} else {
// Probably unreachable.
return createEvaluationErrorConstant(
node,
templateConstEvalError.withArguments(
'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 createEvaluationErrorConstant(
node,
templateConstEvalError.withArguments(
'Only tear-off constants can be partially instantiated.'));
}
@override
Constant visitConstructorTearOff(ConstructorTearOff node) {
return canonicalize(new ConstructorTearOffConstant(node.target));
}
@override
Constant visitRedirectingFactoryTearOff(RedirectingFactoryTearOff node) {
return canonicalize(new RedirectingFactoryTearOffConstant(node.target));
}
@override
Constant visitTypedefTearOff(TypedefTearOff node) {
final Constant constant = _evaluateSubexpression(node.expression);
if (constant is TearOffConstant) {
FreshTypeParameters freshTypeParameters =
getFreshTypeParameters(node.typeParameters);
List<TypeParameter> typeParameters =
freshTypeParameters.freshTypeParameters;
List<DartType> typeArguments = new List<DartType>.generate(
node.typeArguments.length,
(int i) => freshTypeParameters.substitute(node.typeArguments[i]),
growable: false);
return canonicalize(
new TypedefTearOffConstant(typeParameters, constant, typeArguments));
} else {
// Probably unreachable.
return createEvaluationErrorConstant(
node,
templateConstEvalError.withArguments(
"Unsupported typedef tearoff target: ${constant}."));
}
}
@override
Constant visitCheckLibraryIsLoaded(CheckLibraryIsLoaded node) {
return createEvaluationErrorConstant(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 createEvaluationErrorConstant(
node, messageNonAgnosticConstant);
}
result = strongResult;
break;
case EvaluationMode.weak:
result =
isSubtype(constant, type, SubtypeCheckMode.ignoringNullabilities);
break;
}
if (!result) {
return createEvaluationErrorConstant(
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)) {
// TODO(johnniwinther): Maybe we should always report this in the body
// builder. Currently we report some, because we need to handle
// potentially constant types, but we should be able to handle all (or
// none) in the body builder.
_gotError = createExpressionErrorConstant(
node, messageTypeVariableInConstantContext);
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 createEvaluationErrorConstant(
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 createExpressionErrorConstant(node,
templateNotConstantExpression.withArguments("Binary '$op' operation"));
}
// 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 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);
}
class ConstantEvaluationData {
final ConstantCoverage coverage;
final Set<Library> visitedLibraries;
ConstantEvaluationData(this.coverage, this.visitedLibraries);
}
/// 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.fieldReference] = 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.fieldReference] = 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;
@override
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 accept1<R, A>(ConstantVisitor1<R, A> v, A arg) {
throw new UnimplementedError();
}
@override
R acceptReference<R>(Visitor<R> v) {
throw new UnimplementedError();
}
@override
R acceptReference1<R, A>(Visitor1<R, A> v, A arg) {
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;
final bool isEvaluationError;
_AbortDueToErrorConstant(this.node, this.message,
{this.context, required this.isEvaluationError});
@override
R accept<R>(ConstantVisitor<R> v) {
throw new UnimplementedError();
}
@override
R accept1<R, A>(ConstantVisitor1<R, A> v, A arg) {
throw new UnimplementedError();
}
@override
R acceptReference<R>(Visitor<R> v) {
throw new UnimplementedError();
}
@override
R acceptReference1<R, A>(Visitor1<R, A> v, A arg) {
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 InvalidExpression node;
_AbortDueToInvalidExpressionConstant(this.node);
@override
R accept<R>(ConstantVisitor<R> v) {
throw new UnimplementedError();
}
@override
R accept1<R, A>(ConstantVisitor1<R, A> v, A arg) {
throw new UnimplementedError();
}
@override
R acceptReference<R>(Visitor<R> v) {
throw new UnimplementedError();
}
@override
R acceptReference1<R, A>(Visitor1<R, A> v, A arg) {
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 accept1<R, A>(ConstantVisitor1<R, A> v, A arg) {
throw new UnimplementedError();
}
@override
R acceptReference<R>(Visitor<R> v) {
throw new UnimplementedError();
}
@override
R acceptReference1<R, A>(Visitor1<R, A> v, A arg) {
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]);
}
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);
}
}
}
void _report(LocatedMessage message) {
reportMessage(message.uri, message.charOffset, message.problemMessage);
}
void reportMessage(Uri? uri, int offset, String message) {
io.exitCode = 42;
io.stderr.writeln('$uri:$offset Constant evaluation error: $message');
}
}
bool isInstantiated(DartType type) {
return type.accept(new IsInstantiatedVisitor());
}
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;
}