|  | // 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:front_end/src/fasta/kernel/constructor_tearoff_lowering.dart'; | 
|  | import 'package:kernel/ast.dart'; | 
|  | import 'package:kernel/class_hierarchy.dart'; | 
|  | import 'package:kernel/clone.dart'; | 
|  | import 'package:kernel/core_types.dart'; | 
|  | import 'package:kernel/kernel.dart'; | 
|  | import 'package:kernel/src/const_canonical_type.dart'; | 
|  | import 'package:kernel/src/legacy_erasure.dart'; | 
|  | import 'package:kernel/src/norm.dart'; | 
|  | import 'package:kernel/src/printer.dart' show AstPrinter, AstTextStrategy; | 
|  | import 'package:kernel/type_algebra.dart'; | 
|  | import 'package:kernel/type_environment.dart'; | 
|  | import 'package:kernel/target/targets.dart'; | 
|  |  | 
|  | import '../fasta_codes.dart' | 
|  | show | 
|  | LocatedMessage, | 
|  | Message, | 
|  | messageConstEvalCircularity, | 
|  | messageConstEvalContext, | 
|  | messageConstEvalExtension, | 
|  | messageConstEvalExternalConstructor, | 
|  | messageConstEvalExternalFactory, | 
|  | messageConstEvalFailedAssertion, | 
|  | messageConstEvalNotListOrSetInSpread, | 
|  | messageConstEvalNotMapInSpread, | 
|  | messageConstEvalNonNull, | 
|  | messageConstEvalNullValue, | 
|  | messageConstEvalStartingPoint, | 
|  | messageConstEvalUnevaluated, | 
|  | messageNonAgnosticConstant, | 
|  | messageNotAConstantExpression, | 
|  | noLength, | 
|  | templateConstEvalCaseImplementsEqual, | 
|  | templateConstEvalDeferredLibrary, | 
|  | templateConstEvalDuplicateElement, | 
|  | templateConstEvalDuplicateKey, | 
|  | templateConstEvalElementImplementsEqual, | 
|  | templateConstEvalFailedAssertionWithMessage, | 
|  | templateConstEvalFreeTypeParameter, | 
|  | templateConstEvalGetterNotFound, | 
|  | templateConstEvalInvalidType, | 
|  | templateConstEvalInvalidBinaryOperandType, | 
|  | templateConstEvalInvalidEqualsOperandType, | 
|  | templateConstEvalInvalidMethodInvocation, | 
|  | templateConstEvalInvalidPropertyGet, | 
|  | templateConstEvalInvalidStaticInvocation, | 
|  | templateConstEvalInvalidStringInterpolationOperand, | 
|  | templateConstEvalInvalidSymbolName, | 
|  | templateConstEvalKeyImplementsEqual, | 
|  | templateConstEvalNonConstantVariableGet, | 
|  | templateConstEvalUnhandledCoreException, | 
|  | templateConstEvalUnhandledException, | 
|  | templateConstEvalZeroDivisor; | 
|  |  | 
|  | import 'constant_int_folder.dart'; | 
|  |  | 
|  | part 'constant_collection_builders.dart'; | 
|  |  | 
|  | Component transformComponent( | 
|  | Component component, | 
|  | ConstantsBackend backend, | 
|  | Map<String, String> environmentDefines, | 
|  | ErrorReporter errorReporter, | 
|  | EvaluationMode evaluationMode, | 
|  | {required bool evaluateAnnotations, | 
|  | required bool desugarSets, | 
|  | required bool enableTripleShift, | 
|  | required bool enableConstFunctions, | 
|  | required bool 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.libraries, backend, environmentDefines, | 
|  | typeEnvironment, errorReporter, evaluationMode, | 
|  | enableTripleShift: enableTripleShift, | 
|  | enableConstFunctions: enableConstFunctions, | 
|  | errorOnUnevaluatedConstant: errorOnUnevaluatedConstant, | 
|  | evaluateAnnotations: evaluateAnnotations, | 
|  | enableConstructorTearOff: enableConstructorTearOff); | 
|  | return component; | 
|  | } | 
|  |  | 
|  | ConstantCoverage transformLibraries( | 
|  | List<Library> libraries, | 
|  | ConstantsBackend backend, | 
|  | Map<String, String>? environmentDefines, | 
|  | TypeEnvironment typeEnvironment, | 
|  | ErrorReporter errorReporter, | 
|  | EvaluationMode evaluationMode, | 
|  | {required bool evaluateAnnotations, | 
|  | required bool enableTripleShift, | 
|  | required bool enableConstFunctions, | 
|  | required bool errorOnUnevaluatedConstant, | 
|  | 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( | 
|  | backend, | 
|  | environmentDefines, | 
|  | evaluateAnnotations, | 
|  | enableTripleShift, | 
|  | enableConstFunctions, | 
|  | enableConstructorTearOff, | 
|  | errorOnUnevaluatedConstant, | 
|  | typeEnvironment, | 
|  | errorReporter, | 
|  | evaluationMode); | 
|  | for (final Library library in libraries) { | 
|  | constantsTransformer.convertLibrary(library); | 
|  | } | 
|  | return constantsTransformer.constantEvaluator.getConstantCoverage(); | 
|  | } | 
|  |  | 
|  | void transformProcedure( | 
|  | Procedure procedure, | 
|  | ConstantsBackend backend, | 
|  | Map<String, String>? environmentDefines, | 
|  | TypeEnvironment typeEnvironment, | 
|  | ErrorReporter errorReporter, | 
|  | EvaluationMode evaluationMode, | 
|  | {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( | 
|  | backend, | 
|  | environmentDefines, | 
|  | evaluateAnnotations, | 
|  | enableTripleShift, | 
|  | enableConstFunctions, | 
|  | enableConstructorTearOff, | 
|  | errorOnUnevaluatedConstant, | 
|  | typeEnvironment, | 
|  | errorReporter, | 
|  | evaluationMode); | 
|  | constantsTransformer.visitProcedure(procedure, null); | 
|  | } | 
|  |  | 
|  | enum EvaluationMode { | 
|  | weak, | 
|  | agnostic, | 
|  | strong, | 
|  | } | 
|  |  | 
|  | class ConstantWeakener extends ComputeOnceConstantVisitor<Constant?> { | 
|  | ConstantEvaluator _evaluator; | 
|  |  | 
|  | ConstantWeakener(this._evaluator); | 
|  |  | 
|  | Constant? processValue(Constant node, Constant? value) { | 
|  | if (value != null) { | 
|  | value = _evaluator.canonicalize(value); | 
|  | } | 
|  | return value; | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant? defaultConstant(Constant node) => throw new UnsupportedError( | 
|  | "Unhandled constant ${node} (${node.runtimeType})"); | 
|  |  | 
|  | @override | 
|  | Constant? visitNullConstant(NullConstant node) => null; | 
|  |  | 
|  | @override | 
|  | Constant? visitBoolConstant(BoolConstant node) => null; | 
|  |  | 
|  | @override | 
|  | Constant? visitIntConstant(IntConstant node) => null; | 
|  |  | 
|  | @override | 
|  | Constant? visitDoubleConstant(DoubleConstant node) => null; | 
|  |  | 
|  | @override | 
|  | Constant? visitStringConstant(StringConstant node) => null; | 
|  |  | 
|  | @override | 
|  | Constant? visitSymbolConstant(SymbolConstant node) => null; | 
|  |  | 
|  | @override | 
|  | Constant? visitMapConstant(MapConstant node) { | 
|  | DartType? keyType = computeConstCanonicalType( | 
|  | node.keyType, _evaluator.coreTypes, | 
|  | isNonNullableByDefault: _evaluator.isNonNullableByDefault); | 
|  | DartType? valueType = computeConstCanonicalType( | 
|  | node.valueType, _evaluator.coreTypes, | 
|  | isNonNullableByDefault: _evaluator.isNonNullableByDefault); | 
|  | List<ConstantMapEntry>? entries; | 
|  | for (int index = 0; index < node.entries.length; index++) { | 
|  | ConstantMapEntry entry = node.entries[index]; | 
|  | Constant? key = visitConstant(entry.key); | 
|  | Constant? value = visitConstant(entry.value); | 
|  | if (key != null || value != null) { | 
|  | entries ??= node.entries.toList(growable: false); | 
|  | entries[index] = | 
|  | new ConstantMapEntry(key ?? entry.key, value ?? entry.value); | 
|  | } | 
|  | } | 
|  | if (keyType != null || valueType != null || entries != null) { | 
|  | return new MapConstant(keyType ?? node.keyType, | 
|  | valueType ?? node.valueType, entries ?? node.entries); | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant? visitListConstant(ListConstant node) { | 
|  | DartType? typeArgument = computeConstCanonicalType( | 
|  | node.typeArgument, _evaluator.coreTypes, | 
|  | isNonNullableByDefault: _evaluator.isNonNullableByDefault); | 
|  | List<Constant>? entries; | 
|  | for (int index = 0; index < node.entries.length; index++) { | 
|  | Constant? entry = visitConstant(node.entries[index]); | 
|  | if (entry != null) { | 
|  | entries ??= node.entries.toList(growable: false); | 
|  | entries[index] = entry; | 
|  | } | 
|  | } | 
|  | if (typeArgument != null || entries != null) { | 
|  | return new ListConstant( | 
|  | typeArgument ?? node.typeArgument, entries ?? node.entries); | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant? visitSetConstant(SetConstant node) { | 
|  | DartType? typeArgument = computeConstCanonicalType( | 
|  | node.typeArgument, _evaluator.coreTypes, | 
|  | isNonNullableByDefault: _evaluator.isNonNullableByDefault); | 
|  | List<Constant>? entries; | 
|  | for (int index = 0; index < node.entries.length; index++) { | 
|  | Constant? entry = visitConstant(node.entries[index]); | 
|  | if (entry != null) { | 
|  | entries ??= node.entries.toList(growable: false); | 
|  | entries[index] = entry; | 
|  | } | 
|  | } | 
|  | if (typeArgument != null || entries != null) { | 
|  | return new SetConstant( | 
|  | typeArgument ?? node.typeArgument, entries ?? node.entries); | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant? visitInstanceConstant(InstanceConstant node) { | 
|  | List<DartType>? typeArguments; | 
|  | for (int index = 0; index < node.typeArguments.length; index++) { | 
|  | DartType? typeArgument = computeConstCanonicalType( | 
|  | node.typeArguments[index], _evaluator.coreTypes, | 
|  | isNonNullableByDefault: _evaluator.isNonNullableByDefault); | 
|  | if (typeArgument != null) { | 
|  | typeArguments ??= node.typeArguments.toList(growable: false); | 
|  | typeArguments[index] = typeArgument; | 
|  | } | 
|  | } | 
|  | Map<Reference, Constant>? fieldValues; | 
|  | for (MapEntry<Reference, Constant> entry in node.fieldValues.entries) { | 
|  | Reference reference = entry.key; | 
|  | Constant? value = visitConstant(entry.value); | 
|  | if (value != null) { | 
|  | fieldValues ??= new Map<Reference, Constant>.from(node.fieldValues); | 
|  | fieldValues[reference] = value; | 
|  | } | 
|  | } | 
|  | if (typeArguments != null || fieldValues != null) { | 
|  | return new InstanceConstant(node.classReference, | 
|  | typeArguments ?? node.typeArguments, fieldValues ?? node.fieldValues); | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant? 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( | 
|  | this.backend, | 
|  | Map<String, String>? environmentDefines, | 
|  | this.evaluateAnnotations, | 
|  | this.enableTripleShift, | 
|  | this.enableConstFunctions, | 
|  | this.enableConstructorTearOff, | 
|  | this.errorOnUnevaluatedConstant, | 
|  | this.typeEnvironment, | 
|  | ErrorReporter errorReporter, | 
|  | EvaluationMode evaluationMode) | 
|  | : constantEvaluator = new ConstantEvaluator( | 
|  | backend, environmentDefines, typeEnvironment, errorReporter, | 
|  | enableTripleShift: enableTripleShift, | 
|  | enableConstFunctions: enableConstFunctions, | 
|  | errorOnUnevaluatedConstant: errorOnUnevaluatedConstant, | 
|  | evaluationMode: evaluationMode); | 
|  |  | 
|  | /// Whether to preserve constant [Field]s. All use-sites will be rewritten. | 
|  | bool get keepFields => backend.keepFields; | 
|  |  | 
|  | /// Whether to preserve constant [VariableDeclaration]s. All use-sites will be | 
|  | /// rewritten. | 
|  | bool get keepLocals => backend.keepLocals; | 
|  |  | 
|  | // Transform the library/class members: | 
|  |  | 
|  | void convertLibrary(Library library) { | 
|  | _staticTypeContext = | 
|  | new StaticTypeContext.forAnnotations(library, typeEnvironment); | 
|  |  | 
|  | transformAnnotations(library.annotations, library); | 
|  |  | 
|  | transformLibraryDependencyList(library.dependencies, library); | 
|  | transformLibraryPartList(library.parts, library); | 
|  | transformTypedefList(library.typedefs, library); | 
|  | transformClassList(library.classes, library); | 
|  | transformExtensionList(library.extensions, library); | 
|  | transformProcedureList(library.procedures, library); | 
|  | transformFieldList(library.fields, library); | 
|  |  | 
|  | if (!keepFields) { | 
|  | // The transformer API does not iterate over `Library.additionalExports`, | 
|  | // so we manually delete the references to shaken nodes. | 
|  | library.additionalExports.removeWhere((Reference reference) { | 
|  | return reference.node is Field && reference.canonicalName == null; | 
|  | }); | 
|  | } | 
|  | _staticTypeContext = null; | 
|  | } | 
|  |  | 
|  | @override | 
|  | LibraryPart visitLibraryPart(LibraryPart node, TreeNode? removalSentinel) { | 
|  | constantEvaluator.withNewEnvironment(() { | 
|  | transformAnnotations(node.annotations, node); | 
|  | }); | 
|  | return node; | 
|  | } | 
|  |  | 
|  | @override | 
|  | LibraryDependency visitLibraryDependency( | 
|  | LibraryDependency node, TreeNode? removalSentinel) { | 
|  | constantEvaluator.withNewEnvironment(() { | 
|  | transformAnnotations(node.annotations, node); | 
|  | }); | 
|  | return node; | 
|  | } | 
|  |  | 
|  | @override | 
|  | Class visitClass(Class node, TreeNode? removalSentinel) { | 
|  | StaticTypeContext? oldStaticTypeContext = _staticTypeContext; | 
|  | _staticTypeContext = new StaticTypeContext.forAnnotations( | 
|  | node.enclosingLibrary, typeEnvironment); | 
|  | constantEvaluator.withNewEnvironment(() { | 
|  | transformAnnotations(node.annotations, node); | 
|  | transformFieldList(node.fields, node); | 
|  | transformTypeParameterList(node.typeParameters, node); | 
|  | transformConstructorList(node.constructors, node); | 
|  | transformProcedureList(node.procedures, node); | 
|  | 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); | 
|  | transformTypeParameterList(node.typeParametersOfFunctionType, node); | 
|  | transformVariableDeclarationList(node.positionalParameters, node); | 
|  | transformVariableDeclarationList(node.namedParameters, 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 ConstantExpression) { | 
|  | if (result.typeArguments.every(isInstantiated)) { | 
|  | return evaluateAndTransformWithContext(node, result); | 
|  | } else if (enableConstructorTearOff) { | 
|  | Constant constant = expression.constant; | 
|  | if (constant is TypedefTearOffConstant) { | 
|  | Substitution substitution = | 
|  | Substitution.fromPairs(constant.parameters, node.typeArguments); | 
|  | return new Instantiation( | 
|  | new ConstantExpression(constant.tearOffConstant, | 
|  | constant.tearOffConstant.getType(_staticTypeContext!)) | 
|  | ..fileOffset = expression.fileOffset, | 
|  | constant.types.map(substitution.substituteType).toList()); | 
|  | } else { | 
|  | LoweredTypedefTearOff? loweredTypedefTearOff = | 
|  | LoweredTypedefTearOff.fromConstant(constant); | 
|  | if (loweredTypedefTearOff != null) { | 
|  | Constant tearOffConstant = constantEvaluator | 
|  | .canonicalize(loweredTypedefTearOff.targetTearOffConstant); | 
|  | Substitution substitution = Substitution.fromPairs( | 
|  | loweredTypedefTearOff.typedefTearOff.function.typeParameters, | 
|  | node.typeArguments); | 
|  | return new Instantiation( | 
|  | new ConstantExpression(tearOffConstant, | 
|  | tearOffConstant.getType(_staticTypeContext!)) | 
|  | ..fileOffset = expression.fileOffset, | 
|  | loweredTypedefTearOff.typeArguments | 
|  | .map(substitution.substituteType) | 
|  | .toList()); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | 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 && library.isNonNullableByDefault) { | 
|  | for (SwitchCase switchCase in node.cases) { | 
|  | for (Expression caseExpression in switchCase.expressions) { | 
|  | if (caseExpression is ConstantExpression) { | 
|  | if (!constantEvaluator.hasPrimitiveEqual(caseExpression.constant)) { | 
|  | constantEvaluator.errorReporter.report( | 
|  | constantEvaluator.createLocatedMessage( | 
|  | caseExpression, | 
|  | templateConstEvalCaseImplementsEqual.withArguments( | 
|  | caseExpression.constant, | 
|  | constantEvaluator.isNonNullableByDefault)), | 
|  | null); | 
|  | } | 
|  | } else { | 
|  | // If caseExpression is not ConstantExpression, an error is reported | 
|  | // elsewhere. | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | @override | 
|  | TreeNode visitVariableGet(VariableGet node, TreeNode? removalSentinel) { | 
|  | final VariableDeclaration variable = node.variable; | 
|  | if (variable.isConst) { | 
|  | variable.initializer = | 
|  | evaluateAndTransformWithContext(variable, variable.initializer!) | 
|  | ..parent = variable; | 
|  | if (shouldInline(variable.initializer!)) { | 
|  | return evaluateAndTransformWithContext(node, node); | 
|  | } | 
|  | } | 
|  | return super.visitVariableGet(node, removalSentinel); | 
|  | } | 
|  |  | 
|  | @override | 
|  | TreeNode visitListLiteral(ListLiteral node, TreeNode? removalSentinel) { | 
|  | if (node.isConst) { | 
|  | return evaluateAndTransformWithContext(node, node); | 
|  | } | 
|  | return super.visitListLiteral(node, removalSentinel); | 
|  | } | 
|  |  | 
|  | @override | 
|  | TreeNode visitListConcatenation( | 
|  | ListConcatenation node, TreeNode? removalSentinel) { | 
|  | return evaluateAndTransformWithContext(node, node); | 
|  | } | 
|  |  | 
|  | @override | 
|  | TreeNode visitSetLiteral(SetLiteral node, TreeNode? removalSentinel) { | 
|  | if (node.isConst) { | 
|  | return evaluateAndTransformWithContext(node, node); | 
|  | } | 
|  | return super.visitSetLiteral(node, removalSentinel); | 
|  | } | 
|  |  | 
|  | @override | 
|  | TreeNode visitSetConcatenation( | 
|  | SetConcatenation node, TreeNode? removalSentinel) { | 
|  | return evaluateAndTransformWithContext(node, node); | 
|  | } | 
|  |  | 
|  | @override | 
|  | TreeNode visitMapLiteral(MapLiteral node, TreeNode? removalSentinel) { | 
|  | if (node.isConst) { | 
|  | return evaluateAndTransformWithContext(node, node); | 
|  | } | 
|  | return super.visitMapLiteral(node, removalSentinel); | 
|  | } | 
|  |  | 
|  | @override | 
|  | TreeNode visitTypeLiteral(TypeLiteral node, TreeNode? removalSentinel) { | 
|  | if (!containsFreeTypeVariables(node.type)) { | 
|  | return evaluateAndTransformWithContext(node, node); | 
|  | } | 
|  | return super.visitTypeLiteral(node, removalSentinel); | 
|  | } | 
|  |  | 
|  | @override | 
|  | TreeNode visitMapConcatenation( | 
|  | MapConcatenation node, TreeNode? removalSentinel) { | 
|  | return evaluateAndTransformWithContext(node, node); | 
|  | } | 
|  |  | 
|  | @override | 
|  | TreeNode visitConstructorInvocation( | 
|  | ConstructorInvocation node, TreeNode? removalSentinel) { | 
|  | if (node.isConst) { | 
|  | return evaluateAndTransformWithContext(node, node); | 
|  | } | 
|  | return super.visitConstructorInvocation(node, removalSentinel); | 
|  | } | 
|  |  | 
|  | @override | 
|  | TreeNode visitStaticInvocation( | 
|  | StaticInvocation node, TreeNode? removalSentinel) { | 
|  | if (node.isConst) { | 
|  | return evaluateAndTransformWithContext(node, node); | 
|  | } | 
|  | return super.visitStaticInvocation(node, removalSentinel); | 
|  | } | 
|  |  | 
|  | @override | 
|  | TreeNode visitConstantExpression( | 
|  | ConstantExpression node, TreeNode? removalSentinel) { | 
|  | Constant constant = node.constant; | 
|  | if (constant is UnevaluatedConstant) { | 
|  | Expression expression = constant.expression; | 
|  | return evaluateAndTransformWithContext(expression, expression); | 
|  | } else { | 
|  | node.constant = constantEvaluator.canonicalize(constant); | 
|  | return node; | 
|  | } | 
|  | } | 
|  |  | 
|  | Expression evaluateAndTransformWithContext( | 
|  | TreeNode treeContext, Expression node) { | 
|  | return makeConstantExpression(evaluateWithContext(treeContext, node), node); | 
|  | } | 
|  |  | 
|  | Constant evaluateWithContext(TreeNode treeContext, Expression node) { | 
|  | if (treeContext == node) { | 
|  | return constantEvaluator.evaluate(_staticTypeContext!, node); | 
|  | } | 
|  |  | 
|  | return constantEvaluator.evaluate(_staticTypeContext!, node, | 
|  | contextNode: treeContext); | 
|  | } | 
|  |  | 
|  | Expression makeConstantExpression(Constant constant, Expression node) { | 
|  | if (constant is UnevaluatedConstant && | 
|  | constant.expression is InvalidExpression) { | 
|  | return constant.expression; | 
|  | } | 
|  | return new ConstantExpression( | 
|  | constant, node.getStaticType(_staticTypeContext!)) | 
|  | ..fileOffset = node.fileOffset; | 
|  | } | 
|  |  | 
|  | bool shouldInline(Expression initializer) { | 
|  | if (initializer is ConstantExpression) { | 
|  | return backend.shouldInlineConstant(initializer); | 
|  | } | 
|  | return true; | 
|  | } | 
|  | } | 
|  |  | 
|  | class ConstantEvaluator implements ExpressionVisitor<Constant> { | 
|  | final ConstantsBackend backend; | 
|  | final NumberSemantics numberSemantics; | 
|  | late ConstantIntFolder intFolder; | 
|  | Map<String, String>? environmentDefines; | 
|  | final bool errorOnUnevaluatedConstant; | 
|  | final CoreTypes coreTypes; | 
|  | final TypeEnvironment typeEnvironment; | 
|  | StaticTypeContext? _staticTypeContext; | 
|  | final ErrorReporter errorReporter; | 
|  | final EvaluationMode evaluationMode; | 
|  |  | 
|  | final bool enableTripleShift; | 
|  | final bool enableConstFunctions; | 
|  |  | 
|  | final Map<Constant, Constant> canonicalizationCache; | 
|  | final Map<Node, Constant?> nodeCache; | 
|  | final CloneVisitorNotMembers cloner = new CloneVisitorNotMembers(); | 
|  |  | 
|  | late Map<Class, bool> primitiveEqualCache; | 
|  |  | 
|  | final NullConstant nullConstant = new NullConstant(); | 
|  | final BoolConstant trueConstant = new BoolConstant(true); | 
|  | final BoolConstant falseConstant = new BoolConstant(false); | 
|  |  | 
|  | InstanceBuilder? instanceBuilder; | 
|  | EvaluationEnvironment env; | 
|  | Set<Expression> replacementNodes = new Set<Expression>.identity(); | 
|  | Map<Constant, Constant> lowered = new Map<Constant, Constant>.identity(); | 
|  |  | 
|  | bool seenUnevaluatedChild = false; // Any children that were left unevaluated? | 
|  | int lazyDepth = -1; // Current nesting depth of lazy regions. | 
|  |  | 
|  | bool get shouldBeUnevaluated => seenUnevaluatedChild || lazyDepth != 0; | 
|  |  | 
|  | bool get targetingJavaScript => numberSemantics == NumberSemantics.js; | 
|  |  | 
|  | bool get isNonNullableByDefault => | 
|  | _staticTypeContext!.nonNullable == Nullability.nonNullable; | 
|  |  | 
|  | late ConstantWeakener _weakener; | 
|  |  | 
|  | ConstantEvaluator(this.backend, this.environmentDefines, this.typeEnvironment, | 
|  | this.errorReporter, | 
|  | {this.enableTripleShift = false, | 
|  | this.enableConstFunctions = false, | 
|  | this.errorOnUnevaluatedConstant = false, | 
|  | this.evaluationMode: EvaluationMode.weak}) | 
|  | : numberSemantics = backend.numberSemantics, | 
|  | coreTypes = typeEnvironment.coreTypes, | 
|  | canonicalizationCache = <Constant, Constant>{}, | 
|  | nodeCache = <Node, Constant?>{}, | 
|  | env = new EvaluationEnvironment() { | 
|  | if (environmentDefines == null && !backend.supportsUnevaluatedConstants) { | 
|  | throw new ArgumentError( | 
|  | "No 'environmentDefines' passed to the constant evaluator but the " | 
|  | "ConstantsBackend does not support unevaluated constants."); | 
|  | } | 
|  | intFolder = new ConstantIntFolder.forSemantics(this, numberSemantics); | 
|  | primitiveEqualCache = <Class, bool>{ | 
|  | coreTypes.boolClass: true, | 
|  | coreTypes.doubleClass: false, | 
|  | coreTypes.intClass: true, | 
|  | coreTypes.internalSymbolClass: true, | 
|  | coreTypes.listClass: true, | 
|  | coreTypes.mapClass: true, | 
|  | coreTypes.objectClass: true, | 
|  | coreTypes.setClass: true, | 
|  | coreTypes.stringClass: true, | 
|  | coreTypes.symbolClass: true, | 
|  | coreTypes.typeClass: true, | 
|  | }; | 
|  | _weakener = new ConstantWeakener(this); | 
|  | } | 
|  |  | 
|  | DartType convertType(DartType type) { | 
|  | switch (evaluationMode) { | 
|  | case EvaluationMode.strong: | 
|  | case EvaluationMode.agnostic: | 
|  | return norm(coreTypes, type); | 
|  | case EvaluationMode.weak: | 
|  | type = norm(coreTypes, type); | 
|  | return computeConstCanonicalType(type, coreTypes, | 
|  | isNonNullableByDefault: isNonNullableByDefault) ?? | 
|  | type; | 
|  | } | 
|  | } | 
|  |  | 
|  | List<DartType> convertTypes(List<DartType> types) { | 
|  | switch (evaluationMode) { | 
|  | case EvaluationMode.strong: | 
|  | case EvaluationMode.agnostic: | 
|  | return types.map((DartType type) => norm(coreTypes, type)).toList(); | 
|  | case EvaluationMode.weak: | 
|  | return types.map((DartType type) { | 
|  | type = norm(coreTypes, type); | 
|  | return computeConstCanonicalType(type, coreTypes, | 
|  | isNonNullableByDefault: isNonNullableByDefault) ?? | 
|  | type; | 
|  | }).toList(); | 
|  | } | 
|  | } | 
|  |  | 
|  | LocatedMessage createLocatedMessage(TreeNode? node, Message message) { | 
|  | Uri? uri = getFileUri(node); | 
|  | if (uri == null) { | 
|  | // TODO(johnniwinther): Ensure that we always have a uri. | 
|  | return message.withoutLocation(); | 
|  | } | 
|  | int offset = getFileOffset(uri, node); | 
|  | return message.withLocation(uri, offset, noLength); | 
|  | } | 
|  |  | 
|  | // TODO(johnniwinther): Avoid this by adding a current file uri field. | 
|  | Uri? getFileUri(TreeNode? node) { | 
|  | while (node != null) { | 
|  | if (node is FileUriNode) { | 
|  | return node.fileUri; | 
|  | } | 
|  | node = node.parent; | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | int getFileOffset(Uri? uri, TreeNode? node) { | 
|  | if (uri == null) return TreeNode.noOffset; | 
|  | while (node != null && node.fileOffset == TreeNode.noOffset) { | 
|  | node = node.parent; | 
|  | } | 
|  | return node == null ? TreeNode.noOffset : node.fileOffset; | 
|  | } | 
|  |  | 
|  | /// Evaluate [node] and possibly cache the evaluation result. | 
|  | /// Returns UnevaluatedConstant if the constant could not be evaluated. | 
|  | /// If the expression in the UnevaluatedConstant is an InvalidExpression, | 
|  | /// an error occurred during constant evaluation. | 
|  | Constant evaluate(StaticTypeContext context, Expression node, | 
|  | {TreeNode? contextNode}) { | 
|  | _staticTypeContext = context; | 
|  | seenUnevaluatedChild = false; | 
|  | lazyDepth = 0; | 
|  | Constant result = _evaluateSubexpression(node); | 
|  | if (result is AbortConstant) { | 
|  | if (result is _AbortDueToErrorConstant) { | 
|  | final LocatedMessage locatedMessageActualError = | 
|  | createLocatedMessage(result.node, result.message); | 
|  | final List<LocatedMessage> contextMessages = <LocatedMessage>[ | 
|  | locatedMessageActualError | 
|  | ]; | 
|  | if (result.context != null) contextMessages.addAll(result.context!); | 
|  | if (contextNode != null && contextNode != result.node) { | 
|  | contextMessages | 
|  | .add(createLocatedMessage(contextNode, messageConstEvalContext)); | 
|  | } | 
|  |  | 
|  | { | 
|  | final LocatedMessage locatedMessage = | 
|  | createLocatedMessage(node, messageConstEvalStartingPoint); | 
|  | errorReporter.report(locatedMessage, contextMessages); | 
|  | } | 
|  | return new UnevaluatedConstant( | 
|  | new InvalidExpression(result.message.message)); | 
|  | } | 
|  | if (result is _AbortDueToInvalidExpressionConstant) { | 
|  | InvalidExpression invalid = new InvalidExpression(result.message) | 
|  | ..fileOffset = node.fileOffset; | 
|  | errorReporter.reportInvalidExpression(invalid); | 
|  | return new UnevaluatedConstant(invalid); | 
|  | } else if (result is _AbortDueToThrowConstant) { | 
|  | final Object value = result.throwValue; | 
|  | Message? message; | 
|  | if (value is Constant) { | 
|  | message = templateConstEvalUnhandledException.withArguments( | 
|  | value, isNonNullableByDefault); | 
|  | } else if (value is Error) { | 
|  | message = templateConstEvalUnhandledCoreException | 
|  | .withArguments(value.toString()); | 
|  | } | 
|  | assert(message != null); | 
|  |  | 
|  | final LocatedMessage locatedMessageActualError = | 
|  | createLocatedMessage(result.node, message!); | 
|  | final List<LocatedMessage> contextMessages = <LocatedMessage>[ | 
|  | locatedMessageActualError | 
|  | ]; | 
|  | { | 
|  | final LocatedMessage locatedMessage = | 
|  | createLocatedMessage(node, messageConstEvalStartingPoint); | 
|  | errorReporter.report(locatedMessage, contextMessages); | 
|  | } | 
|  | return new UnevaluatedConstant(new InvalidExpression(message.message)); | 
|  | } | 
|  | throw "Unexpected error constant"; | 
|  | } | 
|  | if (result is UnevaluatedConstant) { | 
|  | if (errorOnUnevaluatedConstant) { | 
|  | return createErrorConstant(node, messageConstEvalUnevaluated); | 
|  | } | 
|  | return canonicalize(new UnevaluatedConstant( | 
|  | removeRedundantFileUriExpressions(result.expression))); | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | /// Execute a function body using the [StatementConstantEvaluator]. | 
|  | Constant executeBody(Statement statement) { | 
|  | StatementConstantEvaluator statementEvaluator = | 
|  | new StatementConstantEvaluator(this); | 
|  | ExecutionStatus status = statement.accept(statementEvaluator); | 
|  | if (status is ReturnStatus) { | 
|  | Constant? value = status.value; | 
|  | if (value == null) { | 
|  | // Void return type from executing the function body. | 
|  | return new NullConstant(); | 
|  | } | 
|  | return value; | 
|  | } else if (status is AbortStatus) { | 
|  | return status.error; | 
|  | } else if (status is ProceedStatus) { | 
|  | // No return statement in function body with void return type. | 
|  | return new NullConstant(); | 
|  | } | 
|  | return createInvalidExpressionConstant(statement, | 
|  | 'No valid constant returned from the execution of $statement.'); | 
|  | } | 
|  |  | 
|  | /// Returns [null] on success and an error-"constant" on failure, as such the | 
|  | /// return value should be checked. | 
|  | AbortConstant? executeConstructorBody(Constructor constructor) { | 
|  | final Statement body = constructor.function.body!; | 
|  | StatementConstantEvaluator statementEvaluator = | 
|  | new StatementConstantEvaluator(this); | 
|  | ExecutionStatus status = body.accept(statementEvaluator); | 
|  | if (status is AbortStatus) { | 
|  | return status.error; | 
|  | } else if (status is ReturnStatus) { | 
|  | if (status.value == null) return null; | 
|  | // Should not be reachable. | 
|  | return createInvalidExpressionConstant( | 
|  | constructor, "Constructors can't have a return value."); | 
|  | } else if (status is! ProceedStatus) { | 
|  | return createInvalidExpressionConstant( | 
|  | constructor, "Invalid execution status of constructor body."); | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | /// Create an error-constant indicating that an error has been detected during | 
|  | /// constant evaluation. | 
|  | AbortConstant createErrorConstant(TreeNode node, Message message, | 
|  | {List<LocatedMessage>? context}) { | 
|  | return new _AbortDueToErrorConstant(node, message, context: context); | 
|  | } | 
|  |  | 
|  | /// Create an error-constant indicating a construct that should not occur | 
|  | /// inside a potentially constant expression. | 
|  | /// It is assumed that an error has already been reported. | 
|  | AbortConstant createInvalidExpressionConstant(TreeNode node, String message) { | 
|  | return new _AbortDueToInvalidExpressionConstant(node, message); | 
|  | } | 
|  |  | 
|  | /// Produce an unevaluated constant node for an expression. | 
|  | Constant unevaluated(Expression original, Expression replacement) { | 
|  | replacement.fileOffset = original.fileOffset; | 
|  | return new UnevaluatedConstant( | 
|  | new FileUriExpression(replacement, getFileUri(original)!) | 
|  | ..fileOffset = original.fileOffset); | 
|  | } | 
|  |  | 
|  | Expression removeRedundantFileUriExpressions(Expression node) { | 
|  | return node.accept(new RedundantFileUriExpressionRemover()) as Expression; | 
|  | } | 
|  |  | 
|  | /// Extract an expression from a (possibly unevaluated) constant to become | 
|  | /// part of the expression tree of another unevaluated constant. | 
|  | /// Makes sure a particular expression occurs only once in the tree by | 
|  | /// cloning further instances. | 
|  | Expression extract(Constant constant) { | 
|  | Expression expression = constant.asExpression(); | 
|  | if (!replacementNodes.add(expression)) { | 
|  | expression = cloner.clone(expression); | 
|  | replacementNodes.add(expression); | 
|  | } | 
|  | return expression; | 
|  | } | 
|  |  | 
|  | /// Enter a region of lazy evaluation. All leaf nodes are evaluated normally | 
|  | /// (to ensure inlining of referenced local variables), but composite nodes | 
|  | /// always treat their children as unevaluated, resulting in a partially | 
|  | /// evaluated clone of the original expression tree. | 
|  | /// Lazy evaluation is used for the subtrees of lazy operations with | 
|  | /// unevaluated conditions to ensure no errors are reported for problems | 
|  | /// in the subtree as long as the subtree is potentially constant. | 
|  | void enterLazy() => lazyDepth++; | 
|  |  | 
|  | /// Leave a (possibly nested) region of lazy evaluation. | 
|  | void leaveLazy() => lazyDepth--; | 
|  |  | 
|  | Constant lower(Constant original, Constant replacement) { | 
|  | if (!identical(original, replacement)) { | 
|  | original = canonicalize(original); | 
|  | replacement = canonicalize(replacement); | 
|  | lowered[replacement] = original; | 
|  | return replacement; | 
|  | } | 
|  | return canonicalize(replacement); | 
|  | } | 
|  |  | 
|  | Constant unlower(Constant constant) { | 
|  | return lowered[constant] ?? constant; | 
|  | } | 
|  |  | 
|  | Constant lowerListConstant(ListConstant constant) { | 
|  | if (shouldBeUnevaluated) return constant; | 
|  | return lower(constant, backend.lowerListConstant(constant)); | 
|  | } | 
|  |  | 
|  | Constant lowerSetConstant(SetConstant constant) { | 
|  | if (shouldBeUnevaluated) return constant; | 
|  | return lower(constant, backend.lowerSetConstant(constant)); | 
|  | } | 
|  |  | 
|  | Constant lowerMapConstant(MapConstant constant) { | 
|  | if (shouldBeUnevaluated) return constant; | 
|  | return lower(constant, backend.lowerMapConstant(constant)); | 
|  | } | 
|  |  | 
|  | Map<Uri, Set<Reference>> _constructorCoverage = {}; | 
|  |  | 
|  | ConstantCoverage getConstantCoverage() { | 
|  | return new ConstantCoverage(_constructorCoverage); | 
|  | } | 
|  |  | 
|  | void _recordConstructorCoverage(Constructor constructor, TreeNode caller) { | 
|  | Uri currentUri = getFileUri(caller)!; | 
|  | Set<Reference> uriCoverage = _constructorCoverage[currentUri] ??= {}; | 
|  | uriCoverage.add(constructor.reference); | 
|  | } | 
|  |  | 
|  | /// Evaluate [node] and possibly cache the evaluation result. | 
|  | /// | 
|  | /// Returns [_AbortDueToErrorConstant] or | 
|  | /// [_AbortDueToInvalidExpressionConstant] (both of which is an | 
|  | /// [AbortConstant]) if the expression can't be evaluated. | 
|  | /// As such the return value should be checked (e.g. `is AbortConstant`) | 
|  | /// before further use. | 
|  | Constant _evaluateSubexpression(Expression node) { | 
|  | if (node is ConstantExpression) { | 
|  | if (node.constant is! UnevaluatedConstant) { | 
|  | // ConstantExpressions just pointing to an actual constant can be | 
|  | // short-circuited. Note that it's accepted instead of just returned to | 
|  | // get canonicalization. | 
|  | return node.accept(this); | 
|  | } | 
|  | } else if (node is BasicLiteral) { | 
|  | // Basic literals (string literals, int literals, double literals, | 
|  | // bool literals and null literals) can be short-circuited too. | 
|  | return node.accept(this); | 
|  | } | 
|  |  | 
|  | bool wasUnevaluated = seenUnevaluatedChild; | 
|  | seenUnevaluatedChild = false; | 
|  | Constant result; | 
|  | if (env.isEmpty) { | 
|  | // We only try to evaluate the same [node] *once* within an empty | 
|  | // environment. | 
|  | // For const functions, recompute getters instead of using the cached | 
|  | // value. | 
|  | bool isGetter = node is InstanceGet; | 
|  | if (nodeCache.containsKey(node) && !(enableConstFunctions && isGetter)) { | 
|  | Constant? cachedResult = nodeCache[node]; | 
|  | if (cachedResult == null) { | 
|  | // [null] is a sentinel value only used when still evaluating the same | 
|  | // node. | 
|  | return createErrorConstant(node, messageConstEvalCircularity); | 
|  | } | 
|  | result = cachedResult; | 
|  | } else { | 
|  | nodeCache[node] = null; | 
|  | Constant evaluatedResult = node.accept(this); | 
|  | if (evaluatedResult is AbortConstant) { | 
|  | nodeCache.remove(node); | 
|  | return evaluatedResult; | 
|  | } else { | 
|  | nodeCache[node] = evaluatedResult; | 
|  | } | 
|  | result = evaluatedResult; | 
|  | } | 
|  | } else { | 
|  | bool sentinelInserted = false; | 
|  | if (nodeCache.containsKey(node)) { | 
|  | bool isRecursiveFunctionCall = node is InstanceInvocation || | 
|  | node is FunctionInvocation || | 
|  | node is LocalFunctionInvocation || | 
|  | node is StaticInvocation; | 
|  | if (nodeCache[node] == null && | 
|  | !(enableConstFunctions && isRecursiveFunctionCall)) { | 
|  | // recursive call | 
|  | return createErrorConstant(node, messageConstEvalCircularity); | 
|  | } | 
|  | // else we've seen the node before and come to a result -> we won't | 
|  | // go into an infinite loop here either. | 
|  | } else { | 
|  | // We haven't seen this node before. Risk of loop. | 
|  | nodeCache[node] = null; | 
|  | sentinelInserted = true; | 
|  | } | 
|  | Constant evaluatedResult = node.accept(this); | 
|  | if (sentinelInserted) { | 
|  | nodeCache.remove(node); | 
|  | } | 
|  | if (evaluatedResult is AbortConstant) { | 
|  | return evaluatedResult; | 
|  | } | 
|  | result = evaluatedResult; | 
|  | } | 
|  | seenUnevaluatedChild = wasUnevaluated || result is UnevaluatedConstant; | 
|  | return result; | 
|  | } | 
|  |  | 
|  | Constant _evaluateNullableSubexpression(Expression? node) { | 
|  | if (node == null) return nullConstant; | 
|  | return _evaluateSubexpression(node); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant defaultExpression(Expression node) { | 
|  | // Only a subset of the expression language is valid for constant | 
|  | // evaluation. | 
|  | return createInvalidExpressionConstant( | 
|  | node, 'Constant evaluation has no support for ${node.runtimeType}!'); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitFileUriExpression(FileUriExpression node) { | 
|  | return _evaluateSubexpression(node.expression); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitNullLiteral(NullLiteral node) => nullConstant; | 
|  |  | 
|  | @override | 
|  | Constant visitBoolLiteral(BoolLiteral node) { | 
|  | return makeBoolConstant(node.value); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitIntLiteral(IntLiteral node) { | 
|  | // The frontend ensures that integer literals are valid according to the | 
|  | // target representation. | 
|  | return canonicalize(intFolder.makeIntConstant(node.value, unsigned: true)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitDoubleLiteral(DoubleLiteral node) { | 
|  | return canonicalize(new DoubleConstant(node.value)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitStringLiteral(StringLiteral node) { | 
|  | return canonicalize(new StringConstant(node.value)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitTypeLiteral(TypeLiteral node) { | 
|  | DartType? type = _evaluateDartType(node, node.type); | 
|  | if (type != null) { | 
|  | type = convertType(type); | 
|  | } | 
|  | if (type == null) { | 
|  | AbortConstant error = _gotError!; | 
|  | _gotError = null; | 
|  | return error; | 
|  | } | 
|  | assert(_gotError == null); | 
|  | // ignore: unnecessary_null_comparison | 
|  | assert(type != null); | 
|  | return canonicalize(new TypeLiteralConstant(type)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitConstantExpression(ConstantExpression node) { | 
|  | Constant constant = node.constant; | 
|  | Constant result = constant; | 
|  | if (constant is UnevaluatedConstant) { | 
|  | if (environmentDefines != null) { | 
|  | result = _evaluateSubexpression(constant.expression); | 
|  | if (result is AbortConstant) return result; | 
|  | } else { | 
|  | // Still no environment. Doing anything is just wasted time. | 
|  | result = constant; | 
|  | } | 
|  | } | 
|  | // If there were already constants in the AST then we make sure we | 
|  | // re-canonicalize them.  After running the transformer we will therefore | 
|  | // have a fully-canonicalized constant DAG with roots coming from the | 
|  | // [ConstantExpression] nodes in the AST. | 
|  | return canonicalize(result); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitListLiteral(ListLiteral node) { | 
|  | if (!node.isConst && !enableConstFunctions) { | 
|  | return createInvalidExpressionConstant(node, "Non-constant list literal"); | 
|  | } | 
|  | final ListConstantBuilder builder = new ListConstantBuilder( | 
|  | node, convertType(node.typeArgument), this, | 
|  | isMutable: !node.isConst); | 
|  | // These expressions are at the same level, so one of them being | 
|  | // unevaluated doesn't mean a sibling is or has an unevaluated child. | 
|  | // We therefore reset it before each call, combine it and set it correctly | 
|  | // at the end. | 
|  | bool wasOrBecameUnevaluated = seenUnevaluatedChild; | 
|  | for (Expression element in node.expressions) { | 
|  | seenUnevaluatedChild = false; | 
|  | AbortConstant? error = builder.add(element); | 
|  | wasOrBecameUnevaluated |= seenUnevaluatedChild; | 
|  | if (error != null) return error; | 
|  | } | 
|  | seenUnevaluatedChild = wasOrBecameUnevaluated; | 
|  | return builder.build(); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitListConcatenation(ListConcatenation node) { | 
|  | final ListConstantBuilder builder = | 
|  | new ListConstantBuilder(node, convertType(node.typeArgument), this); | 
|  | for (Expression list in node.lists) { | 
|  | AbortConstant? error = builder.addSpread(list); | 
|  | if (error != null) return error; | 
|  | } | 
|  | return builder.build(); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitSetLiteral(SetLiteral node) { | 
|  | if (!node.isConst) { | 
|  | return createInvalidExpressionConstant(node, "Non-constant set literal"); | 
|  | } | 
|  | final SetConstantBuilder builder = | 
|  | new SetConstantBuilder(node, convertType(node.typeArgument), this); | 
|  | // These expressions are at the same level, so one of them being | 
|  | // unevaluated doesn't mean a sibling is or has an unevaluated child. | 
|  | // We therefore reset it before each call, combine it and set it correctly | 
|  | // at the end. | 
|  | bool wasOrBecameUnevaluated = seenUnevaluatedChild; | 
|  | for (Expression element in node.expressions) { | 
|  | seenUnevaluatedChild = false; | 
|  | AbortConstant? error = builder.add(element); | 
|  | wasOrBecameUnevaluated |= seenUnevaluatedChild; | 
|  | if (error != null) return error; | 
|  | } | 
|  | seenUnevaluatedChild = wasOrBecameUnevaluated; | 
|  | return builder.build(); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitSetConcatenation(SetConcatenation node) { | 
|  | final SetConstantBuilder builder = | 
|  | new SetConstantBuilder(node, convertType(node.typeArgument), this); | 
|  | for (Expression set_ in node.sets) { | 
|  | AbortConstant? error = builder.addSpread(set_); | 
|  | if (error != null) return error; | 
|  | } | 
|  | return builder.build(); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitMapLiteral(MapLiteral node) { | 
|  | if (!node.isConst) { | 
|  | return createInvalidExpressionConstant(node, "Non-constant map literal"); | 
|  | } | 
|  | final MapConstantBuilder builder = new MapConstantBuilder( | 
|  | node, convertType(node.keyType), convertType(node.valueType), this); | 
|  | // These expressions are at the same level, so one of them being | 
|  | // unevaluated doesn't mean a sibling is or has an unevaluated child. | 
|  | // We therefore reset it before each call, combine it and set it correctly | 
|  | // at the end. | 
|  | bool wasOrBecameUnevaluated = seenUnevaluatedChild; | 
|  | for (MapLiteralEntry element in node.entries) { | 
|  | seenUnevaluatedChild = false; | 
|  | AbortConstant? error = builder.add(element); | 
|  | wasOrBecameUnevaluated |= seenUnevaluatedChild; | 
|  | if (error != null) return error; | 
|  | } | 
|  | seenUnevaluatedChild = wasOrBecameUnevaluated; | 
|  | return builder.build(); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitMapConcatenation(MapConcatenation node) { | 
|  | final MapConstantBuilder builder = new MapConstantBuilder( | 
|  | node, convertType(node.keyType), convertType(node.valueType), this); | 
|  | for (Expression map in node.maps) { | 
|  | AbortConstant? error = builder.addSpread(map); | 
|  | if (error != null) return error; | 
|  | } | 
|  | return builder.build(); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitFunctionExpression(FunctionExpression node) { | 
|  | if (enableConstFunctions) { | 
|  | return new FunctionValue(node.function, env); | 
|  | } | 
|  | return createInvalidExpressionConstant(node, "Function literal"); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitConstructorInvocation(ConstructorInvocation node) { | 
|  | if (!node.isConst && !enableConstFunctions) { | 
|  | return createInvalidExpressionConstant( | 
|  | node, 'Non-constant constructor invocation "$node".'); | 
|  | } | 
|  |  | 
|  | final Constructor constructor = node.target; | 
|  | AbortConstant? error = checkConstructorConst(node, constructor); | 
|  | if (error != null) return error; | 
|  |  | 
|  | final Class klass = constructor.enclosingClass; | 
|  | if (klass.isAbstract) { | 
|  | // Probably unreachable. | 
|  | return createInvalidExpressionConstant( | 
|  | node, 'Constructor "$node" belongs to abstract class "${klass}".'); | 
|  | } | 
|  |  | 
|  | final List<Constant>? positionals = | 
|  | _evaluatePositionalArguments(node.arguments); | 
|  | if (positionals == null) { | 
|  | AbortConstant error = _gotError!; | 
|  | _gotError = null; | 
|  | return error; | 
|  | } | 
|  | assert(_gotError == null); | 
|  | // ignore: unnecessary_null_comparison | 
|  | assert(positionals != null); | 
|  |  | 
|  | final Map<String, Constant>? named = | 
|  | _evaluateNamedArguments(node.arguments); | 
|  | if (named == null) { | 
|  | AbortConstant error = _gotError!; | 
|  | _gotError = null; | 
|  | return error; | 
|  | } | 
|  | assert(_gotError == null); | 
|  | // ignore: unnecessary_null_comparison | 
|  | assert(named != null); | 
|  |  | 
|  | bool isSymbol = klass == coreTypes.internalSymbolClass; | 
|  | if (isSymbol && shouldBeUnevaluated) { | 
|  | return unevaluated( | 
|  | node, | 
|  | new ConstructorInvocation(constructor, | 
|  | unevaluatedArguments(positionals, named, node.arguments.types), | 
|  | isConst: true)); | 
|  | } | 
|  |  | 
|  | // Special case the dart:core's Symbol class here and convert it to a | 
|  | // [SymbolConstant].  For invalid values we report a compile-time error. | 
|  | if (isSymbol) { | 
|  | final Constant nameValue = positionals.single; | 
|  |  | 
|  | // 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 createErrorConstant( | 
|  | node.arguments.positional.first, | 
|  | templateConstEvalInvalidSymbolName.withArguments( | 
|  | nameValue, isNonNullableByDefault)); | 
|  | } | 
|  |  | 
|  | List<DartType>? types = _evaluateTypeArguments(node, node.arguments); | 
|  | if (types == null) { | 
|  | AbortConstant error = _gotError!; | 
|  | _gotError = null; | 
|  | return error; | 
|  | } | 
|  | assert(_gotError == null); | 
|  | // ignore: unnecessary_null_comparison | 
|  | assert(types != null); | 
|  |  | 
|  | final List<DartType> typeArguments = convertTypes(types); | 
|  |  | 
|  | // Fill in any missing type arguments with "dynamic". | 
|  | for (int i = typeArguments.length; i < klass.typeParameters.length; i++) { | 
|  | // Probably unreachable. | 
|  | typeArguments.add(const DynamicType()); | 
|  | } | 
|  |  | 
|  | // Start building a new instance. | 
|  | return withNewInstanceBuilder(klass, typeArguments, () { | 
|  | // "Run" the constructor (and any super constructor calls), which will | 
|  | // initialize the fields of the new instance. | 
|  | if (shouldBeUnevaluated) { | 
|  | enterLazy(); | 
|  | AbortConstant? error = handleConstructorInvocation( | 
|  | constructor, typeArguments, positionals, named, node); | 
|  | if (error != null) return error; | 
|  | leaveLazy(); | 
|  | return unevaluated(node, instanceBuilder!.buildUnevaluatedInstance()); | 
|  | } | 
|  | AbortConstant? error = handleConstructorInvocation( | 
|  | constructor, typeArguments, positionals, named, node); | 
|  | if (error != null) return error; | 
|  | if (shouldBeUnevaluated) { | 
|  | return unevaluated(node, instanceBuilder!.buildUnevaluatedInstance()); | 
|  | } | 
|  | return canonicalize(instanceBuilder!.buildInstance()); | 
|  | }); | 
|  | } | 
|  |  | 
|  | /// Returns [null] on success and an error-"constant" on failure, as such the | 
|  | /// return value should be checked. | 
|  | AbortConstant? checkConstructorConst(TreeNode node, Constructor constructor) { | 
|  | if (!constructor.isConst) { | 
|  | return createInvalidExpressionConstant( | 
|  | node, 'Non-const constructor invocation.'); | 
|  | } | 
|  | if (constructor.function.body != null && | 
|  | constructor.function.body is! EmptyStatement && | 
|  | !enableConstFunctions) { | 
|  | // Probably unreachable. | 
|  | return createInvalidExpressionConstant( | 
|  | node, | 
|  | 'Constructor "$node" has non-trivial body ' | 
|  | '"${constructor.function.body.runtimeType}".'); | 
|  | } else if (constructor.isExternal) { | 
|  | return createErrorConstant(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, constructor); | 
|  | if (error != null) return error; | 
|  | List<DartType>? types = _evaluateSuperTypeArguments( | 
|  | init, constructor.enclosingClass.supertype!); | 
|  | if (types == null) { | 
|  | AbortConstant error = _gotError!; | 
|  | _gotError = null; | 
|  | return error; | 
|  | } | 
|  | assert(_gotError == null); | 
|  | // ignore: unnecessary_null_comparison | 
|  | assert(types != null); | 
|  |  | 
|  | List<Constant>? positionalArguments = | 
|  | _evaluatePositionalArguments(init.arguments); | 
|  | if (positionalArguments == null) { | 
|  | AbortConstant error = _gotError!; | 
|  | _gotError = null; | 
|  | return error; | 
|  | } | 
|  | assert(_gotError == null); | 
|  | // ignore: unnecessary_null_comparison | 
|  | assert(positionalArguments != null); | 
|  | Map<String, Constant>? namedArguments = | 
|  | _evaluateNamedArguments(init.arguments); | 
|  | if (namedArguments == null) { | 
|  | AbortConstant error = _gotError!; | 
|  | _gotError = null; | 
|  | return error; | 
|  | } | 
|  | assert(_gotError == null); | 
|  | // ignore: unnecessary_null_comparison | 
|  | assert(namedArguments != null); | 
|  | error = handleConstructorInvocation( | 
|  | init.target, types, positionalArguments, namedArguments, 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, constructor); | 
|  | if (error != null) return error; | 
|  | List<Constant>? positionalArguments = | 
|  | _evaluatePositionalArguments(init.arguments); | 
|  | if (positionalArguments == null) { | 
|  | AbortConstant error = _gotError!; | 
|  | _gotError = null; | 
|  | return error; | 
|  | } | 
|  | assert(_gotError == null); | 
|  | // ignore: unnecessary_null_comparison | 
|  | assert(positionalArguments != null); | 
|  |  | 
|  | Map<String, Constant>? namedArguments = | 
|  | _evaluateNamedArguments(init.arguments); | 
|  | if (namedArguments == null) { | 
|  | AbortConstant error = _gotError!; | 
|  | _gotError = null; | 
|  | return error; | 
|  | } | 
|  | assert(_gotError == null); | 
|  | // ignore: unnecessary_null_comparison | 
|  | assert(namedArguments != null); | 
|  |  | 
|  | error = handleConstructorInvocation(init.target, typeArguments, | 
|  | positionalArguments, namedArguments, 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. | 
|  | return createInvalidExpressionConstant( | 
|  | constructor, | 
|  | 'No support for handling initializer of type ' | 
|  | '"${init.runtimeType}".'); | 
|  | } | 
|  | } | 
|  |  | 
|  | for (UnevaluatedConstant constant in env.unevaluatedUnreadConstants) { | 
|  | instanceBuilder!.unusedArguments.add(extract(constant)); | 
|  | } | 
|  |  | 
|  | // ignore: unnecessary_null_comparison | 
|  | if (enableConstFunctions && constructor.function != null) { | 
|  | AbortConstant? error = executeConstructorBody(constructor); | 
|  | if (error != null) return error; | 
|  | } | 
|  |  | 
|  | return null; | 
|  | }); | 
|  | } | 
|  |  | 
|  | /// Returns [null] on success and an error-"constant" on failure, as such the | 
|  | /// return value should be checked. | 
|  | AbortConstant? checkAssert(AssertStatement statement) { | 
|  | final Constant condition = _evaluateSubexpression(statement.condition); | 
|  | if (condition is AbortConstant) return condition; | 
|  |  | 
|  | if (shouldBeUnevaluated) { | 
|  | Expression? message = null; | 
|  | if (statement.message != null) { | 
|  | enterLazy(); | 
|  | Constant constant = _evaluateSubexpression(statement.message!); | 
|  | if (constant is AbortConstant) return constant; | 
|  | message = extract(constant); | 
|  | leaveLazy(); | 
|  | } | 
|  | instanceBuilder!.asserts.add(new AssertStatement(extract(condition), | 
|  | message: message, | 
|  | conditionStartOffset: statement.conditionStartOffset, | 
|  | conditionEndOffset: statement.conditionEndOffset)); | 
|  | } else if (condition is BoolConstant) { | 
|  | if (!condition.value) { | 
|  | if (statement.message == null) { | 
|  | return createErrorConstant( | 
|  | statement.condition, messageConstEvalFailedAssertion); | 
|  | } | 
|  | final Constant message = _evaluateSubexpression(statement.message!); | 
|  | if (message is AbortConstant) return message; | 
|  | if (shouldBeUnevaluated) { | 
|  | instanceBuilder!.asserts.add(new AssertStatement(extract(condition), | 
|  | message: extract(message), | 
|  | conditionStartOffset: statement.conditionStartOffset, | 
|  | conditionEndOffset: statement.conditionEndOffset)); | 
|  | } else if (message is StringConstant) { | 
|  | return createErrorConstant( | 
|  | statement.condition, | 
|  | templateConstEvalFailedAssertionWithMessage | 
|  | .withArguments(message.value)); | 
|  | } else { | 
|  | return createErrorConstant( | 
|  | statement.message!, | 
|  | templateConstEvalInvalidType.withArguments( | 
|  | message, | 
|  | typeEnvironment.coreTypes.stringLegacyRawType, | 
|  | message.getType(_staticTypeContext!), | 
|  | isNonNullableByDefault)); | 
|  | } | 
|  | } | 
|  | } else { | 
|  | return createErrorConstant( | 
|  | statement.condition, | 
|  | templateConstEvalInvalidType.withArguments( | 
|  | condition, | 
|  | typeEnvironment.coreTypes.boolLegacyRawType, | 
|  | condition.getType(_staticTypeContext!), | 
|  | isNonNullableByDefault)); | 
|  | } | 
|  |  | 
|  | return null; | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitInvalidExpression(InvalidExpression node) { | 
|  | return createInvalidExpressionConstant(node, node.message ?? ''); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitDynamicInvocation(DynamicInvocation node) { | 
|  | // We have no support for generic method invocation at the moment. | 
|  | if (node.arguments.types.isNotEmpty) { | 
|  | return createInvalidExpressionConstant(node, "generic method invocation"); | 
|  | } | 
|  |  | 
|  | // We have no support for method invocation with named arguments at the | 
|  | // moment. | 
|  | if (node.arguments.named.isNotEmpty) { | 
|  | return createInvalidExpressionConstant( | 
|  | node, "method invocation with named arguments"); | 
|  | } | 
|  |  | 
|  | final Constant receiver = _evaluateSubexpression(node.receiver); | 
|  | if (receiver is AbortConstant) return receiver; | 
|  | final List<Constant>? positionalArguments = | 
|  | _evaluatePositionalArguments(node.arguments); | 
|  |  | 
|  | if (positionalArguments == null) { | 
|  | AbortConstant error = _gotError!; | 
|  | _gotError = null; | 
|  | return error; | 
|  | } | 
|  | assert(_gotError == null); | 
|  | // ignore: unnecessary_null_comparison | 
|  | assert(positionalArguments != null); | 
|  |  | 
|  | if (shouldBeUnevaluated) { | 
|  | return unevaluated( | 
|  | node, | 
|  | new DynamicInvocation( | 
|  | node.kind, | 
|  | extract(receiver), | 
|  | node.name, | 
|  | unevaluatedArguments( | 
|  | positionalArguments, {}, node.arguments.types)) | 
|  | ..fileOffset = node.fileOffset); | 
|  | } | 
|  |  | 
|  | return _handleInvocation(node, node.name, receiver, positionalArguments, | 
|  | arguments: node.arguments); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitInstanceInvocation(InstanceInvocation node) { | 
|  | // We have no support for generic method invocation at the moment. | 
|  | if (node.arguments.types.isNotEmpty) { | 
|  | return createInvalidExpressionConstant(node, "generic method invocation"); | 
|  | } | 
|  |  | 
|  | // We have no support for method invocation with named arguments at the | 
|  | // moment. | 
|  | if (node.arguments.named.isNotEmpty) { | 
|  | return createInvalidExpressionConstant( | 
|  | node, "method invocation with named arguments"); | 
|  | } | 
|  |  | 
|  | final Constant receiver = _evaluateSubexpression(node.receiver); | 
|  | if (receiver is AbortConstant) return receiver; | 
|  | final List<Constant>? positionalArguments = | 
|  | _evaluatePositionalArguments(node.arguments); | 
|  |  | 
|  | if (positionalArguments == null) { | 
|  | AbortConstant error = _gotError!; | 
|  | _gotError = null; | 
|  | return error; | 
|  | } | 
|  | assert(_gotError == null); | 
|  | // ignore: unnecessary_null_comparison | 
|  | assert(positionalArguments != null); | 
|  |  | 
|  | if (shouldBeUnevaluated) { | 
|  | return unevaluated( | 
|  | node, | 
|  | new InstanceInvocation( | 
|  | node.kind, | 
|  | extract(receiver), | 
|  | node.name, | 
|  | unevaluatedArguments( | 
|  | positionalArguments, {}, node.arguments.types), | 
|  | functionType: node.functionType, | 
|  | interfaceTarget: node.interfaceTarget) | 
|  | ..fileOffset = node.fileOffset | 
|  | ..flags = node.flags); | 
|  | } | 
|  |  | 
|  | return _handleInvocation(node, node.name, receiver, positionalArguments, | 
|  | arguments: node.arguments); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitFunctionInvocation(FunctionInvocation node) { | 
|  | if (!enableConstFunctions) { | 
|  | return createInvalidExpressionConstant(node, "function invocation"); | 
|  | } | 
|  |  | 
|  | final Constant receiver = _evaluateSubexpression(node.receiver); | 
|  | if (receiver is AbortConstant) return receiver; | 
|  |  | 
|  | return _evaluateFunctionInvocation(node, receiver, node.arguments); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitLocalFunctionInvocation(LocalFunctionInvocation node) { | 
|  | if (!enableConstFunctions) { | 
|  | return createInvalidExpressionConstant(node, "local function invocation"); | 
|  | } | 
|  |  | 
|  | final Constant receiver = env.lookupVariable(node.variable)!; | 
|  | // ignore: unnecessary_null_comparison | 
|  | assert(receiver != null); | 
|  | if (receiver is AbortConstant) return receiver; | 
|  |  | 
|  | return _evaluateFunctionInvocation(node, receiver, node.arguments); | 
|  | } | 
|  |  | 
|  | Constant _evaluateFunctionInvocation( | 
|  | TreeNode node, Constant receiver, Arguments argumentsNode) { | 
|  | final List<Constant>? arguments = | 
|  | _evaluatePositionalArguments(argumentsNode); | 
|  |  | 
|  | if (arguments == null) { | 
|  | AbortConstant error = _gotError!; | 
|  | _gotError = null; | 
|  | return error; | 
|  | } | 
|  | assert(_gotError == null); | 
|  | // ignore: unnecessary_null_comparison | 
|  | assert(arguments != null); | 
|  |  | 
|  | // Evaluate type arguments of the function invoked. | 
|  | List<DartType>? types = _evaluateTypeArguments(node, argumentsNode); | 
|  | if (types == null) { | 
|  | AbortConstant error = _gotError!; | 
|  | _gotError = null; | 
|  | return error; | 
|  | } | 
|  | assert(_gotError == null); | 
|  | // ignore: unnecessary_null_comparison | 
|  | assert(types != null); | 
|  |  | 
|  | // Evaluate named arguments of the function invoked. | 
|  | final Map<String, Constant>? named = _evaluateNamedArguments(argumentsNode); | 
|  | if (named == null) { | 
|  | AbortConstant error = _gotError!; | 
|  | _gotError = null; | 
|  | return error; | 
|  | } | 
|  | assert(_gotError == null); | 
|  | // ignore: unnecessary_null_comparison | 
|  | assert(named != null); | 
|  |  | 
|  | if (receiver is FunctionValue) { | 
|  | return _handleFunctionInvocation( | 
|  | receiver.function, types, arguments, named, | 
|  | functionEnvironment: receiver.environment); | 
|  | } else { | 
|  | return createInvalidExpressionConstant( | 
|  | node, "function invocation with invalid receiver"); | 
|  | } | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitEqualsCall(EqualsCall node) { | 
|  | final Constant left = _evaluateSubexpression(node.left); | 
|  | if (left is AbortConstant) return left; | 
|  | final Constant right = _evaluateSubexpression(node.right); | 
|  | if (right is AbortConstant) return right; | 
|  |  | 
|  | if (shouldBeUnevaluated) { | 
|  | return unevaluated( | 
|  | node, | 
|  | new EqualsCall(extract(left), extract(right), | 
|  | functionType: node.functionType, | 
|  | interfaceTarget: node.interfaceTarget) | 
|  | ..fileOffset = node.fileOffset); | 
|  | } | 
|  |  | 
|  | return _handleEquals(node, left, right); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitEqualsNull(EqualsNull node) { | 
|  | final Constant expression = _evaluateSubexpression(node.expression); | 
|  | if (expression is AbortConstant) return expression; | 
|  |  | 
|  | if (shouldBeUnevaluated) { | 
|  | return unevaluated(node, | 
|  | new EqualsNull(extract(expression))..fileOffset = node.fileOffset); | 
|  | } | 
|  |  | 
|  | return _handleEquals(node, expression, nullConstant); | 
|  | } | 
|  |  | 
|  | Constant _handleEquals(Expression node, Constant left, Constant right) { | 
|  | if (left is NullConstant || | 
|  | left is BoolConstant || | 
|  | left is IntConstant || | 
|  | left is DoubleConstant || | 
|  | left is StringConstant || | 
|  | right is NullConstant) { | 
|  | // [DoubleConstant] uses [identical] to determine equality, so we need | 
|  | // to take the special cases into account. | 
|  | return doubleSpecialCases(left, right) ?? makeBoolConstant(left == right); | 
|  | } else { | 
|  | return createErrorConstant( | 
|  | node, | 
|  | templateConstEvalInvalidEqualsOperandType.withArguments( | 
|  | left, left.getType(_staticTypeContext!), isNonNullableByDefault)); | 
|  | } | 
|  | } | 
|  |  | 
|  | Constant _handleInvocation(Expression node, Name name, Constant receiver, | 
|  | List<Constant> positionalArguments, | 
|  | {required Arguments arguments}) { | 
|  | final String op = name.text; | 
|  |  | 
|  | // TODO(kallentu): Handle all constant toString methods. | 
|  | if (receiver is PrimitiveConstant && | 
|  | op == 'toString' && | 
|  | enableConstFunctions) { | 
|  | return new StringConstant(receiver.value.toString()); | 
|  | } | 
|  |  | 
|  | // Handle == and != first (it's common between all types). Since `a != b` is | 
|  | // parsed as `!(a == b)` it is handled implicitly through ==. | 
|  | if (positionalArguments.length == 1 && op == '==') { | 
|  | final Constant right = positionalArguments[0]; | 
|  | return _handleEquals(node, receiver, right); | 
|  | } | 
|  |  | 
|  | // This is a white-listed set of methods we need to support on constants. | 
|  | if (receiver is StringConstant) { | 
|  | if (positionalArguments.length == 1) { | 
|  | final Constant other = positionalArguments[0]; | 
|  | switch (op) { | 
|  | case '+': | 
|  | if (other is StringConstant) { | 
|  | return canonicalize( | 
|  | new StringConstant(receiver.value + other.value)); | 
|  | } | 
|  | return createErrorConstant( | 
|  | node, | 
|  | templateConstEvalInvalidBinaryOperandType.withArguments( | 
|  | '+', | 
|  | receiver, | 
|  | typeEnvironment.coreTypes.stringLegacyRawType, | 
|  | other.getType(_staticTypeContext!), | 
|  | isNonNullableByDefault)); | 
|  | case '[]': | 
|  | if (enableConstFunctions) { | 
|  | int? index = intFolder.asInt(other); | 
|  | if (index != null) { | 
|  | if (index < 0 || index >= receiver.value.length) { | 
|  | return new _AbortDueToThrowConstant( | 
|  | node, new RangeError.index(index, receiver.value)); | 
|  | } | 
|  | return canonicalize(new StringConstant(receiver.value[index])); | 
|  | } | 
|  | return createErrorConstant( | 
|  | node, | 
|  | templateConstEvalInvalidBinaryOperandType.withArguments( | 
|  | '[]', | 
|  | receiver, | 
|  | typeEnvironment.coreTypes.intNonNullableRawType, | 
|  | other.getType(_staticTypeContext!), | 
|  | isNonNullableByDefault)); | 
|  | } | 
|  | } | 
|  | } | 
|  | } else if (intFolder.isInt(receiver)) { | 
|  | if (positionalArguments.length == 0) { | 
|  | return canonicalize(intFolder.foldUnaryOperator(node, op, receiver)); | 
|  | } else if (positionalArguments.length == 1) { | 
|  | final Constant other = positionalArguments[0]; | 
|  | if (intFolder.isInt(other)) { | 
|  | return canonicalize( | 
|  | intFolder.foldBinaryOperator(node, op, receiver, other)); | 
|  | } else if (other is DoubleConstant) { | 
|  | if ((op == '|' || op == '&' || op == '^') || | 
|  | (op == '<<' || op == '>>' || op == '>>>')) { | 
|  | return createErrorConstant( | 
|  | node, | 
|  | templateConstEvalInvalidBinaryOperandType.withArguments( | 
|  | op, | 
|  | other, | 
|  | typeEnvironment.coreTypes.intLegacyRawType, | 
|  | other.getType(_staticTypeContext!), | 
|  | isNonNullableByDefault)); | 
|  | } | 
|  | num receiverValue = (receiver as PrimitiveConstant<num>).value; | 
|  | return canonicalize(evaluateBinaryNumericOperation( | 
|  | op, receiverValue, other.value, node)); | 
|  | } | 
|  | return createErrorConstant( | 
|  | node, | 
|  | templateConstEvalInvalidBinaryOperandType.withArguments( | 
|  | op, | 
|  | receiver, | 
|  | typeEnvironment.coreTypes.numLegacyRawType, | 
|  | other.getType(_staticTypeContext!), | 
|  | isNonNullableByDefault)); | 
|  | } | 
|  | } else if (receiver is DoubleConstant) { | 
|  | if ((op == '|' || op == '&' || op == '^') || | 
|  | (op == '<<' || op == '>>' || op == '>>>')) { | 
|  | return createErrorConstant( | 
|  | node, | 
|  | templateConstEvalInvalidBinaryOperandType.withArguments( | 
|  | op, | 
|  | receiver, | 
|  | typeEnvironment.coreTypes.intLegacyRawType, | 
|  | receiver.getType(_staticTypeContext!), | 
|  | isNonNullableByDefault)); | 
|  | } | 
|  | if (positionalArguments.length == 0) { | 
|  | switch (op) { | 
|  | case 'unary-': | 
|  | return canonicalize(new DoubleConstant(-receiver.value)); | 
|  | } | 
|  | } else if (positionalArguments.length == 1) { | 
|  | final Constant other = positionalArguments[0]; | 
|  |  | 
|  | if (other is IntConstant || other is DoubleConstant) { | 
|  | final num value = (other as PrimitiveConstant<num>).value; | 
|  | return canonicalize( | 
|  | evaluateBinaryNumericOperation(op, receiver.value, value, node)); | 
|  | } | 
|  | return createErrorConstant( | 
|  | node, | 
|  | templateConstEvalInvalidBinaryOperandType.withArguments( | 
|  | op, | 
|  | receiver, | 
|  | typeEnvironment.coreTypes.numLegacyRawType, | 
|  | other.getType(_staticTypeContext!), | 
|  | isNonNullableByDefault)); | 
|  | } | 
|  | } else if (receiver is BoolConstant) { | 
|  | if (positionalArguments.length == 1) { | 
|  | final Constant other = positionalArguments[0]; | 
|  | if (other is BoolConstant) { | 
|  | switch (op) { | 
|  | case '|': | 
|  | return canonicalize( | 
|  | new BoolConstant(receiver.value || other.value)); | 
|  | case '&': | 
|  | return canonicalize( | 
|  | new BoolConstant(receiver.value && other.value)); | 
|  | case '^': | 
|  | return canonicalize( | 
|  | new BoolConstant(receiver.value != other.value)); | 
|  | } | 
|  | } | 
|  | } | 
|  | } else if (receiver is NullConstant) { | 
|  | return createErrorConstant(node, messageConstEvalNullValue); | 
|  | } else if (receiver is ListConstant && enableConstFunctions) { | 
|  | if (positionalArguments.length == 1) { | 
|  | final Constant other = positionalArguments[0]; | 
|  | switch (op) { | 
|  | case '[]': | 
|  | int? index = intFolder.asInt(other); | 
|  | if (index != null) { | 
|  | if (index < 0 || index >= receiver.entries.length) { | 
|  | return new _AbortDueToThrowConstant( | 
|  | node, new RangeError.index(index, receiver.entries)); | 
|  | } | 
|  | return receiver.entries[index]; | 
|  | } | 
|  | return createErrorConstant( | 
|  | node, | 
|  | templateConstEvalInvalidBinaryOperandType.withArguments( | 
|  | '[]', | 
|  | receiver, | 
|  | typeEnvironment.coreTypes.intNonNullableRawType, | 
|  | other.getType(_staticTypeContext!), | 
|  | isNonNullableByDefault)); | 
|  | case 'add': | 
|  | if (receiver is MutableListConstant) { | 
|  | receiver.entries.add(other); | 
|  | return receiver; | 
|  | } | 
|  | return new _AbortDueToThrowConstant(node, new UnsupportedError(op)); | 
|  | } | 
|  | } | 
|  | } else if (receiver is MapConstant && enableConstFunctions) { | 
|  | if (positionalArguments.length == 1) { | 
|  | final Constant other = positionalArguments[0]; | 
|  | switch (op) { | 
|  | case '[]': | 
|  | for (ConstantMapEntry entry in receiver.entries) { | 
|  | if (entry.key == other) { | 
|  | return entry.value; | 
|  | } | 
|  | } | 
|  | return new NullConstant(); | 
|  | } | 
|  | } | 
|  | } else if (enableConstFunctions) { | 
|  | // Evaluate type arguments of the method invoked. | 
|  | List<DartType>? typeArguments = _evaluateTypeArguments(node, arguments); | 
|  | if (typeArguments == null) { | 
|  | AbortConstant error = _gotError!; | 
|  | _gotError = null; | 
|  | return error; | 
|  | } | 
|  | assert(_gotError == null); | 
|  | // ignore: unnecessary_null_comparison | 
|  | assert(typeArguments != null); | 
|  |  | 
|  | // Evaluate named arguments of the method invoked. | 
|  | final Map<String, Constant>? namedArguments = | 
|  | _evaluateNamedArguments(arguments); | 
|  | if (namedArguments == null) { | 
|  | AbortConstant error = _gotError!; | 
|  | _gotError = null; | 
|  | return error; | 
|  | } | 
|  | assert(_gotError == null); | 
|  | // ignore: unnecessary_null_comparison | 
|  | assert(namedArguments != null); | 
|  |  | 
|  | if (receiver is FunctionValue && name == Name.callName) { | 
|  | return _handleFunctionInvocation(receiver.function, typeArguments, | 
|  | positionalArguments, namedArguments, | 
|  | functionEnvironment: receiver.environment); | 
|  | } else if (receiver is InstanceConstant) { | 
|  | final Class instanceClass = receiver.classNode; | 
|  | assert(typeEnvironment.hierarchy is ClassHierarchy); | 
|  | final Member member = (typeEnvironment.hierarchy as ClassHierarchy) | 
|  | .getDispatchTarget(instanceClass, name)!; | 
|  | final FunctionNode? function = member.function; | 
|  |  | 
|  | // TODO(kallentu): Implement [Object] class methods which have backend | 
|  | // specific functions that cannot be run by the constant evaluator. | 
|  | final bool isObjectMember = member.enclosingClass != null && | 
|  | member.enclosingClass!.name == "Object"; | 
|  | if (function != null && !isObjectMember) { | 
|  | // TODO(johnniwinther): Make [typeArguments] and [namedArguments] | 
|  | // required and non-nullable. | 
|  | return withNewInstanceBuilder(instanceClass, typeArguments, () { | 
|  | final EvaluationEnvironment newEnv = new EvaluationEnvironment(); | 
|  | for (int i = 0; i < instanceClass.typeParameters.length; i++) { | 
|  | newEnv.addTypeParameterValue( | 
|  | instanceClass.typeParameters[i], receiver.typeArguments[i]); | 
|  | } | 
|  |  | 
|  | // Ensure that fields are visible for instance access. | 
|  | receiver.fieldValues.forEach((Reference fieldRef, Constant value) => | 
|  | instanceBuilder!.setFieldValue(fieldRef.asField, value)); | 
|  | return _handleFunctionInvocation(function, receiver.typeArguments, | 
|  | positionalArguments, namedArguments, | 
|  | functionEnvironment: newEnv); | 
|  | }); | 
|  | } | 
|  |  | 
|  | switch (op) { | 
|  | case 'toString': | 
|  | // Default value for toString() of instances. | 
|  | return new StringConstant( | 
|  | "Instance of '${receiver.classReference.toStringInternal()}'"); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | return createErrorConstant( | 
|  | node, | 
|  | templateConstEvalInvalidMethodInvocation.withArguments( | 
|  | op, receiver, isNonNullableByDefault)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitLogicalExpression(LogicalExpression node) { | 
|  | final Constant left = _evaluateSubexpression(node.left); | 
|  | if (left is AbortConstant) return left; | 
|  | if (shouldBeUnevaluated) { | 
|  | enterLazy(); | 
|  | Constant right = _evaluateSubexpression(node.right); | 
|  | if (right is AbortConstant) return right; | 
|  | leaveLazy(); | 
|  | return unevaluated( | 
|  | node, | 
|  | new LogicalExpression( | 
|  | extract(left), node.operatorEnum, extract(right))); | 
|  | } | 
|  | switch (node.operatorEnum) { | 
|  | case LogicalExpressionOperator.OR: | 
|  | if (left is BoolConstant) { | 
|  | if (left.value) return trueConstant; | 
|  |  | 
|  | final Constant right = _evaluateSubexpression(node.right); | 
|  | if (right is AbortConstant) return right; | 
|  | if (right is BoolConstant || right is UnevaluatedConstant) { | 
|  | return right; | 
|  | } | 
|  |  | 
|  | return createErrorConstant( | 
|  | node, | 
|  | templateConstEvalInvalidBinaryOperandType.withArguments( | 
|  | logicalExpressionOperatorToString(node.operatorEnum), | 
|  | left, | 
|  | typeEnvironment.coreTypes.boolLegacyRawType, | 
|  | right.getType(_staticTypeContext!), | 
|  | isNonNullableByDefault)); | 
|  | } | 
|  | return createErrorConstant( | 
|  | node, | 
|  | templateConstEvalInvalidMethodInvocation.withArguments( | 
|  | logicalExpressionOperatorToString(node.operatorEnum), | 
|  | left, | 
|  | isNonNullableByDefault)); | 
|  | case LogicalExpressionOperator.AND: | 
|  | if (left is BoolConstant) { | 
|  | if (!left.value) return falseConstant; | 
|  |  | 
|  | final Constant right = _evaluateSubexpression(node.right); | 
|  | if (right is AbortConstant) return right; | 
|  | if (right is BoolConstant || right is UnevaluatedConstant) { | 
|  | return right; | 
|  | } | 
|  |  | 
|  | return createErrorConstant( | 
|  | node, | 
|  | templateConstEvalInvalidBinaryOperandType.withArguments( | 
|  | logicalExpressionOperatorToString(node.operatorEnum), | 
|  | left, | 
|  | typeEnvironment.coreTypes.boolLegacyRawType, | 
|  | right.getType(_staticTypeContext!), | 
|  | isNonNullableByDefault)); | 
|  | } | 
|  | return createErrorConstant( | 
|  | node, | 
|  | templateConstEvalInvalidMethodInvocation.withArguments( | 
|  | logicalExpressionOperatorToString(node.operatorEnum), | 
|  | left, | 
|  | isNonNullableByDefault)); | 
|  | default: | 
|  | // Probably unreachable. | 
|  | return createErrorConstant( | 
|  | node, | 
|  | templateConstEvalInvalidMethodInvocation.withArguments( | 
|  | logicalExpressionOperatorToString(node.operatorEnum), | 
|  | left, | 
|  | isNonNullableByDefault)); | 
|  | } | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitConditionalExpression(ConditionalExpression node) { | 
|  | final Constant condition = _evaluateSubexpression(node.condition); | 
|  | if (condition is AbortConstant) return condition; | 
|  | if (condition == trueConstant) { | 
|  | return _evaluateSubexpression(node.then); | 
|  | } else if (condition == falseConstant) { | 
|  | return _evaluateSubexpression(node.otherwise); | 
|  | } else if (shouldBeUnevaluated) { | 
|  | enterLazy(); | 
|  | Constant then = _evaluateSubexpression(node.then); | 
|  | if (then is AbortConstant) return then; | 
|  | Constant otherwise = _evaluateSubexpression(node.otherwise); | 
|  | if (otherwise is AbortConstant) return otherwise; | 
|  | leaveLazy(); | 
|  | return unevaluated( | 
|  | node, | 
|  | new ConditionalExpression(extract(condition), extract(then), | 
|  | extract(otherwise), node.staticType)); | 
|  | } else { | 
|  | return createErrorConstant( | 
|  | node.condition, | 
|  | templateConstEvalInvalidType.withArguments( | 
|  | condition, | 
|  | typeEnvironment.coreTypes.boolLegacyRawType, | 
|  | condition.getType(_staticTypeContext!), | 
|  | isNonNullableByDefault)); | 
|  | } | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitInstanceGet(InstanceGet node) { | 
|  | if (node.receiver is ThisExpression) { | 
|  | // Probably unreachable unless trying to evaluate non-const stuff as | 
|  | // const. | 
|  | // Access "this" during instance creation. | 
|  | if (instanceBuilder == null) { | 
|  | return createErrorConstant(node, messageNotAConstantExpression); | 
|  | } | 
|  |  | 
|  | for (final MapEntry<Field, Constant> entry | 
|  | in instanceBuilder!.fields.entries) { | 
|  | final Field field = entry.key; | 
|  | if (field.name == node.name) { | 
|  | return entry.value; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Meant as a "stable backstop for situations where Fasta fails to | 
|  | // rewrite various erroneous constructs into invalid expressions". | 
|  | // Probably unreachable. | 
|  | return createInvalidExpressionConstant(node, | 
|  | 'Could not evaluate field get ${node.name} on incomplete instance'); | 
|  | } | 
|  |  | 
|  | final Constant receiver = _evaluateSubexpression(node.receiver); | 
|  | if (receiver is AbortConstant) return receiver; | 
|  | if (receiver is StringConstant && node.name.text == 'length') { | 
|  | return canonicalize(intFolder.makeIntConstant(receiver.value.length)); | 
|  | } else if (shouldBeUnevaluated) { | 
|  | return unevaluated( | 
|  | node, | 
|  | new InstanceGet(node.kind, extract(receiver), node.name, | 
|  | resultType: node.resultType, | 
|  | interfaceTarget: node.interfaceTarget)); | 
|  | } else if (receiver is NullConstant) { | 
|  | return createErrorConstant(node, messageConstEvalNullValue); | 
|  | } else if (receiver is ListConstant && enableConstFunctions) { | 
|  | switch (node.name.text) { | 
|  | case 'first': | 
|  | if (receiver.entries.isEmpty) { | 
|  | return new _AbortDueToThrowConstant( | 
|  | node, new StateError('No element')); | 
|  | } | 
|  | return receiver.entries.first; | 
|  | case 'isEmpty': | 
|  | return new BoolConstant(receiver.entries.isEmpty); | 
|  | case 'isNotEmpty': | 
|  | return new BoolConstant(receiver.entries.isNotEmpty); | 
|  | // TODO(kallentu): case 'iterator' | 
|  | case 'last': | 
|  | if (receiver.entries.isEmpty) { | 
|  | return new _AbortDueToThrowConstant( | 
|  | node, new StateError('No element')); | 
|  | } | 
|  | return receiver.entries.last; | 
|  | case 'length': | 
|  | return new IntConstant(receiver.entries.length); | 
|  | // TODO(kallentu): case 'reversed' | 
|  | case 'single': | 
|  | if (receiver.entries.isEmpty) { | 
|  | return new _AbortDueToThrowConstant( | 
|  | node, new StateError('No element')); | 
|  | } else if (receiver.entries.length > 1) { | 
|  | return new _AbortDueToThrowConstant( | 
|  | node, new StateError('Too many elements')); | 
|  | } | 
|  | return receiver.entries.single; | 
|  | } | 
|  | } else if (receiver is InstanceConstant && enableConstFunctions) { | 
|  | for (final MapEntry<Reference, Constant> entry | 
|  | in receiver.fieldValues.entries) { | 
|  | final Field field = entry.key.asField; | 
|  | if (field.name == node.name) { | 
|  | return entry.value; | 
|  | } | 
|  | } | 
|  | } | 
|  | return createErrorConstant( | 
|  | node, | 
|  | templateConstEvalInvalidPropertyGet.withArguments( | 
|  | node.name.text, receiver, isNonNullableByDefault)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitDynamicGet(DynamicGet node) { | 
|  | final Constant receiver = _evaluateSubexpression(node.receiver); | 
|  | if (receiver is AbortConstant) return receiver; | 
|  | if (receiver is StringConstant && node.name.text == 'length') { | 
|  | return canonicalize(intFolder.makeIntConstant(receiver.value.length)); | 
|  | } else if (shouldBeUnevaluated) { | 
|  | return unevaluated( | 
|  | node, new DynamicGet(node.kind, extract(receiver), node.name)); | 
|  | } else if (receiver is NullConstant) { | 
|  | return createErrorConstant(node, messageConstEvalNullValue); | 
|  | } | 
|  | return createErrorConstant( | 
|  | node, | 
|  | templateConstEvalInvalidPropertyGet.withArguments( | 
|  | node.name.text, receiver, isNonNullableByDefault)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitInstanceTearOff(InstanceTearOff node) { | 
|  | final Constant receiver = _evaluateSubexpression(node.receiver); | 
|  | if (receiver is AbortConstant) return receiver; | 
|  | return createErrorConstant( | 
|  | node, | 
|  | templateConstEvalInvalidPropertyGet.withArguments( | 
|  | node.name.text, receiver, isNonNullableByDefault)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitFunctionTearOff(FunctionTearOff node) { | 
|  | final Constant receiver = _evaluateSubexpression(node.receiver); | 
|  | if (receiver is AbortConstant) return receiver; | 
|  | return createErrorConstant( | 
|  | node, | 
|  | templateConstEvalInvalidPropertyGet.withArguments( | 
|  | Name.callName.text, receiver, isNonNullableByDefault)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitLet(Let node) { | 
|  | Constant value = _evaluateSubexpression(node.variable.initializer!); | 
|  | if (value is AbortConstant) return value; | 
|  | env.addVariableValue(node.variable, value); | 
|  | return _evaluateSubexpression(node.body); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitVariableGet(VariableGet node) { | 
|  | // Not every variable which a [VariableGet] refers to must be marked as | 
|  | // constant.  For example function parameters as well as constructs | 
|  | // desugared to [Let] expressions are ok. | 
|  | // | 
|  | // TODO(kustermann): The heuristic of allowing all [VariableGet]s on [Let] | 
|  | // variables might allow more than it should. | 
|  | final VariableDeclaration variable = node.variable; | 
|  | if (enableConstFunctions) { | 
|  | return env.lookupVariable(variable) ?? | 
|  | createErrorConstant( | 
|  | node, | 
|  | templateConstEvalGetterNotFound | 
|  | .withArguments(variable.name ?? '')); | 
|  | } else { | 
|  | if (variable.parent is Let || _isFormalParameter(variable)) { | 
|  | return env.lookupVariable(node.variable) ?? | 
|  | createErrorConstant( | 
|  | node, | 
|  | templateConstEvalNonConstantVariableGet | 
|  | .withArguments(variable.name ?? '')); | 
|  | } | 
|  | if (variable.isConst) { | 
|  | return _evaluateSubexpression(variable.initializer!); | 
|  | } | 
|  | } | 
|  | return createInvalidExpressionConstant( | 
|  | node, 'Variable get of a non-const variable.'); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitVariableSet(VariableSet node) { | 
|  | if (enableConstFunctions) { | 
|  | final VariableDeclaration variable = node.variable; | 
|  | Constant value = _evaluateSubexpression(node.value); | 
|  | if (value is AbortConstant) return value; | 
|  | return env.updateVariableValue(variable, value) ?? | 
|  | createInvalidExpressionConstant( | 
|  | node, 'Variable set of an unknown value.'); | 
|  | } | 
|  | return defaultExpression(node); | 
|  | } | 
|  |  | 
|  | /// Computes the constant for [expression] defined in the context of [member]. | 
|  | /// | 
|  | /// This compute the constant as seen in the current evaluation mode even when | 
|  | /// the constant is defined in a library compiled with the agnostic evaluation | 
|  | /// mode. | 
|  | Constant _evaluateExpressionInContext(Member member, Expression expression) { | 
|  | StaticTypeContext? oldStaticTypeContext = _staticTypeContext; | 
|  | _staticTypeContext = new StaticTypeContext(member, typeEnvironment); | 
|  | Constant constant = _evaluateSubexpression(expression); | 
|  | if (constant is! AbortConstant) { | 
|  | if (_staticTypeContext!.nonNullableByDefaultCompiledMode == | 
|  | NonNullableByDefaultCompiledMode.Agnostic && | 
|  | evaluationMode == EvaluationMode.weak) { | 
|  | constant = _weakener.visitConstant(constant) ?? constant; | 
|  | } | 
|  | } | 
|  | _staticTypeContext = oldStaticTypeContext; | 
|  | return constant; | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitStaticGet(StaticGet node) { | 
|  | return withNewEnvironment(() { | 
|  | final Member target = node.target; | 
|  | if (target is Field) { | 
|  | if (target.isConst) { | 
|  | return _evaluateExpressionInContext(target, target.initializer!); | 
|  | } | 
|  | return createErrorConstant( | 
|  | node, | 
|  | templateConstEvalInvalidStaticInvocation | 
|  | .withArguments(target.name.text)); | 
|  | } else if (target is Procedure) { | 
|  | if (target.kind == ProcedureKind.Method) { | 
|  | return canonicalize(new StaticTearOffConstant(target)); | 
|  | } | 
|  | return createErrorConstant( | 
|  | node, | 
|  | templateConstEvalInvalidStaticInvocation | 
|  | .withArguments(target.name.text)); | 
|  | } else { | 
|  | return createInvalidExpressionConstant( | 
|  | node, 'No support for ${target.runtimeType} in a static-get.'); | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitStaticTearOff(StaticTearOff node) { | 
|  | return 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 createErrorConstant( | 
|  | node, | 
|  | templateConstEvalInvalidStringInterpolationOperand.withArguments( | 
|  | constant, isNonNullableByDefault)); | 
|  | } | 
|  | } | 
|  | if (concatenated.length > 1) { | 
|  | final List<Expression> expressions = | 
|  | new List<Expression>.generate(concatenated.length, (int i) { | 
|  | Object value = concatenated[i]; | 
|  | if (value is StringBuffer) { | 
|  | return new ConstantExpression( | 
|  | canonicalize(new StringConstant(value.toString()))); | 
|  | } else { | 
|  | // The value is either unevaluated constant or a non-primitive | 
|  | // constant in an unevaluated expression. | 
|  | return extract(value as Constant); | 
|  | } | 
|  | }, growable: false); | 
|  | return unevaluated(node, new StringConcatenation(expressions)); | 
|  | } | 
|  | return canonicalize(new StringConstant(concatenated.single.toString())); | 
|  | } | 
|  |  | 
|  | Constant _getFromEnvironmentDefaultValue(Procedure target) { | 
|  | VariableDeclaration variable = target.function.namedParameters | 
|  | .singleWhere((v) => v.name == 'defaultValue'); | 
|  | return _evaluateExpressionInContext(target, variable.initializer!); | 
|  | } | 
|  |  | 
|  | Constant _handleFromEnvironment( | 
|  | Procedure target, StringConstant name, Map<String, Constant> named) { | 
|  | String? value = environmentDefines![name.value]; | 
|  | Constant? defaultValue = named["defaultValue"]; | 
|  | if (target.enclosingClass == coreTypes.boolClass) { | 
|  | Constant boolConstant; | 
|  | if (value == "true") { | 
|  | boolConstant = trueConstant; | 
|  | } else if (value == "false") { | 
|  | boolConstant = falseConstant; | 
|  | } else if (defaultValue != null) { | 
|  | if (defaultValue is BoolConstant) { | 
|  | boolConstant = makeBoolConstant(defaultValue.value); | 
|  | } else if (defaultValue is NullConstant) { | 
|  | boolConstant = nullConstant; | 
|  | } else { | 
|  | // Probably unreachable. | 
|  | boolConstant = falseConstant; | 
|  | } | 
|  | } else { | 
|  | boolConstant = _getFromEnvironmentDefaultValue(target); | 
|  | } | 
|  | return boolConstant; | 
|  | } else if (target.enclosingClass == coreTypes.intClass) { | 
|  | int? intValue = value != null ? int.tryParse(value) : null; | 
|  | Constant intConstant; | 
|  | if (intValue != null) { | 
|  | bool negated = value!.startsWith('-'); | 
|  | intConstant = intFolder.makeIntConstant(intValue, unsigned: !negated); | 
|  | } else if (defaultValue != null) { | 
|  | if (intFolder.isInt(defaultValue)) { | 
|  | intConstant = defaultValue; | 
|  | } else { | 
|  | intConstant = nullConstant; | 
|  | } | 
|  | } else { | 
|  | intConstant = _getFromEnvironmentDefaultValue(target); | 
|  | } | 
|  | return canonicalize(intConstant); | 
|  | } else if (target.enclosingClass == coreTypes.stringClass) { | 
|  | Constant stringConstant; | 
|  | if (value != null) { | 
|  | stringConstant = canonicalize(new StringConstant(value)); | 
|  | } else if (defaultValue != null) { | 
|  | if (defaultValue is StringConstant) { | 
|  | stringConstant = defaultValue; | 
|  | } else { | 
|  | stringConstant = nullConstant; | 
|  | } | 
|  | } else { | 
|  | stringConstant = _getFromEnvironmentDefaultValue(target); | 
|  | } | 
|  | return stringConstant; | 
|  | } | 
|  | // Unreachable until fromEnvironment is added to other classes in dart:core | 
|  | // than bool, int and String. | 
|  | throw new UnsupportedError( | 
|  | 'Unexpected fromEnvironment constructor: $target'); | 
|  | } | 
|  |  | 
|  | Constant _handleHasEnvironment(StringConstant name) { | 
|  | return environmentDefines!.containsKey(name.value) | 
|  | ? trueConstant | 
|  | : falseConstant; | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitStaticInvocation(StaticInvocation node) { | 
|  | final Procedure target = node.target; | 
|  | final Arguments arguments = node.arguments; | 
|  | List<DartType>? types = _evaluateTypeArguments(node, arguments); | 
|  | if (types == null) { | 
|  | AbortConstant error = _gotError!; | 
|  | _gotError = null; | 
|  | return error; | 
|  | } | 
|  | assert(_gotError == null); | 
|  | // ignore: unnecessary_null_comparison | 
|  | assert(types != null); | 
|  |  | 
|  | final List<DartType> typeArguments = convertTypes(types); | 
|  |  | 
|  | final List<Constant>? positionals = _evaluatePositionalArguments(arguments); | 
|  | if (positionals == null) { | 
|  | AbortConstant error = _gotError!; | 
|  | _gotError = null; | 
|  | return error; | 
|  | } | 
|  | assert(_gotError == null); | 
|  | // ignore: unnecessary_null_comparison | 
|  | assert(positionals != null); | 
|  |  | 
|  | final Map<String, Constant>? named = _evaluateNamedArguments(arguments); | 
|  | if (named == null) { | 
|  | AbortConstant error = _gotError!; | 
|  | _gotError = null; | 
|  | return error; | 
|  | } | 
|  | assert(_gotError == null); | 
|  | // ignore: unnecessary_null_comparison | 
|  | assert(named != null); | 
|  |  | 
|  | if (shouldBeUnevaluated) { | 
|  | return unevaluated( | 
|  | node, | 
|  | new StaticInvocation( | 
|  | target, unevaluatedArguments(positionals, named, arguments.types), | 
|  | isConst: true)); | 
|  | } | 
|  | if (target.kind == ProcedureKind.Factory) { | 
|  | if (target.isConst) { | 
|  | if (target.enclosingLibrary == coreTypes.coreLibrary && | 
|  | positionals.length == 1 && | 
|  | (target.name.text == "fromEnvironment" || | 
|  | target.name.text == "hasEnvironment")) { | 
|  | if (environmentDefines != null) { | 
|  | // Evaluate environment constant. | 
|  | Constant name = positionals.single; | 
|  | if (name is StringConstant) { | 
|  | if (target.name.text == "fromEnvironment") { | 
|  | return _handleFromEnvironment(target, name, named); | 
|  | } else { | 
|  | return _handleHasEnvironment(name); | 
|  | } | 
|  | } else if (name is NullConstant) { | 
|  | return createErrorConstant(node, messageConstEvalNullValue); | 
|  | } | 
|  | } else { | 
|  | // Leave environment constant unevaluated. | 
|  | return unevaluated( | 
|  | node, | 
|  | new StaticInvocation(target, | 
|  | unevaluatedArguments(positionals, named, arguments.types), | 
|  | isConst: true)); | 
|  | } | 
|  | } else if (target.isExternal) { | 
|  | return createErrorConstant(node, messageConstEvalExternalFactory); | 
|  | } | 
|  | } | 
|  | } else if (target.name.text == 'identical') { | 
|  | // Ensure the "identical()" function comes from dart:core. | 
|  | final TreeNode? parent = target.parent; | 
|  | if (parent is Library && parent == coreTypes.coreLibrary) { | 
|  | final Constant left = positionals[0]; | 
|  | final Constant right = positionals[1]; | 
|  |  | 
|  | Constant evaluateIdentical() { | 
|  | // Since we canonicalize constants during the evaluation, we can use | 
|  | // identical here. | 
|  | Constant result = makeBoolConstant(identical(left, right)); | 
|  | if (evaluationMode == EvaluationMode.agnostic) { | 
|  | Constant? weakLeft = _weakener.visitConstant(left); | 
|  | Constant? weakRight = _weakener.visitConstant(right); | 
|  | if (weakLeft != null || weakRight != null) { | 
|  | Constant weakResult = makeBoolConstant( | 
|  | identical(weakLeft ?? left, weakRight ?? right)); | 
|  | if (!identical(result, weakResult)) { | 
|  | return createErrorConstant(node, messageNonAgnosticConstant); | 
|  | } | 
|  | } | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | if (targetingJavaScript) { | 
|  | // In JavaScript, we lower [identical] to `===`, so we need to take | 
|  | // the double special cases into account. | 
|  | return doubleSpecialCases(left, right) ?? evaluateIdentical(); | 
|  | } | 
|  | return evaluateIdentical(); | 
|  | } | 
|  | } else if (target.isExtensionMember) { | 
|  | return createErrorConstant(node, messageConstEvalExtension); | 
|  | } else if (enableConstFunctions && target.kind == ProcedureKind.Method) { | 
|  | return _handleFunctionInvocation( | 
|  | node.target.function, typeArguments, positionals, named); | 
|  | } | 
|  |  | 
|  | String name = target.name.text; | 
|  | if (target.isFactory) { | 
|  | if (name.isEmpty) { | 
|  | name = target.enclosingClass!.name; | 
|  | } else { | 
|  | name = '${target.enclosingClass!.name}.${name}'; | 
|  | } | 
|  |  | 
|  | if (enableConstFunctions) { | 
|  | return _handleFunctionInvocation( | 
|  | node.target.function, typeArguments, positionals, named); | 
|  | } | 
|  | } | 
|  | return createInvalidExpressionConstant(node, "Invocation of $name"); | 
|  | } | 
|  |  | 
|  | Constant _handleFunctionInvocation( | 
|  | FunctionNode function, | 
|  | List<DartType> typeArguments, | 
|  | List<Constant> positionalArguments, | 
|  | Map<String, Constant> namedArguments, | 
|  | {EvaluationEnvironment? functionEnvironment}) { | 
|  | Constant executeFunction() { | 
|  | // Map arguments from caller to callee. | 
|  | for (int i = 0; i < function.typeParameters.length; i++) { | 
|  | env.addTypeParameterValue(function.typeParameters[i], typeArguments[i]); | 
|  | } | 
|  | for (int i = 0; i < function.positionalParameters.length; i++) { | 
|  | final VariableDeclaration parameter = function.positionalParameters[i]; | 
|  | final Constant value = (i < positionalArguments.length) | 
|  | ? positionalArguments[i] | 
|  | // TODO(johnniwinther): This should call [_evaluateSubexpression]. | 
|  | : _evaluateNullableSubexpression(parameter.initializer); | 
|  | if (value is AbortConstant) return value; | 
|  | env.addVariableValue(parameter, value); | 
|  | } | 
|  | for (final VariableDeclaration parameter in function.namedParameters) { | 
|  | final Constant value = namedArguments[parameter.name] ?? | 
|  | // TODO(johnniwinther): This should call [_evaluateSubexpression]. | 
|  | _evaluateNullableSubexpression(parameter.initializer); | 
|  | if (value is AbortConstant) return value; | 
|  | env.addVariableValue(parameter, value); | 
|  | } | 
|  |  | 
|  | final Constant result = executeBody(function.body!); | 
|  | if (result is NullConstant && | 
|  | function.returnType.nullability == Nullability.nonNullable) { | 
|  | // Ensure that the evaluated constant returned is not null if the | 
|  | // function has a non-nullable return type. | 
|  | return createErrorConstant( | 
|  | function, | 
|  | templateConstEvalInvalidType.withArguments( | 
|  | result, | 
|  | function.returnType, | 
|  | result.getType(_staticTypeContext!), | 
|  | isNonNullableByDefault)); | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | if (functionEnvironment != null) { | 
|  | return withEnvironment(functionEnvironment, executeFunction); | 
|  | } | 
|  | return withNewEnvironment(executeFunction); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitAsExpression(AsExpression node) { | 
|  | final Constant constant = _evaluateSubexpression(node.operand); | 
|  | if (constant is AbortConstant) return constant; | 
|  | if (shouldBeUnevaluated) { | 
|  | return unevaluated( | 
|  | node, | 
|  | new AsExpression(extract(constant), env.substituteType(node.type)) | 
|  | ..isForNonNullableByDefault = | 
|  | _staticTypeContext!.isNonNullableByDefault); | 
|  | } | 
|  | DartType? type = _evaluateDartType(node, node.type); | 
|  | if (type == null) { | 
|  | AbortConstant error = _gotError!; | 
|  | _gotError = null; | 
|  | return error; | 
|  | } | 
|  | assert(_gotError == null); | 
|  | // ignore: unnecessary_null_comparison | 
|  | assert(type != null); | 
|  | return ensureIsSubtype(constant, type, node); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitIsExpression(IsExpression node) { | 
|  | final Constant constant = _evaluateSubexpression(node.operand); | 
|  | if (constant is AbortConstant) return constant; | 
|  | if (shouldBeUnevaluated) { | 
|  | return unevaluated( | 
|  | node, | 
|  | new IsExpression(extract(constant), node.type) | 
|  | ..fileOffset = node.fileOffset | 
|  | ..flags = node.flags); | 
|  | } | 
|  |  | 
|  | DartType? type = _evaluateDartType(node, node.type); | 
|  | if (type == null) { | 
|  | AbortConstant error = _gotError!; | 
|  | _gotError = null; | 
|  | return error; | 
|  | } | 
|  | assert(_gotError == null); | 
|  | // ignore: unnecessary_null_comparison | 
|  | assert(type != null); | 
|  |  | 
|  | bool performIs(Constant constant, {required bool strongMode}) { | 
|  | // ignore: unnecessary_null_comparison | 
|  | assert(strongMode != null); | 
|  | if (strongMode) { | 
|  | return isSubtype(constant, type, SubtypeCheckMode.withNullabilities); | 
|  | } else { | 
|  | // In weak checking mode: if e evaluates to a value v and v has runtime | 
|  | // type S, an instance check e is T occurring in a legacy library or an | 
|  | // opted-in library is evaluated as follows: | 
|  | // | 
|  | //    If v is null and T is a legacy type, | 
|  | //       return LEGACY_SUBTYPE(T, NULL) || LEGACY_SUBTYPE(Object, T) | 
|  | //    If v is null and T is not a legacy type, | 
|  | //       return NNBD_SUBTYPE(NULL, T) | 
|  | //    Otherwise return LEGACY_SUBTYPE(S, T) | 
|  | if (constant is NullConstant) { | 
|  | if (type.nullability == Nullability.legacy) { | 
|  | // `null is Null` is handled below. | 
|  | return typeEnvironment.isSubtypeOf(type, const NullType(), | 
|  | SubtypeCheckMode.ignoringNullabilities) || | 
|  | typeEnvironment.isSubtypeOf(typeEnvironment.objectLegacyRawType, | 
|  | type, SubtypeCheckMode.ignoringNullabilities); | 
|  | } else { | 
|  | return typeEnvironment.isSubtypeOf( | 
|  | const NullType(), type, SubtypeCheckMode.withNullabilities); | 
|  | } | 
|  | } | 
|  | return isSubtype( | 
|  | constant, type, SubtypeCheckMode.ignoringNullabilities); | 
|  | } | 
|  | } | 
|  |  | 
|  | switch (evaluationMode) { | 
|  | case EvaluationMode.strong: | 
|  | return makeBoolConstant(performIs(constant, strongMode: true)); | 
|  | case EvaluationMode.agnostic: | 
|  | bool strongResult = performIs(constant, strongMode: true); | 
|  | Constant weakConstant = _weakener.visitConstant(constant) ?? constant; | 
|  | bool weakResult = performIs(weakConstant, strongMode: false); | 
|  | if (strongResult != weakResult) { | 
|  | return createErrorConstant(node, messageNonAgnosticConstant); | 
|  | } | 
|  | return makeBoolConstant(strongResult); | 
|  | case EvaluationMode.weak: | 
|  | return makeBoolConstant(performIs(constant, strongMode: false)); | 
|  | } | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitNot(Not node) { | 
|  | final Constant constant = _evaluateSubexpression(node.operand); | 
|  | if (constant is AbortConstant) return constant; | 
|  | if (constant is BoolConstant) { | 
|  | return makeBoolConstant(constant != trueConstant); | 
|  | } | 
|  | if (shouldBeUnevaluated) { | 
|  | return unevaluated(node, new Not(extract(constant))); | 
|  | } | 
|  | return createErrorConstant( | 
|  | node, | 
|  | templateConstEvalInvalidType.withArguments( | 
|  | constant, | 
|  | typeEnvironment.coreTypes.boolLegacyRawType, | 
|  | constant.getType(_staticTypeContext!), | 
|  | isNonNullableByDefault)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitNullCheck(NullCheck node) { | 
|  | final Constant constant = _evaluateSubexpression(node.operand); | 
|  | if (constant is AbortConstant) return constant; | 
|  | if (constant is NullConstant) { | 
|  | return createErrorConstant(node, messageConstEvalNonNull); | 
|  | } | 
|  | if (shouldBeUnevaluated) { | 
|  | return unevaluated(node, new NullCheck(extract(constant))); | 
|  | } | 
|  | return constant; | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitSymbolLiteral(SymbolLiteral node) { | 
|  | final Reference? libraryReference = | 
|  | node.value.startsWith('_') ? libraryOf(node).reference : null; | 
|  | return canonicalize(new SymbolConstant(node.value, libraryReference)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitThrow(Throw node) { | 
|  | if (enableConstFunctions) { | 
|  | final Constant value = _evaluateSubexpression(node.expression); | 
|  | if (value is AbortConstant) return value; | 
|  | return new _AbortDueToThrowConstant(node, value); | 
|  | } | 
|  | return defaultExpression(node); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitInstantiation(Instantiation node) { | 
|  | 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); | 
|  |  | 
|  | List<DartType> typeArguments = convertTypes(types); | 
|  | if (constant is TypedefTearOffConstant) { | 
|  | Substitution substitution = | 
|  | Substitution.fromPairs(constant.parameters, typeArguments); | 
|  | typeArguments = | 
|  | constant.types.map(substitution.substituteType).toList(); | 
|  | constant = constant.tearOffConstant; | 
|  | } else { | 
|  | LoweredTypedefTearOff? loweredTypedefTearOff = | 
|  | LoweredTypedefTearOff.fromConstant(constant); | 
|  | if (loweredTypedefTearOff != null) { | 
|  | constant = | 
|  | canonicalize(loweredTypedefTearOff.targetTearOffConstant); | 
|  | Substitution substitution = Substitution.fromPairs( | 
|  | loweredTypedefTearOff.typedefTearOff.function.typeParameters, | 
|  | node.typeArguments); | 
|  | typeArguments = loweredTypedefTearOff.typeArguments | 
|  | .map(substitution.substituteType) | 
|  | .toList(); | 
|  | } | 
|  | } | 
|  | return canonicalize(new InstantiationConstant(constant, typeArguments)); | 
|  | } else { | 
|  | // Probably unreachable. | 
|  | return createInvalidExpressionConstant( | 
|  | node, | 
|  | 'The number of type arguments supplied in the partial ' | 
|  | 'instantiation does not match the number of type arguments ' | 
|  | 'of the $constant.'); | 
|  | } | 
|  | } | 
|  | // The inner expression in an instantiation can never be null, since | 
|  | // instantiations are only inferred on direct references to declarations. | 
|  | // Probably unreachable. | 
|  | return createInvalidExpressionConstant( | 
|  | node, 'Only tear-off constants can be partially instantiated.'); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitConstructorTearOff(ConstructorTearOff node) { | 
|  | return 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 createInvalidExpressionConstant( | 
|  | node, "Unexpected typedef tearoff target: ${constant}."); | 
|  | } | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant visitCheckLibraryIsLoaded(CheckLibraryIsLoaded node) { | 
|  | return createErrorConstant(node, | 
|  | templateConstEvalDeferredLibrary.withArguments(node.import.name!)); | 
|  | } | 
|  |  | 
|  | // Helper methods: | 
|  |  | 
|  | /// If both constants are DoubleConstant whose values would give different | 
|  | /// results from == and [identical], return the result of ==. Otherwise | 
|  | /// return null. | 
|  | Constant? doubleSpecialCases(Constant a, Constant b) { | 
|  | if (a is DoubleConstant && b is DoubleConstant) { | 
|  | if (a.value.isNaN && b.value.isNaN) return falseConstant; | 
|  | if (a.value == 0.0 && b.value == 0.0) return trueConstant; | 
|  | } | 
|  |  | 
|  | if (a is DoubleConstant && b is IntConstant) { | 
|  | return makeBoolConstant(a.value == b.value); | 
|  | } | 
|  |  | 
|  | if (a is IntConstant && b is DoubleConstant) { | 
|  | return makeBoolConstant(a.value == b.value); | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | bool hasPrimitiveEqual(Constant constant) { | 
|  | if (intFolder.isInt(constant)) return true; | 
|  | DartType type = constant.getType(_staticTypeContext!); | 
|  | return !(type is InterfaceType && !classHasPrimitiveEqual(type.classNode)); | 
|  | } | 
|  |  | 
|  | bool classHasPrimitiveEqual(Class klass) { | 
|  | bool? cached = primitiveEqualCache[klass]; | 
|  | if (cached != null) return cached; | 
|  | for (Procedure procedure in klass.procedures) { | 
|  | if (procedure.kind == ProcedureKind.Operator && | 
|  | procedure.name.text == '==' && | 
|  | !procedure.isAbstract && | 
|  | !procedure.isForwardingStub) { | 
|  | return primitiveEqualCache[klass] = false; | 
|  | } | 
|  | } | 
|  | if (klass.supertype == null) return true; // To be on the safe side | 
|  | return primitiveEqualCache[klass] = | 
|  | classHasPrimitiveEqual(klass.supertype!.classNode); | 
|  | } | 
|  |  | 
|  | BoolConstant makeBoolConstant(bool value) => | 
|  | value ? trueConstant : falseConstant; | 
|  |  | 
|  | bool isSubtype(Constant constant, DartType type, SubtypeCheckMode mode) { | 
|  | DartType constantType = constant.getType(_staticTypeContext!); | 
|  | if (mode == SubtypeCheckMode.ignoringNullabilities) { | 
|  | constantType = rawLegacyErasure(constantType) ?? constantType; | 
|  | } | 
|  | bool result = typeEnvironment.isSubtypeOf(constantType, type, mode); | 
|  | if (targetingJavaScript && !result) { | 
|  | if (constantType is InterfaceType && | 
|  | constantType.classNode == typeEnvironment.coreTypes.intClass) { | 
|  | // Probably unreachable. | 
|  | // With JS semantics, an integer is also a double. | 
|  | result = typeEnvironment.isSubtypeOf( | 
|  | new InterfaceType(typeEnvironment.coreTypes.doubleClass, | 
|  | constantType.nullability, const <DartType>[]), | 
|  | type, | 
|  | mode); | 
|  | } else if (intFolder.isInt(constant)) { | 
|  | // With JS semantics, an integer valued double is also an int. | 
|  | result = typeEnvironment.isSubtypeOf( | 
|  | new InterfaceType(typeEnvironment.coreTypes.intClass, | 
|  | constantType.nullability, const <DartType>[]), | 
|  | type, | 
|  | mode); | 
|  | } | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | /// Note that this returns an error-constant on error and as such the | 
|  | /// return value should be checked. | 
|  | Constant ensureIsSubtype(Constant constant, DartType type, TreeNode node) { | 
|  | bool result; | 
|  | switch (evaluationMode) { | 
|  | case EvaluationMode.strong: | 
|  | result = isSubtype(constant, type, SubtypeCheckMode.withNullabilities); | 
|  | break; | 
|  | case EvaluationMode.agnostic: | 
|  | bool strongResult = | 
|  | isSubtype(constant, type, SubtypeCheckMode.withNullabilities); | 
|  | Constant weakConstant = _weakener.visitConstant(constant) ?? constant; | 
|  | bool weakResult = isSubtype( | 
|  | weakConstant, type, SubtypeCheckMode.ignoringNullabilities); | 
|  | if (strongResult != weakResult) { | 
|  | return createErrorConstant(node, messageNonAgnosticConstant); | 
|  | } | 
|  | result = strongResult; | 
|  | break; | 
|  | case EvaluationMode.weak: | 
|  | result = | 
|  | isSubtype(constant, type, SubtypeCheckMode.ignoringNullabilities); | 
|  | break; | 
|  | } | 
|  | if (!result) { | 
|  | return createErrorConstant( | 
|  | node, | 
|  | templateConstEvalInvalidType.withArguments(constant, type, | 
|  | constant.getType(_staticTypeContext!), isNonNullableByDefault)); | 
|  | } | 
|  | return constant; | 
|  | } | 
|  |  | 
|  | /// Returns the types on success and null on failure. | 
|  | /// Note that on failure an errorConstant is saved in [_gotError]. | 
|  | List<DartType>? _evaluateTypeArguments(TreeNode node, Arguments arguments) { | 
|  | return _evaluateDartTypes(node, arguments.types); | 
|  | } | 
|  |  | 
|  | /// Returns the types on success and null on failure. | 
|  | /// Note that on failure an errorConstant is saved in [_gotError]. | 
|  | List<DartType>? _evaluateSuperTypeArguments(TreeNode node, Supertype type) { | 
|  | return _evaluateDartTypes(node, type.typeArguments); | 
|  | } | 
|  |  | 
|  | /// Upon failure in certain procedure calls (e.g. [_evaluateDartTypes]) the | 
|  | /// "error"-constant is saved here. Normally this should be null. | 
|  | /// Once a caller calls such a procedure and it gives an error here, | 
|  | /// the caller should fetch it an null-out this variable. | 
|  | AbortConstant? _gotError; | 
|  |  | 
|  | /// Returns the types on success and null on failure. | 
|  | /// Note that on failure an errorConstant is saved in [_gotError]. | 
|  | List<DartType>? _evaluateDartTypes(TreeNode node, List<DartType> types) { | 
|  | // TODO: Once the frontend guarantees that there are no free type variables | 
|  | // left over after substitution, we can enable this shortcut again: | 
|  | // if (env.isEmpty) return types; | 
|  | List<DartType> result = | 
|  | new List<DartType>.filled(types.length, dummyDartType, growable: true); | 
|  | for (int i = 0; i < types.length; i++) { | 
|  | DartType? type = _evaluateDartType(node, types[i]); | 
|  | if (type == null) { | 
|  | return null; | 
|  | } | 
|  | assert(_gotError == null); | 
|  | // ignore: unnecessary_null_comparison | 
|  | assert(type != null); | 
|  | result[i] = type; | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | /// Returns the type on success and null on failure. | 
|  | /// Note that on failure an errorConstant is saved in [_gotError]. | 
|  | DartType? _evaluateDartType(TreeNode node, DartType type) { | 
|  | final DartType result = env.substituteType(type); | 
|  |  | 
|  | if (!isInstantiated(result)) { | 
|  | _gotError = createErrorConstant( | 
|  | node, | 
|  | templateConstEvalFreeTypeParameter.withArguments( | 
|  | type, isNonNullableByDefault)); | 
|  | return null; | 
|  | } | 
|  |  | 
|  | return result; | 
|  | } | 
|  |  | 
|  | /// Returns the types on success and null on failure. | 
|  | /// Note that on failure an errorConstant is saved in [_gotError]. | 
|  | List<Constant>? _evaluatePositionalArguments(Arguments arguments) { | 
|  | List<Constant> result = new List<Constant>.filled( | 
|  | arguments.positional.length, dummyConstant, | 
|  | growable: true); | 
|  | for (int i = 0; i < arguments.positional.length; i++) { | 
|  | Constant constant = _evaluateSubexpression(arguments.positional[i]); | 
|  | if (constant is AbortConstant) { | 
|  | _gotError = constant; | 
|  | return null; | 
|  | } | 
|  | result[i] = constant; | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | /// Returns the arguments on success and null on failure. | 
|  | /// Note that on failure an errorConstant is saved in [_gotError]. | 
|  | Map<String, Constant>? _evaluateNamedArguments(Arguments arguments) { | 
|  | if (arguments.named.isEmpty) return const <String, Constant>{}; | 
|  |  | 
|  | final Map<String, Constant> named = {}; | 
|  | for (NamedExpression pair in arguments.named) { | 
|  | if (_gotError != null) return null; | 
|  | Constant constant = _evaluateSubexpression(pair.value); | 
|  | if (constant is AbortConstant) { | 
|  | _gotError = constant; | 
|  | return null; | 
|  | } | 
|  | named[pair.name] = constant; | 
|  | } | 
|  | if (_gotError != null) return null; | 
|  | return named; | 
|  | } | 
|  |  | 
|  | Arguments unevaluatedArguments(List<Constant> positionalArgs, | 
|  | Map<String, Constant> namedArgs, List<DartType> types) { | 
|  | final List<Expression> positional = | 
|  | new List<Expression>.filled(positionalArgs.length, dummyExpression); | 
|  | final List<NamedExpression> named = new List<NamedExpression>.filled( | 
|  | namedArgs.length, dummyNamedExpression); | 
|  | for (int i = 0; i < positionalArgs.length; ++i) { | 
|  | positional[i] = extract(positionalArgs[i]); | 
|  | } | 
|  | int i = 0; | 
|  | namedArgs.forEach((String name, Constant value) { | 
|  | named[i++] = new NamedExpression(name, extract(value)); | 
|  | }); | 
|  | return new Arguments(positional, named: named, types: types); | 
|  | } | 
|  |  | 
|  | Constant canonicalize(Constant constant) { | 
|  | // Don't use putIfAbsent to avoid the context allocation needed | 
|  | // for the closure. | 
|  | return canonicalizationCache[constant] ??= constant; | 
|  | } | 
|  |  | 
|  | T withNewInstanceBuilder<T>( | 
|  | Class klass, List<DartType> typeArguments, T fn()) { | 
|  | InstanceBuilder? old = instanceBuilder; | 
|  | instanceBuilder = new InstanceBuilder(this, klass, typeArguments); | 
|  | T result = fn(); | 
|  | instanceBuilder = old; | 
|  | return result; | 
|  | } | 
|  |  | 
|  | T withNewEnvironment<T>(T fn()) { | 
|  | final EvaluationEnvironment oldEnv = env; | 
|  | if (enableConstFunctions) { | 
|  | env = new EvaluationEnvironment.withParent(env); | 
|  | } else { | 
|  | env = new EvaluationEnvironment(); | 
|  | } | 
|  | T result = fn(); | 
|  | env = oldEnv; | 
|  | return result; | 
|  | } | 
|  |  | 
|  | T withEnvironment<T>(EvaluationEnvironment newEnv, T fn()) { | 
|  | final EvaluationEnvironment oldEnv = env; | 
|  | env = newEnv; | 
|  | T result = fn(); | 
|  | env = oldEnv; | 
|  | return result; | 
|  | } | 
|  |  | 
|  | /// Binary operation between two operands, at least one of which is a double. | 
|  | Constant evaluateBinaryNumericOperation( | 
|  | String op, num a, num b, Expression node) { | 
|  | switch (op) { | 
|  | case '+': | 
|  | return new DoubleConstant((a + b) as double); | 
|  | case '-': | 
|  | return new DoubleConstant((a - b) as double); | 
|  | case '*': | 
|  | return new DoubleConstant((a * b) as double); | 
|  | case '/': | 
|  | return new DoubleConstant(a / b); | 
|  | case '~/': | 
|  | if (b == 0) { | 
|  | return createErrorConstant( | 
|  | node, templateConstEvalZeroDivisor.withArguments(op, '$a')); | 
|  | } | 
|  | return intFolder.truncatingDivide(node, a, b); | 
|  | case '%': | 
|  | return new DoubleConstant((a % b) as double); | 
|  | } | 
|  |  | 
|  | switch (op) { | 
|  | case '<': | 
|  | return makeBoolConstant(a < b); | 
|  | case '<=': | 
|  | return makeBoolConstant(a <= b); | 
|  | case '>=': | 
|  | return makeBoolConstant(a >= b); | 
|  | case '>': | 
|  | return makeBoolConstant(a > b); | 
|  | } | 
|  |  | 
|  | // Probably unreachable. | 
|  | return createInvalidExpressionConstant( | 
|  | node, "Unexpected binary numeric operation '$op'."); | 
|  | } | 
|  |  | 
|  | // TODO(johnniwinther): Remove the need for this by adding a current library | 
|  | // field. | 
|  | Library libraryOf(TreeNode? node) { | 
|  | // The tree structure of the kernel AST ensures we always have an enclosing | 
|  | // library. | 
|  | while (true) { | 
|  | if (node is Library) return node; | 
|  | node = node!.parent; | 
|  | } | 
|  | } | 
|  |  | 
|  | @override | 
|  | Constant defaultBasicLiteral(BasicLiteral node) => defaultExpression(node); | 
|  |  | 
|  | @override | 
|  | Constant visitAwaitExpression(AwaitExpression node) => | 
|  | defaultExpression(node); | 
|  |  | 
|  | @override | 
|  | Constant visitBlockExpression(BlockExpression node) => | 
|  | defaultExpression(node); | 
|  |  | 
|  | @override | 
|  | Constant visitDynamicSet(DynamicSet node) => defaultExpression(node); | 
|  |  | 
|  | @override | 
|  | Constant visitInstanceGetterInvocation(InstanceGetterInvocation node) => | 
|  | defaultExpression(node); | 
|  |  | 
|  | @override | 
|  | Constant visitInstanceSet(InstanceSet node) => defaultExpression(node); | 
|  |  | 
|  | @override | 
|  | Constant visitLoadLibrary(LoadLibrary node) => defaultExpression(node); | 
|  |  | 
|  | @override | 
|  | Constant visitRethrow(Rethrow node) => defaultExpression(node); | 
|  |  | 
|  | @override | 
|  | Constant visitStaticSet(StaticSet node) => defaultExpression(node); | 
|  |  | 
|  | @override | 
|  | Constant visitSuperMethodInvocation(SuperMethodInvocation node) => | 
|  | defaultExpression(node); | 
|  |  | 
|  | @override | 
|  | Constant visitSuperPropertyGet(SuperPropertyGet node) => | 
|  | defaultExpression(node); | 
|  |  | 
|  | @override | 
|  | Constant visitSuperPropertySet(SuperPropertySet node) => | 
|  | defaultExpression(node); | 
|  |  | 
|  | @override | 
|  | Constant visitThisExpression(ThisExpression node) => defaultExpression(node); | 
|  | } | 
|  |  | 
|  | class StatementConstantEvaluator extends StatementVisitor<ExecutionStatus> { | 
|  | ConstantEvaluator exprEvaluator; | 
|  |  | 
|  | StatementConstantEvaluator(this.exprEvaluator) { | 
|  | if (!exprEvaluator.enableConstFunctions) { | 
|  | throw new UnsupportedError("Const functions feature is not enabled."); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Evaluate the expression using the [ConstantEvaluator]. | 
|  | Constant evaluate(Expression expr) => expr.accept(exprEvaluator); | 
|  |  | 
|  | @override | 
|  | ExecutionStatus defaultStatement(Statement node) { | 
|  | throw new UnsupportedError( | 
|  | 'Statement constant evaluation does not support ${node.runtimeType}.'); | 
|  | } | 
|  |  | 
|  | @override | 
|  | ExecutionStatus visitAssertBlock(AssertBlock node) => defaultStatement(node); | 
|  |  | 
|  | @override | 
|  | ExecutionStatus visitAssertStatement(AssertStatement node) { | 
|  | AbortConstant? error = exprEvaluator.checkAssert(node); | 
|  | if (error != null) return new AbortStatus(error); | 
|  | return const ProceedStatus(); | 
|  | } | 
|  |  | 
|  | @override | 
|  | ExecutionStatus visitBlock(Block node) { | 
|  | return exprEvaluator.withNewEnvironment(() { | 
|  | for (Statement statement in node.statements) { | 
|  | final ExecutionStatus status = statement.accept(this); | 
|  | if (status is! ProceedStatus) return status; | 
|  | } | 
|  | return const ProceedStatus(); | 
|  | }); | 
|  | } | 
|  |  | 
|  | @override | 
|  | ExecutionStatus visitBreakStatement(BreakStatement node) => | 
|  | new BreakStatus(node.target); | 
|  |  | 
|  | @override | 
|  | ExecutionStatus visitContinueSwitchStatement(ContinueSwitchStatement node) => | 
|  | node.target.body.accept(this); | 
|  |  | 
|  | @override | 
|  | ExecutionStatus visitDoStatement(DoStatement node) { | 
|  | Constant condition; | 
|  | do { | 
|  | ExecutionStatus status = node.body.accept(this); | 
|  | if (status is! ProceedStatus) return status; | 
|  | condition = evaluate(node.condition); | 
|  | } while (condition is BoolConstant && condition.value); | 
|  |  | 
|  | if (condition is AbortConstant) { | 
|  | return new AbortStatus(condition); | 
|  | } | 
|  | assert(condition is BoolConstant); | 
|  | return const ProceedStatus(); | 
|  | } | 
|  |  | 
|  | @override | 
|  | ExecutionStatus visitEmptyStatement(EmptyStatement node) => | 
|  | const ProceedStatus(); | 
|  |  | 
|  | @override | 
|  | ExecutionStatus visitFunctionDeclaration(FunctionDeclaration node) { | 
|  | final EvaluationEnvironment newEnv = | 
|  | new EvaluationEnvironment.withParent(exprEvaluator.env); | 
|  | newEnv.addVariableValue( | 
|  | node.variable, new FunctionValue(node.function, null)); | 
|  | final FunctionValue function = new FunctionValue(node.function, newEnv); | 
|  | exprEvaluator.env.addVariableValue(node.variable, function); | 
|  | return const ProceedStatus(); | 
|  | } | 
|  |  | 
|  | @override | 
|  | ExecutionStatus visitIfStatement(IfStatement node) { | 
|  | Constant condition = evaluate(node.condition); | 
|  | if (condition is AbortConstant) return new AbortStatus(condition); | 
|  | assert(condition is BoolConstant); | 
|  | if ((condition as BoolConstant).value) { | 
|  | return node.then.accept(this); | 
|  | } else if (node.otherwise != null) { | 
|  | return node.otherwise!.accept(this); | 
|  | } | 
|  | return const ProceedStatus(); | 
|  | } | 
|  |  | 
|  | @override | 
|  | ExecutionStatus visitForStatement(ForStatement node) { | 
|  | for (VariableDeclaration variable in node.variables) { | 
|  | final ExecutionStatus status = variable.accept(this); | 
|  | if (status is! ProceedStatus) return status; | 
|  | } | 
|  |  | 
|  | Constant? condition = | 
|  | node.condition != null ? evaluate(node.condition!) : null; | 
|  | while (node.condition == null || condition is BoolConstant) { | 
|  | if (condition is BoolConstant && !condition.value) break; | 
|  |  | 
|  | final ExecutionStatus status = node.body.accept(this); | 
|  | if (status is! ProceedStatus) return status; | 
|  |  | 
|  | for (Expression update in node.updates) { | 
|  | Constant updateConstant = evaluate(update); | 
|  | if (updateConstant is AbortConstant) { | 
|  | return new AbortStatus(updateConstant); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (node.condition != null) { | 
|  | condition = evaluate(node.condition!); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (condition is AbortConstant) return new AbortStatus(condition); | 
|  | assert(condition is BoolConstant); | 
|  | return const ProceedStatus(); | 
|  | } | 
|  |  | 
|  | @override | 
|  | ExecutionStatus visitExpressionStatement(ExpressionStatement node) { | 
|  | Constant value = evaluate(node.expression); | 
|  | if (value is AbortConstant) return new AbortStatus(value); | 
|  | return const ProceedStatus(); | 
|  | } | 
|  |  | 
|  | @override | 
|  | ExecutionStatus visitLabeledStatement(LabeledStatement node) { | 
|  | final ExecutionStatus status = node.body.accept(this); | 
|  | if (status is BreakStatus && status.target == node) { | 
|  | return const ProceedStatus(); | 
|  | } | 
|  | return status; | 
|  | } | 
|  |  | 
|  | @override | 
|  | ExecutionStatus visitReturnStatement(ReturnStatement node) { | 
|  | Constant? result; | 
|  | if (node.expression != null) { | 
|  | result = evaluate(node.expression!); | 
|  | if (result is AbortConstant) return new AbortStatus(result); | 
|  | } | 
|  | return new ReturnStatus(result); | 
|  | } | 
|  |  | 
|  | @override | 
|  | ExecutionStatus visitSwitchStatement(SwitchStatement node) { | 
|  | final Constant value = evaluate(node.expression); | 
|  | if (value is AbortConstant) return new AbortStatus(value); | 
|  |  | 
|  | for (SwitchCase switchCase in node.cases) { | 
|  | if (switchCase.isDefault) return switchCase.body.accept(this); | 
|  | for (Expression expr in switchCase.expressions) { | 
|  | final Constant caseValue = evaluate(expr); | 
|  | if (value == caseValue) return switchCase.body.accept(this); | 
|  | } | 
|  | } | 
|  | return const ProceedStatus(); | 
|  | } | 
|  |  | 
|  | @override | 
|  | ExecutionStatus visitTryCatch(TryCatch node) { | 
|  | final ExecutionStatus tryStatus = node.body.accept(this); | 
|  | if (tryStatus is AbortStatus) { | 
|  | final Constant error = tryStatus.error; | 
|  | if (error is _AbortDueToThrowConstant) { | 
|  | final Object throwValue = error.throwValue; | 
|  | final DartType defaultType = | 
|  | exprEvaluator.typeEnvironment.coreTypes.objectNonNullableRawType; | 
|  |  | 
|  | DartType? throwType; | 
|  | if (throwValue is Constant) { | 
|  | throwType = throwValue.getType(exprEvaluator._staticTypeContext!); | 
|  | } else if (throwValue is StateError) { | 
|  | final Class stateErrorClass = exprEvaluator | 
|  | .coreTypes.coreLibrary.classes | 
|  | .firstWhere((Class klass) => klass.name == 'StateError'); | 
|  | throwType = | 
|  | new InterfaceType(stateErrorClass, Nullability.nonNullable); | 
|  | } else if (throwValue is RangeError) { | 
|  | final Class rangeErrorClass = exprEvaluator | 
|  | .coreTypes.coreLibrary.classes | 
|  | .firstWhere((Class klass) => klass.name == 'RangeError'); | 
|  | throwType = | 
|  | new InterfaceType(rangeErrorClass, Nullability.nonNullable); | 
|  | } | 
|  | assert(throwType != null); | 
|  |  | 
|  | for (Catch catchClause in node.catches) { | 
|  | if (exprEvaluator.typeEnvironment.isSubtypeOf(throwType!, | 
|  | catchClause.guard, SubtypeCheckMode.withNullabilities) || | 
|  | catchClause.guard == defaultType) { | 
|  | return exprEvaluator.withNewEnvironment(() { | 
|  | if (catchClause.exception != null) { | 
|  | // TODO(kallentu): Store non-constant exceptions. | 
|  | if (throwValue is Constant) { | 
|  | exprEvaluator.env | 
|  | .addVariableValue(catchClause.exception!, throwValue); | 
|  | } | 
|  | } | 
|  | // TODO(kallentu): Store appropriate stack trace in environment. | 
|  | return catchClause.body.accept(this); | 
|  | }); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | return tryStatus; | 
|  | } | 
|  |  | 
|  | @override | 
|  | ExecutionStatus visitTryFinally(TryFinally node) { | 
|  | final ExecutionStatus tryStatus = node.body.accept(this); | 
|  | final ExecutionStatus finallyStatus = node.finalizer.accept(this); | 
|  | if (finallyStatus is! ProceedStatus) return finallyStatus; | 
|  | return tryStatus; | 
|  | } | 
|  |  | 
|  | @override | 
|  | ExecutionStatus visitVariableDeclaration(VariableDeclaration node) { | 
|  | Constant value; | 
|  | if (node.initializer != null) { | 
|  | value = evaluate(node.initializer!); | 
|  | if (value is AbortConstant) return new AbortStatus(value); | 
|  | } else { | 
|  | value = new NullConstant(); | 
|  | } | 
|  | exprEvaluator.env.addVariableValue(node, value); | 
|  | return const ProceedStatus(); | 
|  | } | 
|  |  | 
|  | @override | 
|  | ExecutionStatus visitWhileStatement(WhileStatement node) { | 
|  | Constant condition = evaluate(node.condition); | 
|  | while (condition is BoolConstant && condition.value) { | 
|  | final ExecutionStatus status = node.body.accept(this); | 
|  | if (status is! ProceedStatus) return status; | 
|  | condition = evaluate(node.condition); | 
|  | } | 
|  | if (condition is AbortConstant) return new AbortStatus(condition); | 
|  | assert(condition is BoolConstant); | 
|  | return const ProceedStatus(); | 
|  | } | 
|  | } | 
|  |  | 
|  | class ConstantCoverage { | 
|  | final Map<Uri, Set<Reference>> constructorCoverage; | 
|  |  | 
|  | ConstantCoverage(this.constructorCoverage); | 
|  | } | 
|  |  | 
|  | /// Holds the necessary information for a constant object, namely | 
|  | ///   * the [klass] being instantiated | 
|  | ///   * the [typeArguments] used for the instantiation | 
|  | ///   * the [fields] the instance will obtain (all fields from the | 
|  | ///     instantiated [klass] up to the [Object] klass). | 
|  | class InstanceBuilder { | 
|  | ConstantEvaluator evaluator; | 
|  |  | 
|  | /// The class of the new instance. | 
|  | final Class klass; | 
|  |  | 
|  | /// The values of the type parameters of the new instance. | 
|  | final List<DartType> typeArguments; | 
|  |  | 
|  | /// The field values of the new instance. | 
|  | final Map<Field, Constant> fields = <Field, Constant>{}; | 
|  |  | 
|  | final List<AssertStatement> asserts = <AssertStatement>[]; | 
|  |  | 
|  | final List<Expression> unusedArguments = <Expression>[]; | 
|  |  | 
|  | InstanceBuilder(this.evaluator, this.klass, this.typeArguments); | 
|  |  | 
|  | void setFieldValue(Field field, Constant constant) { | 
|  | fields[field] = constant; | 
|  | } | 
|  |  | 
|  | InstanceConstant buildInstance() { | 
|  | assert(asserts.isEmpty); | 
|  | final Map<Reference, Constant> fieldValues = <Reference, Constant>{}; | 
|  | fields.forEach((Field field, Constant value) { | 
|  | assert(value is! UnevaluatedConstant); | 
|  | fieldValues[field.getterReference] = value; | 
|  | }); | 
|  | assert(unusedArguments.isEmpty); | 
|  | return new InstanceConstant(klass.reference, typeArguments, fieldValues); | 
|  | } | 
|  |  | 
|  | InstanceCreation buildUnevaluatedInstance() { | 
|  | final Map<Reference, Expression> fieldValues = <Reference, Expression>{}; | 
|  | fields.forEach((Field field, Constant value) { | 
|  | fieldValues[field.getterReference] = evaluator.extract(value); | 
|  | }); | 
|  | return new InstanceCreation( | 
|  | klass.reference, typeArguments, fieldValues, asserts, unusedArguments); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Holds an environment of type parameters, parameters and variables. | 
|  | class EvaluationEnvironment { | 
|  | /// The values of the type parameters in scope. | 
|  | final Map<TypeParameter, DartType> _typeVariables = | 
|  | <TypeParameter, DartType>{}; | 
|  |  | 
|  | /// The references to values of the parameters/variables in scope. | 
|  | final Map<VariableDeclaration, EvaluationReference> _variables = | 
|  | <VariableDeclaration, EvaluationReference>{}; | 
|  |  | 
|  | /// The variables that hold unevaluated constants. | 
|  | /// | 
|  | /// Variables are removed from this set when looked up, leaving only the | 
|  | /// unread variables at the end. | 
|  | final Set<VariableDeclaration> _unreadUnevaluatedVariables = | 
|  | new Set<VariableDeclaration>(); | 
|  |  | 
|  | final EvaluationEnvironment? _parent; | 
|  |  | 
|  | EvaluationEnvironment() : _parent = null; | 
|  | EvaluationEnvironment.withParent(this._parent); | 
|  |  | 
|  | /// Whether the current environment is empty. | 
|  | bool get isEmpty { | 
|  | // Since we look up variables in enclosing environment, the environment | 
|  | // is not empty if its parent is not empty. | 
|  | if (_parent != null && !_parent!.isEmpty) return false; | 
|  | return _typeVariables.isEmpty && _variables.isEmpty; | 
|  | } | 
|  |  | 
|  | void addTypeParameterValue(TypeParameter parameter, DartType value) { | 
|  | assert(!_typeVariables.containsKey(parameter)); | 
|  | _typeVariables[parameter] = value; | 
|  | } | 
|  |  | 
|  | void addVariableValue(VariableDeclaration variable, Constant value) { | 
|  | _variables[variable] = new EvaluationReference(value); | 
|  | if (value is UnevaluatedConstant) { | 
|  | _unreadUnevaluatedVariables.add(variable); | 
|  | } | 
|  | } | 
|  |  | 
|  | Constant? updateVariableValue(VariableDeclaration variable, Constant value) { | 
|  | EvaluationReference? reference = _variables[variable]; | 
|  | if (reference != null) { | 
|  | reference.value = value; | 
|  | return value; | 
|  | } | 
|  | return _parent?.updateVariableValue(variable, value); | 
|  | } | 
|  |  | 
|  | Constant? lookupVariable(VariableDeclaration variable) { | 
|  | Constant? value = _variables[variable]?.value; | 
|  | if (value is UnevaluatedConstant) { | 
|  | _unreadUnevaluatedVariables.remove(variable); | 
|  | } else if (value == null) { | 
|  | return _parent?.lookupVariable(variable); | 
|  | } | 
|  | return value; | 
|  | } | 
|  |  | 
|  | /// The unevaluated constants of variables that were never read. | 
|  | Iterable<UnevaluatedConstant> get unevaluatedUnreadConstants { | 
|  | if (_unreadUnevaluatedVariables.isEmpty) return const []; | 
|  | return _unreadUnevaluatedVariables.map<UnevaluatedConstant>( | 
|  | (VariableDeclaration variable) => | 
|  | _variables[variable]!.value as UnevaluatedConstant); | 
|  | } | 
|  |  | 
|  | DartType substituteType(DartType type) { | 
|  | if (_typeVariables.isEmpty) return _parent?.substituteType(type) ?? type; | 
|  | final DartType substitutedType = substitute(type, _typeVariables); | 
|  | if (identical(substitutedType, type) && _parent != null) { | 
|  | // No distinct type created, substitute type in parent. | 
|  | return _parent!.substituteType(type); | 
|  | } | 
|  | return substitutedType; | 
|  | } | 
|  | } | 
|  |  | 
|  | class RedundantFileUriExpressionRemover extends Transformer { | 
|  | Uri? currentFileUri = null; | 
|  |  | 
|  | TreeNode visitFileUriExpression(FileUriExpression node) { | 
|  | if (node.fileUri == currentFileUri) { | 
|  | return node.expression.accept(this); | 
|  | } else { | 
|  | Uri? oldFileUri = currentFileUri; | 
|  | currentFileUri = node.fileUri; | 
|  | node.expression = transform(node.expression)..parent = node; | 
|  | currentFileUri = oldFileUri; | 
|  | return node; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Location that stores a value in the [ConstantEvaluator]. | 
|  | class EvaluationReference { | 
|  | Constant value; | 
|  |  | 
|  | EvaluationReference(this.value); | 
|  | } | 
|  |  | 
|  | /// Represents a status for statement execution. | 
|  | abstract class ExecutionStatus { | 
|  | const ExecutionStatus(); | 
|  | } | 
|  |  | 
|  | /// Status that the statement completed execution successfully. | 
|  | class ProceedStatus extends ExecutionStatus { | 
|  | const ProceedStatus(); | 
|  | } | 
|  |  | 
|  | /// Status that the statement returned a valid [Constant] value. | 
|  | class ReturnStatus extends ExecutionStatus { | 
|  | final Constant? value; | 
|  | ReturnStatus(this.value); | 
|  | } | 
|  |  | 
|  | /// Status with an exception or error that the statement has thrown. | 
|  | class AbortStatus extends ExecutionStatus { | 
|  | final AbortConstant error; | 
|  | AbortStatus(this.error); | 
|  | } | 
|  |  | 
|  | /// Status that the statement breaks out of an enclosing [LabeledStatement]. | 
|  | class BreakStatus extends ExecutionStatus { | 
|  | final LabeledStatement target; | 
|  | BreakStatus(this.target); | 
|  | } | 
|  |  | 
|  | /// Mutable lists used within the [ConstantEvaluator]. | 
|  | class MutableListConstant extends ListConstant { | 
|  | MutableListConstant(DartType typeArgument, List<Constant> entries) | 
|  | : super(typeArgument, entries); | 
|  |  | 
|  | @override | 
|  | String toString() => 'MutableListConstant(${toStringInternal()})'; | 
|  | } | 
|  |  | 
|  | /// An intermediate result that is used for invoking function nodes with their | 
|  | /// respective environment within the [ConstantEvaluator]. | 
|  | class FunctionValue implements Constant { | 
|  | final FunctionNode function; | 
|  | final EvaluationEnvironment? environment; | 
|  |  | 
|  | FunctionValue(this.function, this.environment); | 
|  |  | 
|  | @override | 
|  | R accept<R>(ConstantVisitor<R> v) { | 
|  | throw new UnimplementedError(); | 
|  | } | 
|  |  | 
|  | @override | 
|  | R 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; | 
|  |  | 
|  | _AbortDueToErrorConstant(this.node, this.message, {this.context}); | 
|  |  | 
|  | @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 TreeNode node; | 
|  | final String message; | 
|  |  | 
|  | _AbortDueToInvalidExpressionConstant(this.node, this.message); | 
|  |  | 
|  | @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); | 
|  |  | 
|  | void reportInvalidExpression(InvalidExpression node); | 
|  | } | 
|  |  | 
|  | class SimpleErrorReporter implements ErrorReporter { | 
|  | const SimpleErrorReporter(); | 
|  |  | 
|  | @override | 
|  | void report(LocatedMessage message, List<LocatedMessage>? context) { | 
|  | _report(message); | 
|  | if (context != null) { | 
|  | for (LocatedMessage contextMessage in context) { | 
|  | _report(contextMessage); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | @override | 
|  | void reportInvalidExpression(InvalidExpression node) { | 
|  | // Ignored | 
|  | } | 
|  |  | 
|  | void _report(LocatedMessage message) { | 
|  | reportMessage(message.uri, message.charOffset, message.message); | 
|  | } | 
|  |  | 
|  | void reportMessage(Uri? uri, int offset, String message) { | 
|  | io.exitCode = 42; | 
|  | io.stderr.writeln('$uri:$offset Constant evaluation error: $message'); | 
|  | } | 
|  | } | 
|  |  | 
|  | 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; | 
|  | } |