| // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| /// This library implements a kernel2kernel constant evaluation transformation. |
| /// |
| /// Even though it is expected that the frontend does not emit kernel AST which |
| /// contains compile-time errors, this transformation still performs some |
| /// validation and throws a [ConstantEvaluationError] if there was a |
| /// compile-time errors. |
| /// |
| /// Due to the lack information which is is only available in the front-end, |
| /// this validation is incomplete (e.g. whether an integer literal used the |
| /// hexadecimal syntax or not). |
| /// |
| /// Furthermore due to the lowering of certain constructs in the front-end |
| /// (e.g. '??') we need to support a super-set of the normal constant expression |
| /// language. Issue(http://dartbug.com/31799) |
| library fasta.constant_evaluator; |
| |
| import 'dart:io' as io; |
| |
| import 'package:kernel/ast.dart'; |
| import 'package:kernel/class_hierarchy.dart'; |
| import 'package:kernel/clone.dart'; |
| import 'package:kernel/core_types.dart'; |
| import 'package:kernel/kernel.dart'; |
| import 'package:kernel/src/const_canonical_type.dart'; |
| import 'package:kernel/src/legacy_erasure.dart'; |
| import 'package:kernel/src/norm.dart'; |
| import 'package:kernel/src/printer.dart' show AstPrinter, AstTextStrategy; |
| import 'package:kernel/type_algebra.dart'; |
| import 'package:kernel/type_environment.dart'; |
| import 'package:kernel/target/targets.dart'; |
| |
| import '../fasta_codes.dart' |
| show |
| LocatedMessage, |
| Message, |
| messageConstEvalCircularity, |
| messageConstEvalContext, |
| messageConstEvalExtension, |
| messageConstEvalFailedAssertion, |
| messageConstEvalNotListOrSetInSpread, |
| messageConstEvalNotMapInSpread, |
| messageConstEvalNonNull, |
| messageConstEvalNullValue, |
| messageConstEvalStartingPoint, |
| messageConstEvalUnevaluated, |
| messageNonAgnosticConstant, |
| messageNotAConstantExpression, |
| noLength, |
| templateConstEvalCaseImplementsEqual, |
| templateConstEvalDeferredLibrary, |
| templateConstEvalDuplicateElement, |
| templateConstEvalDuplicateKey, |
| templateConstEvalElementImplementsEqual, |
| templateConstEvalFailedAssertionWithMessage, |
| templateConstEvalFreeTypeParameter, |
| templateConstEvalGetterNotFound, |
| templateConstEvalInvalidType, |
| templateConstEvalInvalidBinaryOperandType, |
| templateConstEvalInvalidEqualsOperandType, |
| templateConstEvalInvalidMethodInvocation, |
| templateConstEvalInvalidPropertyGet, |
| templateConstEvalInvalidStaticInvocation, |
| templateConstEvalInvalidStringInterpolationOperand, |
| templateConstEvalInvalidSymbolName, |
| templateConstEvalKeyImplementsEqual, |
| templateConstEvalNonConstantVariableGet, |
| templateConstEvalUnhandledCoreException, |
| templateConstEvalUnhandledException, |
| templateConstEvalZeroDivisor; |
| |
| import 'constant_int_folder.dart'; |
| |
| part 'constant_collection_builders.dart'; |
| |
| Component transformComponent( |
| Component component, |
| ConstantsBackend backend, |
| Map<String, String> environmentDefines, |
| ErrorReporter errorReporter, |
| EvaluationMode evaluationMode, |
| {required bool evaluateAnnotations, |
| required bool desugarSets, |
| required bool enableTripleShift, |
| required bool enableConstFunctions, |
| required bool 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); |
| transformRedirectingFactoryConstructorList( |
| node.redirectingFactoryConstructors, node); |
| }); |
| _staticTypeContext = oldStaticTypeContext; |
| return node; |
| } |
| |
| @override |
| Extension visitExtension(Extension node, TreeNode? removalSentinel) { |
| StaticTypeContext? oldStaticTypeContext = _staticTypeContext; |
| _staticTypeContext = new StaticTypeContext.forAnnotations( |
| node.enclosingLibrary, typeEnvironment); |
| constantEvaluator.withNewEnvironment(() { |
| transformAnnotations(node.annotations, node); |
| transformTypeParameterList(node.typeParameters, node); |
| }); |
| _staticTypeContext = oldStaticTypeContext; |
| return node; |
| } |
| |
| @override |
| Procedure visitProcedure(Procedure node, TreeNode? removalSentinel) { |
| StaticTypeContext? oldStaticTypeContext = _staticTypeContext; |
| _staticTypeContext = new StaticTypeContext(node, typeEnvironment); |
| constantEvaluator.withNewEnvironment(() { |
| transformAnnotations(node.annotations, node); |
| node.function = transform(node.function)..parent = node; |
| }); |
| _staticTypeContext = oldStaticTypeContext; |
| return node; |
| } |
| |
| @override |
| Constructor visitConstructor(Constructor node, TreeNode? removalSentinel) { |
| StaticTypeContext? oldStaticTypeContext = _staticTypeContext; |
| _staticTypeContext = new StaticTypeContext(node, typeEnvironment); |
| constantEvaluator.withNewEnvironment(() { |
| transformAnnotations(node.annotations, node); |
| transformInitializerList(node.initializers, node); |
| node.function = transform(node.function)..parent = node; |
| }); |
| _staticTypeContext = oldStaticTypeContext; |
| return node; |
| } |
| |
| @override |
| Typedef visitTypedef(Typedef node, TreeNode? removalSentinel) { |
| constantEvaluator.withNewEnvironment(() { |
| transformAnnotations(node.annotations, node); |
| transformTypeParameterList(node.typeParameters, node); |
| transformTypeParameterList(node.typeParametersOfFunctionType, node); |
| transformVariableDeclarationList(node.positionalParameters, node); |
| transformVariableDeclarationList(node.namedParameters, node); |
| }); |
| return node; |
| } |
| |
| @override |
| RedirectingFactoryConstructor visitRedirectingFactoryConstructor( |
| RedirectingFactoryConstructor node, TreeNode? removalSentinel) { |
| // Currently unreachable as the compiler doesn't produce |
| // RedirectingFactoryConstructor. |
| StaticTypeContext? oldStaticTypeContext = _staticTypeContext; |
| _staticTypeContext = new StaticTypeContext(node, typeEnvironment); |
| constantEvaluator.withNewEnvironment(() { |
| transformAnnotations(node.annotations, node); |
| transformTypeParameterList(node.typeParameters, node); |
| transformVariableDeclarationList(node.positionalParameters, node); |
| transformVariableDeclarationList(node.namedParameters, node); |
| }); |
| _staticTypeContext = oldStaticTypeContext; |
| return node; |
| } |
| |
| @override |
| TypeParameter visitTypeParameter( |
| TypeParameter node, TreeNode? removalSentinel) { |
| transformAnnotations(node.annotations, node); |
| return node; |
| } |
| |
| void transformAnnotations(List<Expression> nodes, TreeNode parent) { |
| if (evaluateAnnotations && nodes.length > 0) { |
| transformExpressions(nodes, parent); |
| } |
| } |
| |
| void transformExpressions(List<Expression> nodes, TreeNode parent) { |
| constantEvaluator.withNewEnvironment(() { |
| for (int i = 0; i < nodes.length; ++i) { |
| nodes[i] = evaluateAndTransformWithContext(parent, nodes[i]) |
| ..parent = parent; |
| } |
| }); |
| } |
| |
| // Handle definition of constants: |
| |
| @override |
| FunctionNode visitFunctionNode(FunctionNode node, TreeNode? removalSentinel) { |
| transformTypeParameterList(node.typeParameters, node); |
| final int positionalParameterCount = node.positionalParameters.length; |
| for (int i = 0; i < positionalParameterCount; ++i) { |
| final VariableDeclaration variable = node.positionalParameters[i]; |
| transformAnnotations(variable.annotations, variable); |
| Expression? initializer = variable.initializer; |
| if (initializer != null) { |
| variable.initializer = |
| evaluateAndTransformWithContext(variable, initializer) |
| ..parent = variable; |
| } |
| } |
| for (final VariableDeclaration variable in node.namedParameters) { |
| transformAnnotations(variable.annotations, variable); |
| Expression? initializer = variable.initializer; |
| if (initializer != null) { |
| variable.initializer = |
| evaluateAndTransformWithContext(variable, initializer) |
| ..parent = variable; |
| } |
| } |
| if (node.body != null) { |
| node.body = transform(node.body!)..parent = node; |
| } |
| return node; |
| } |
| |
| @override |
| TreeNode visitFunctionDeclaration( |
| FunctionDeclaration node, TreeNode? removalSentinel) { |
| if (enableConstFunctions) { |
| // ignore: unnecessary_null_comparison |
| if (node.function != null) { |
| node.function = transform(node.function)..parent = node; |
| } |
| constantEvaluator.env.addVariableValue( |
| node.variable, new FunctionValue(node.function, null)); |
| } else { |
| return super.visitFunctionDeclaration(node, removalSentinel); |
| } |
| return node; |
| } |
| |
| @override |
| TreeNode visitVariableDeclaration( |
| VariableDeclaration node, TreeNode? removalSentinel) { |
| transformAnnotations(node.annotations, node); |
| |
| Expression? initializer = node.initializer; |
| if (initializer != null) { |
| if (node.isConst) { |
| final Constant constant = evaluateWithContext(node, initializer); |
| constantEvaluator.env.addVariableValue(node, constant); |
| initializer = node.initializer = |
| makeConstantExpression(constant, initializer)..parent = node; |
| |
| // If this constant is inlined, remove it. |
| if (!keepLocals && shouldInline(initializer)) { |
| if (constant is! UnevaluatedConstant) { |
| // If the constant is unevaluated we need to keep the expression, |
| // so that, in the case the constant contains error but the local |
| // is unused, the error will still be reported. |
| return removalSentinel /*!*/ ?? node; |
| } |
| } |
| } else { |
| node.initializer = transform(initializer)..parent = node; |
| } |
| } |
| return node; |
| } |
| |
| @override |
| TreeNode visitField(Field node, TreeNode? removalSentinel) { |
| StaticTypeContext? oldStaticTypeContext = _staticTypeContext; |
| _staticTypeContext = new StaticTypeContext(node, typeEnvironment); |
| TreeNode result = constantEvaluator.withNewEnvironment(() { |
| Expression? initializer = node.initializer; |
| if (node.isConst) { |
| transformAnnotations(node.annotations, node); |
| initializer = node.initializer = |
| evaluateAndTransformWithContext(node, initializer!)..parent = node; |
| |
| // If this constant is inlined, remove it. |
| if (!keepFields && shouldInline(initializer)) { |
| return removalSentinel!; |
| } |
| } else { |
| transformAnnotations(node.annotations, node); |
| if (initializer != null) { |
| node.initializer = transform(initializer)..parent = node; |
| } |
| } |
| return node; |
| }); |
| _staticTypeContext = oldStaticTypeContext; |
| return result; |
| } |
| |
| // Handle use-sites of constants (and "inline" constant expressions): |
| |
| @override |
| TreeNode visitSymbolLiteral(SymbolLiteral node, TreeNode? removalSentinel) { |
| return makeConstantExpression( |
| constantEvaluator.evaluate(_staticTypeContext!, node), node); |
| } |
| |
| bool _isNull(Expression node) { |
| return node is NullLiteral || |
| node is ConstantExpression && node.constant is NullConstant; |
| } |
| |
| @override |
| TreeNode visitEqualsCall(EqualsCall node, TreeNode? removalSentinel) { |
| Expression left = transform(node.left); |
| Expression right = transform(node.right); |
| if (_isNull(left)) { |
| return new EqualsNull(right)..fileOffset = node.fileOffset; |
| } else if (_isNull(right)) { |
| return new EqualsNull(left)..fileOffset = node.fileOffset; |
| } |
| node.left = left..parent = node; |
| node.right = right..parent = node; |
| return node; |
| } |
| |
| @override |
| TreeNode visitStaticGet(StaticGet node, TreeNode? removalSentinel) { |
| final Member target = node.target; |
| if (target is Field && target.isConst) { |
| // Make sure the initializer is evaluated first. |
| StaticTypeContext? oldStaticTypeContext = _staticTypeContext; |
| _staticTypeContext = new StaticTypeContext(target, typeEnvironment); |
| target.initializer = |
| evaluateAndTransformWithContext(target, target.initializer!) |
| ..parent = target; |
| _staticTypeContext = oldStaticTypeContext; |
| if (shouldInline(target.initializer!)) { |
| return evaluateAndTransformWithContext(node, node); |
| } |
| } else if (target is Procedure && target.kind == ProcedureKind.Method) { |
| return evaluateAndTransformWithContext(node, node); |
| } |
| return super.visitStaticGet(node, removalSentinel); |
| } |
| |
| @override |
| TreeNode visitStaticTearOff(StaticTearOff node, TreeNode? removalSentinel) { |
| final Member target = node.target; |
| if (target is Procedure && target.kind == ProcedureKind.Method) { |
| return evaluateAndTransformWithContext(node, node); |
| } |
| return super.visitStaticTearOff(node, removalSentinel); |
| } |
| |
| @override |
| TreeNode visitInstantiation(Instantiation node, TreeNode? removalSentinel) { |
| Instantiation result = |
| super.visitInstantiation(node, removalSentinel) as Instantiation; |
| if (result.expression is ConstantExpression && |
| result.typeArguments.every(isInstantiated)) { |
| return evaluateAndTransformWithContext(node, result); |
| } |
| return node; |
| } |
| |
| @override |
| TreeNode visitSwitchCase(SwitchCase node, TreeNode? removalSentinel) { |
| transformExpressions(node.expressions, node); |
| return super.visitSwitchCase(node, removalSentinel); |
| } |
| |
| @override |
| TreeNode visitSwitchStatement( |
| SwitchStatement node, TreeNode? removalSentinel) { |
| TreeNode result = super.visitSwitchStatement(node, removalSentinel); |
| Library library = constantEvaluator.libraryOf(node); |
| // ignore: unnecessary_null_comparison |
| if (library != null && 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 || node is PropertyGet; |
| if (nodeCache.containsKey(node) && !(enableConstFunctions && isGetter)) { |
| Constant? cachedResult = nodeCache[node]; |
| if (cachedResult == null) { |
| // [null] is a sentinel value only used when still evaluating the same |
| // node. |
| return createErrorConstant(node, messageConstEvalCircularity); |
| } |
| result = cachedResult; |
| } else { |
| nodeCache[node] = null; |
| Constant evaluatedResult = node.accept(this); |
| if (evaluatedResult is AbortConstant) { |
| nodeCache.remove(node); |
| return evaluatedResult; |
| } else { |
| nodeCache[node] = evaluatedResult; |
| } |
| result = evaluatedResult; |
| } |
| } else { |
| bool sentinelInserted = false; |
| if (nodeCache.containsKey(node)) { |
| bool isRecursiveFunctionCall = node is MethodInvocation || |
| node is InstanceInvocation || |
| node is FunctionInvocation || |
| node is LocalFunctionInvocation || |
| node is StaticInvocation; |
| if (nodeCache[node] == null && |
| !(enableConstFunctions && isRecursiveFunctionCall)) { |
| // recursive call |
| return createErrorConstant(node, messageConstEvalCircularity); |
| } |
| // else we've seen the node before and come to a result -> we won't |
| // go into an infinite loop here either. |
| } else { |
| // We haven't seen this node before. Risk of loop. |
| nodeCache[node] = null; |
| sentinelInserted = true; |
| } |
| Constant evaluatedResult = node.accept(this); |
| if (sentinelInserted) { |
| nodeCache.remove(node); |
| } |
| if (evaluatedResult is AbortConstant) { |
| return evaluatedResult; |
| } |
| result = evaluatedResult; |
| } |
| seenUnevaluatedChild = wasUnevaluated || result is UnevaluatedConstant; |
| return result; |
| } |
| |
| Constant _evaluateNullableSubexpression(Expression? node) { |
| if (node == null) return nullConstant; |
| return _evaluateSubexpression(node); |
| } |
| |
| @override |
| Constant defaultExpression(Expression node) { |
| // Only a subset of the expression language is valid for constant |
| // evaluation. |
| return createInvalidExpressionConstant( |
| node, 'Constant evaluation has no support for ${node.runtimeType}!'); |
| } |
| |
| @override |
| Constant visitFileUriExpression(FileUriExpression node) { |
| return _evaluateSubexpression(node.expression); |
| } |
| |
| @override |
| Constant visitNullLiteral(NullLiteral node) => nullConstant; |
| |
| @override |
| Constant visitBoolLiteral(BoolLiteral node) { |
| return makeBoolConstant(node.value); |
| } |
| |
| @override |
| Constant visitIntLiteral(IntLiteral node) { |
| // The frontend ensures that integer literals are valid according to the |
| // target representation. |
| return canonicalize(intFolder.makeIntConstant(node.value, unsigned: true)); |
| } |
| |
| @override |
| Constant visitDoubleLiteral(DoubleLiteral node) { |
| return canonicalize(new DoubleConstant(node.value)); |
| } |
| |
| @override |
| Constant visitStringLiteral(StringLiteral node) { |
| return canonicalize(new StringConstant(node.value)); |
| } |
| |
| @override |
| Constant visitTypeLiteral(TypeLiteral node) { |
| DartType? type = _evaluateDartType(node, node.type); |
| if (type != null) { |
| type = convertType(type); |
| } |
| if (type == null) { |
| AbortConstant error = _gotError!; |
| _gotError = null; |
| return error; |
| } |
| assert(_gotError == null); |
| // ignore: unnecessary_null_comparison |
| assert(type != null); |
| return canonicalize(new TypeLiteralConstant(type)); |
| } |
| |
| @override |
| Constant visitConstantExpression(ConstantExpression node) { |
| Constant constant = node.constant; |
| Constant result = constant; |
| if (constant is UnevaluatedConstant) { |
| if (environmentDefines != null) { |
| result = _evaluateSubexpression(constant.expression); |
| if (result is AbortConstant) return result; |
| } else { |
| // Still no environment. Doing anything is just wasted time. |
| result = constant; |
| } |
| } |
| // If there were already constants in the AST then we make sure we |
| // re-canonicalize them. After running the transformer we will therefore |
| // have a fully-canonicalized constant DAG with roots coming from the |
| // [ConstantExpression] nodes in the AST. |
| return canonicalize(result); |
| } |
| |
| @override |
| Constant visitListLiteral(ListLiteral node) { |
| if (!node.isConst && !enableConstFunctions) { |
| return createInvalidExpressionConstant(node, "Non-constant list literal"); |
| } |
| final ListConstantBuilder builder = new ListConstantBuilder( |
| node, convertType(node.typeArgument), this, |
| isMutable: !node.isConst); |
| // These expressions are at the same level, so one of them being |
| // unevaluated doesn't mean a sibling is or has an unevaluated child. |
| // We therefore reset it before each call, combine it and set it correctly |
| // at the end. |
| bool wasOrBecameUnevaluated = seenUnevaluatedChild; |
| for (Expression element in node.expressions) { |
| seenUnevaluatedChild = false; |
| AbortConstant? error = builder.add(element); |
| wasOrBecameUnevaluated |= seenUnevaluatedChild; |
| if (error != null) return error; |
| } |
| seenUnevaluatedChild = wasOrBecameUnevaluated; |
| return builder.build(); |
| } |
| |
| @override |
| Constant visitListConcatenation(ListConcatenation node) { |
| final ListConstantBuilder builder = |
| new ListConstantBuilder(node, convertType(node.typeArgument), this); |
| for (Expression list in node.lists) { |
| AbortConstant? error = builder.addSpread(list); |
| if (error != null) return error; |
| } |
| return builder.build(); |
| } |
| |
| @override |
| Constant visitSetLiteral(SetLiteral node) { |
| if (!node.isConst) { |
| return createInvalidExpressionConstant(node, "Non-constant set literal"); |
| } |
| final SetConstantBuilder builder = |
| new SetConstantBuilder(node, convertType(node.typeArgument), this); |
| // These expressions are at the same level, so one of them being |
| // unevaluated doesn't mean a sibling is or has an unevaluated child. |
| // We therefore reset it before each call, combine it and set it correctly |
| // at the end. |
| bool wasOrBecameUnevaluated = seenUnevaluatedChild; |
| for (Expression element in node.expressions) { |
| seenUnevaluatedChild = false; |
| AbortConstant? error = builder.add(element); |
| wasOrBecameUnevaluated |= seenUnevaluatedChild; |
| if (error != null) return error; |
| } |
| seenUnevaluatedChild = wasOrBecameUnevaluated; |
| return builder.build(); |
| } |
| |
| @override |
| Constant visitSetConcatenation(SetConcatenation node) { |
| final SetConstantBuilder builder = |
| new SetConstantBuilder(node, convertType(node.typeArgument), this); |
| for (Expression set_ in node.sets) { |
| AbortConstant? error = builder.addSpread(set_); |
| if (error != null) return error; |
| } |
| return builder.build(); |
| } |
| |
| @override |
| Constant visitMapLiteral(MapLiteral node) { |
| if (!node.isConst) { |
| return createInvalidExpressionConstant(node, "Non-constant map literal"); |
| } |
| final MapConstantBuilder builder = new MapConstantBuilder( |
| node, convertType(node.keyType), convertType(node.valueType), this); |
| // These expressions are at the same level, so one of them being |
| // unevaluated doesn't mean a sibling is or has an unevaluated child. |
| // We therefore reset it before each call, combine it and set it correctly |
| // at the end. |
| bool wasOrBecameUnevaluated = seenUnevaluatedChild; |
| for (MapLiteralEntry element in node.entries) { |
| seenUnevaluatedChild = false; |
| AbortConstant? error = builder.add(element); |
| wasOrBecameUnevaluated |= seenUnevaluatedChild; |
| if (error != null) return error; |
| } |
| seenUnevaluatedChild = wasOrBecameUnevaluated; |
| return builder.build(); |
| } |
| |
| @override |
| Constant visitMapConcatenation(MapConcatenation node) { |
| final MapConstantBuilder builder = new MapConstantBuilder( |
| node, convertType(node.keyType), convertType(node.valueType), this); |
| for (Expression map in node.maps) { |
| AbortConstant? error = builder.addSpread(map); |
| if (error != null) return error; |
| } |
| return builder.build(); |
| } |
| |
| @override |
| Constant visitFunctionExpression(FunctionExpression node) { |
| if (enableConstFunctions) { |
| return new FunctionValue(node.function, env); |
| } |
| return createInvalidExpressionConstant(node, "Function literal"); |
| } |
| |
| @override |
| Constant visitConstructorInvocation(ConstructorInvocation node) { |
| if (!node.isConst && !enableConstFunctions) { |
| return createInvalidExpressionConstant( |
| node, 'Non-constant constructor invocation "$node".'); |
| } |
| |
| final Constructor constructor = node.target; |
| AbortConstant? error = checkConstructorConst(node, constructor); |
| if (error != null) return error; |
| |
| final Class klass = constructor.enclosingClass; |
| if (klass.isAbstract) { |
| // Probably unreachable. |
| return createInvalidExpressionConstant( |
| node, 'Constructor "$node" belongs to abstract class "${klass}".'); |
| } |
| |
| final List<Constant>? positionals = |
| _evaluatePositionalArguments(node.arguments); |
| if (positionals == null) { |
| AbortConstant error = _gotError!; |
| _gotError = null; |
| return error; |
| } |
| assert(_gotError == null); |
| // ignore: unnecessary_null_comparison |
| assert(positionals != null); |
| |
| final Map<String, Constant>? named = |
| _evaluateNamedArguments(node.arguments); |
| if (named == null) { |
| AbortConstant error = _gotError!; |
| _gotError = null; |
| return error; |
| } |
| assert(_gotError == null); |
| // ignore: unnecessary_null_comparison |
| assert(named != null); |
| |
| bool isSymbol = klass == coreTypes.internalSymbolClass; |
| if (isSymbol && shouldBeUnevaluated) { |
| return unevaluated( |
| node, |
| new ConstructorInvocation(constructor, |
| unevaluatedArguments(positionals, named, node.arguments.types), |
| isConst: true)); |
| } |
| |
| // Special case the dart:core's Symbol class here and convert it to a |
| // [SymbolConstant]. For invalid values we report a compile-time error. |
| if (isSymbol) { |
| final Constant nameValue = positionals.single; |
| |
| // 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}".'); |
| } |
| return null; |
| } |
| |
| @override |
| Constant visitInstanceCreation(InstanceCreation node) { |
| return withNewInstanceBuilder( |
| node.classNode, convertTypes(node.typeArguments), () { |
| for (AssertStatement statement in node.asserts) { |
| AbortConstant? error = checkAssert(statement); |
| if (error != null) return error; |
| } |
| AbortConstant? error; |
| for (MapEntry<Reference, Expression> entry in node.fieldValues.entries) { |
| Reference fieldRef = entry.key; |
| Expression value = entry.value; |
| Constant constant = _evaluateSubexpression(value); |
| if (constant is AbortConstant) { |
| error = constant; |
| break; |
| } |
| instanceBuilder!.setFieldValue(fieldRef.asField, constant); |
| } |
| if (error != null) return error; |
| for (Expression value in node.unusedArguments) { |
| if (error != null) return error; |
| Constant constant = _evaluateSubexpression(value); |
| if (constant is AbortConstant) { |
| error ??= constant; |
| return error; |
| } |
| if (constant is UnevaluatedConstant) { |
| instanceBuilder!.unusedArguments.add(extract(constant)); |
| } |
| } |
| if (error != null) return error; |
| if (shouldBeUnevaluated) { |
| return unevaluated(node, instanceBuilder!.buildUnevaluatedInstance()); |
| } |
| // We can get here when re-evaluating a previously unevaluated constant. |
| return canonicalize(instanceBuilder!.buildInstance()); |
| }); |
| } |
| |
| bool isValidSymbolName(String name) { |
| // See https://api.dartlang.org/stable/2.0.0/dart-core/Symbol/Symbol.html: |
| // |
| // A qualified name is a valid name preceded by a public identifier name |
| // and a '.', e.g., foo.bar.baz= is a qualified version of baz=. |
| // |
| // That means that the content of the name String must be either |
| // - a valid public Dart identifier (that is, an identifier not |
| // starting with "_"), |
| // - such an identifier followed by "=" (a setter name), |
| // - the name of a declarable operator, |
| // - any of the above preceded by any number of qualifiers, where a |
| // qualifier is a non-private identifier followed by '.', |
| // - or the empty string (the default name of a library with no library |
| // name declaration). |
| |
| const Set<String> operatorNames = const <String>{ |
| '+', |
| '-', |
| '*', |
| '/', |
| '%', |
| '~/', |
| '&', |
| '|', |
| '^', |
| '~', |
| '<<', |
| '>>', |
| '>>>', |
| '<', |
| '<=', |
| '>', |
| '>=', |
| '==', |
| '[]', |
| '[]=', |
| 'unary-' |
| }; |
| |
| // ignore: unnecessary_null_comparison |
| if (name == null) return false; |
| if (name == '') return true; |
| |
| final List<String> parts = name.split('.'); |
| |
| // Each qualifier must be a public identifier. |
| for (int i = 0; i < parts.length - 1; ++i) { |
| if (!isValidPublicIdentifier(parts[i])) return false; |
| } |
| |
| String last = parts.last; |
| if (operatorNames.contains(last)) { |
| return enableTripleShift || last != '>>>'; |
| } |
| if (last.endsWith('=')) { |
| last = last.substring(0, last.length - 1); |
| } |
| if (!isValidPublicIdentifier(last)) return false; |
| |
| return true; |
| } |
| |
| /// From the Dart Language specification: |
| /// |
| /// IDENTIFIER: |
| /// IDENTIFIER_START IDENTIFIER_PART* |
| /// |
| /// IDENTIFIER_START: |
| /// IDENTIFIER_START_NO_DOLLAR | ‘$’ |
| /// |
| /// IDENTIFIER_PART: |
| /// IDENTIFIER_START | DIGIT |
| /// |
| /// IDENTIFIER_NO_DOLLAR: |
| /// IDENTIFIER_START_NO_DOLLAR IDENTIFIER_PART_NO_DOLLAR* |
| /// |
| /// IDENTIFIER_START_NO_DOLLAR: |
| /// LETTER | '_' |
| /// |
| /// IDENTIFIER_PART_NO_DOLLAR: |
| /// IDENTIFIER_START_NO_DOLLAR | DIGIT |
| /// |
| static final RegExp publicIdentifierRegExp = |
| new RegExp(r'^[a-zA-Z$][a-zA-Z0-9_$]*$'); |
| |
| static const Set<String> nonUsableKeywords = const <String>{ |
| 'assert', |
| 'break', |
| 'case', |
| 'catch', |
| 'class', |
| 'const', |
| 'continue', |
| 'default', |
| 'do', |
| 'else', |
| 'enum', |
| 'extends', |
| 'false', |
| 'final', |
| 'finally', |
| 'for', |
| 'if', |
| 'in', |
| 'is', |
| 'new', |
| 'null', |
| 'rethrow', |
| 'return', |
| 'super', |
| 'switch', |
| 'this', |
| 'throw', |
| 'true', |
| 'try', |
| 'var', |
| 'while', |
| 'with', |
| }; |
| |
| bool isValidPublicIdentifier(String name) { |
| return publicIdentifierRegExp.hasMatch(name) && |
| !nonUsableKeywords.contains(name); |
| } |
| |
| /// Returns [null] on success and an error-"constant" on failure, as such the |
| /// return value should be checked. |
| AbortConstant? handleConstructorInvocation( |
| Constructor constructor, |
| List<DartType> typeArguments, |
| List<Constant> positionalArguments, |
| Map<String, Constant> namedArguments, |
| TreeNode caller) { |
| return withNewEnvironment(() { |
| final Class klass = constructor.enclosingClass; |
| final FunctionNode function = constructor.function; |
| |
| // Mark in file of the caller that we evaluate this specific constructor. |
| _recordConstructorCoverage(constructor, caller); |
| |
| // We simulate now the constructor invocation. |
| |
| // Step 1) Map type arguments and normal arguments from caller to |
| // callee. |
| for (int i = 0; i < klass.typeParameters.length; i++) { |
| env.addTypeParameterValue(klass.typeParameters[i], typeArguments[i]); |
| } |
| for (int i = 0; i < function.positionalParameters.length; i++) { |
| final VariableDeclaration parameter = function.positionalParameters[i]; |
| final Constant value = (i < positionalArguments.length) |
| ? positionalArguments[i] |
| // TODO(johnniwinther): This should call [_evaluateSubexpression]. |
| : _evaluateNullableSubexpression(parameter.initializer); |
| if (value is AbortConstant) return value; |
| env.addVariableValue(parameter, value); |
| } |
| for (final VariableDeclaration parameter in function.namedParameters) { |
| final Constant value = namedArguments[parameter.name] ?? |
| // TODO(johnniwinther): This should call [_evaluateSubexpression]. |
| _evaluateNullableSubexpression(parameter.initializer); |
| if (value is AbortConstant) return value; |
| env.addVariableValue(parameter, value); |
| } |
| |
| // Step 2) Run all initializers (including super calls) with environment |
| // setup. |
| for (final Field field in klass.fields) { |
| if (!field.isStatic) { |
| Constant constant = _evaluateNullableSubexpression(field.initializer); |
| if (constant is AbortConstant) return constant; |
| instanceBuilder!.setFieldValue(field, constant); |
| } |
| } |
| for (final Initializer init in constructor.initializers) { |
| if (init is FieldInitializer) { |
| Constant constant = _evaluateSubexpression(init.value); |
| if (constant is AbortConstant) return constant; |
| instanceBuilder!.setFieldValue(init.field, constant); |
| } else if (init is LocalInitializer) { |
| final VariableDeclaration variable = init.variable; |
| Constant constant = _evaluateSubexpression(variable.initializer!); |
| if (constant is AbortConstant) return constant; |
| env.addVariableValue(variable, constant); |
| } else if (init is SuperInitializer) { |
| AbortConstant? error = checkConstructorConst(init, constructor); |
| if (error != null) return error; |
| List<DartType>? types = _evaluateSuperTypeArguments( |
| init, constructor.enclosingClass.supertype!); |
| if (types == null) { |
| AbortConstant error = _gotError!; |
| _gotError = null; |
| return error; |
| } |
| assert(_gotError == null); |
| // ignore: unnecessary_null_comparison |
| assert(types != null); |
| |
| List<Constant>? positionalArguments = |
| _evaluatePositionalArguments(init.arguments); |
| if (positionalArguments == null) { |
| AbortConstant error = _gotError!; |
| _gotError = null; |
| return error; |
| } |
| assert(_gotError == null); |
| // ignore: unnecessary_null_comparison |
| assert(positionalArguments != null); |
| Map<String, Constant>? namedArguments = |
| _evaluateNamedArguments(init.arguments); |
| if (namedArguments == null) { |
| AbortConstant error = _gotError!; |
| _gotError = null; |
| return error; |
| } |
| assert(_gotError == null); |
| // ignore: unnecessary_null_comparison |
| assert(namedArguments != null); |
| error = handleConstructorInvocation(init.target, types, |
| positionalArguments, namedArguments, constructor); |
| if (error != null) return error; |
| } else if (init is RedirectingInitializer) { |
| // Since a redirecting constructor targets a constructor of the same |
| // class, we pass the same [typeArguments]. |
| AbortConstant? error = checkConstructorConst(init, constructor); |
| if (error != null) return error; |
| List<Constant>? positionalArguments = |
| _evaluatePositionalArguments(init.arguments); |
| if (positionalArguments == null) { |
| AbortConstant error = _gotError!; |
| _gotError = null; |
| return error; |
| } |
| assert(_gotError == null); |
| // ignore: unnecessary_null_comparison |
| assert(positionalArguments != null); |
| |
| Map<String, Constant>? namedArguments = |
| _evaluateNamedArguments(init.arguments); |
| if (namedArguments == null) { |
| AbortConstant error = _gotError!; |
| _gotError = null; |
| return error; |
| } |
| assert(_gotError == null); |
| // ignore: unnecessary_null_comparison |
| assert(namedArguments != null); |
| |
| error = handleConstructorInvocation(init.target, typeArguments, |
| positionalArguments, namedArguments, constructor); |
| if (error != null) return error; |
| } else if (init is AssertInitializer) { |
| AbortConstant? error = checkAssert(init.statement); |
| if (error != null) return error; |
| } else { |
| // InvalidInitializer or new Initializers. |
| // Probably unreachable. InvalidInitializer is (currently) only |
| // created for classes with no constructors that doesn't have a |
| // super that takes no arguments. It thus cannot be const. |
| // Explicit constructors with incorrect super calls will get a |
| // ShadowInvalidInitializer which is actually a LocalInitializer. |
| return createInvalidExpressionConstant( |
| constructor, |
| 'No support for handling initializer of type ' |
| '"${init.runtimeType}".'); |
| } |
| } |
| |
| for (UnevaluatedConstant constant in env.unevaluatedUnreadConstants) { |
| instanceBuilder!.unusedArguments.add(extract(constant)); |
| } |
| |
| // ignore: unnecessary_null_comparison |
| if (enableConstFunctions && constructor.function != null) { |
| AbortConstant? error = executeConstructorBody(constructor); |
| if (error != null) return error; |
| } |
| |
| return null; |
| }); |
| } |
| |
| /// Returns [null] on success and an error-"constant" on failure, as such the |
| /// return value should be checked. |
| AbortConstant? checkAssert(AssertStatement statement) { |
| final Constant condition = _evaluateSubexpression(statement.condition); |
| if (condition is AbortConstant) return condition; |
| |
| if (shouldBeUnevaluated) { |
| Expression? message = null; |
| if (statement.message != null) { |
| enterLazy(); |
| Constant constant = _evaluateSubexpression(statement.message!); |
| if (constant is AbortConstant) return constant; |
| message = extract(constant); |
| leaveLazy(); |
| } |
| instanceBuilder!.asserts.add(new AssertStatement(extract(condition), |
| message: message, |
| conditionStartOffset: statement.conditionStartOffset, |
| conditionEndOffset: statement.conditionEndOffset)); |
| } else if (condition is BoolConstant) { |
| if (!condition.value) { |
| if (statement.message == null) { |
| return createErrorConstant( |
| statement.condition, messageConstEvalFailedAssertion); |
| } |
| final Constant message = _evaluateSubexpression(statement.message!); |
| if (message is AbortConstant) return message; |
| if (shouldBeUnevaluated) { |
| instanceBuilder!.asserts.add(new AssertStatement(extract(condition), |
| message: extract(message), |
| conditionStartOffset: statement.conditionStartOffset, |
| conditionEndOffset: statement.conditionEndOffset)); |
| } else if (message is StringConstant) { |
| return createErrorConstant( |
| statement.condition, |
| templateConstEvalFailedAssertionWithMessage |
| .withArguments(message.value)); |
| } else { |
| return createErrorConstant( |
| statement.message!, |
| templateConstEvalInvalidType.withArguments( |
| message, |
| typeEnvironment.coreTypes.stringLegacyRawType, |
| message.getType(_staticTypeContext!), |
| isNonNullableByDefault)); |
| } |
| } |
| } else { |
| return createErrorConstant( |
| statement.condition, |
| templateConstEvalInvalidType.withArguments( |
| condition, |
| typeEnvironment.coreTypes.boolLegacyRawType, |
| condition.getType(_staticTypeContext!), |
| isNonNullableByDefault)); |
| } |
| |
| return null; |
| } |
| |
| @override |
| Constant visitInvalidExpression(InvalidExpression node) { |
| return createInvalidExpressionConstant(node, node.message ?? ''); |
| } |
| |
| @override |
| Constant visitDynamicInvocation(DynamicInvocation node) { |
| // We have no support for generic method invocation at the moment. |
| if (node.arguments.types.isNotEmpty) { |
| return createInvalidExpressionConstant(node, "generic method invocation"); |
| } |
| |
| // We have no support for method invocation with named arguments at the |
| // moment. |
| if (node.arguments.named.isNotEmpty) { |
| return createInvalidExpressionConstant( |
| node, "method invocation with named arguments"); |
| } |
| |
| final Constant receiver = _evaluateSubexpression(node.receiver); |
| if (receiver is AbortConstant) return receiver; |
| final List<Constant>? positionalArguments = |
| _evaluatePositionalArguments(node.arguments); |
| |
| if (positionalArguments == null) { |
| AbortConstant error = _gotError!; |
| _gotError = null; |
| return error; |
| } |
| assert(_gotError == null); |
| // ignore: unnecessary_null_comparison |
| assert(positionalArguments != null); |
| |
| if (shouldBeUnevaluated) { |
| return unevaluated( |
| node, |
| new DynamicInvocation( |
| node.kind, |
| extract(receiver), |
| node.name, |
| unevaluatedArguments( |
| positionalArguments, {}, node.arguments.types)) |
| ..fileOffset = node.fileOffset); |
| } |
| |
| return _handleInvocation(node, node.name, receiver, positionalArguments, |
| arguments: node.arguments); |
| } |
| |
| @override |
| Constant visitInstanceInvocation(InstanceInvocation node) { |
| // We have no support for generic method invocation at the moment. |
| if (node.arguments.types.isNotEmpty) { |
| return createInvalidExpressionConstant(node, "generic method invocation"); |
| } |
| |
| // We have no support for method invocation with named arguments at the |
| // moment. |
| if (node.arguments.named.isNotEmpty) { |
| return createInvalidExpressionConstant( |
| node, "method invocation with named arguments"); |
| } |
| |
| final Constant receiver = _evaluateSubexpression(node.receiver); |
| if (receiver is AbortConstant) return receiver; |
| final List<Constant>? positionalArguments = |
| _evaluatePositionalArguments(node.arguments); |
| |
| if (positionalArguments == null) { |
| AbortConstant error = _gotError!; |
| _gotError = null; |
| return error; |
| } |
| assert(_gotError == null); |
| // ignore: unnecessary_null_comparison |
| assert(positionalArguments != null); |
| |
| if (shouldBeUnevaluated) { |
| return unevaluated( |
| node, |
| new InstanceInvocation( |
| node.kind, |
| extract(receiver), |
| node.name, |
| unevaluatedArguments( |
| positionalArguments, {}, node.arguments.types), |
| functionType: node.functionType, |
| interfaceTarget: node.interfaceTarget) |
| ..fileOffset = node.fileOffset |
| ..flags = node.flags); |
| } |
| |
| return _handleInvocation(node, node.name, receiver, positionalArguments, |
| arguments: node.arguments); |
| } |
| |
| @override |
| Constant visitFunctionInvocation(FunctionInvocation node) { |
| if (!enableConstFunctions) { |
| return createInvalidExpressionConstant(node, "function invocation"); |
| } |
| |
| final Constant receiver = _evaluateSubexpression(node.receiver); |
| if (receiver is AbortConstant) return receiver; |
| |
| return _evaluateFunctionInvocation(node, receiver, node.arguments); |
| } |
| |
| @override |
| Constant visitLocalFunctionInvocation(LocalFunctionInvocation node) { |
| if (!enableConstFunctions) { |
| return createInvalidExpressionConstant(node, "local function invocation"); |
| } |
| |
| final Constant receiver = env.lookupVariable(node.variable)!; |
| // ignore: unnecessary_null_comparison |
| assert(receiver != null); |
| if (receiver is AbortConstant) return receiver; |
| |
| return _evaluateFunctionInvocation(node, receiver, node.arguments); |
| } |
| |
| Constant _evaluateFunctionInvocation( |
| TreeNode node, Constant receiver, Arguments argumentsNode) { |
| final List<Constant>? arguments = |
| _evaluatePositionalArguments(argumentsNode); |
| |
| if (arguments == null) { |
| AbortConstant error = _gotError!; |
| _gotError = null; |
| return error; |
| } |
| assert(_gotError == null); |
| // ignore: unnecessary_null_comparison |
| assert(arguments != null); |
| |
| // Evaluate type arguments of the function invoked. |
| List<DartType>? types = _evaluateTypeArguments(node, argumentsNode); |
| if (types == null) { |
| AbortConstant error = _gotError!; |
| _gotError = null; |
| return error; |
| } |
| assert(_gotError == null); |
| // ignore: unnecessary_null_comparison |
| assert(types != null); |
| |
| // Evaluate named arguments of the function invoked. |
| final Map<String, Constant>? named = _evaluateNamedArguments(argumentsNode); |
| if (named == null) { |
| AbortConstant error = _gotError!; |
| _gotError = null; |
| return error; |
| } |
| assert(_gotError == null); |
| // ignore: unnecessary_null_comparison |
| assert(named != null); |
| |
| if (receiver is FunctionValue) { |
| return _handleFunctionInvocation( |
| receiver.function, types, arguments, named, |
| functionEnvironment: receiver.environment); |
| } else { |
| return createInvalidExpressionConstant( |
| node, "function invocation with invalid receiver"); |
| } |
| } |
| |
| @override |
| Constant visitEqualsCall(EqualsCall node) { |
| final Constant left = _evaluateSubexpression(node.left); |
| if (left is AbortConstant) return left; |
| final Constant right = _evaluateSubexpression(node.right); |
| if (right is AbortConstant) return right; |
| |
| if (shouldBeUnevaluated) { |
| return unevaluated( |
| node, |
| new EqualsCall(extract(left), extract(right), |
| functionType: node.functionType, |
| interfaceTarget: node.interfaceTarget) |
| ..fileOffset = node.fileOffset); |
| } |
| |
| return _handleEquals(node, left, right); |
| } |
| |
| @override |
| Constant visitEqualsNull(EqualsNull node) { |
| final Constant expression = _evaluateSubexpression(node.expression); |
| if (expression is AbortConstant) return expression; |
| |
| if (shouldBeUnevaluated) { |
| return unevaluated(node, |
| new EqualsNull(extract(expression))..fileOffset = node.fileOffset); |
| } |
| |
| return _handleEquals(node, expression, nullConstant); |
| } |
| |
| Constant _handleEquals(Expression node, Constant left, Constant right) { |
| if (left is NullConstant || |
| left is BoolConstant || |
| left is IntConstant || |
| left is DoubleConstant || |
| left is StringConstant || |
| right is NullConstant) { |
| // [DoubleConstant] uses [identical] to determine equality, so we need |
| // to take the special cases into account. |
| return doubleSpecialCases(left, right) ?? makeBoolConstant(left == right); |
| } else { |
| return createErrorConstant( |
| node, |
| templateConstEvalInvalidEqualsOperandType.withArguments( |
| left, left.getType(_staticTypeContext!), isNonNullableByDefault)); |
| } |
| } |
| |
| Constant _handleInvocation(Expression node, Name name, Constant receiver, |
| List<Constant> positionalArguments, |
| {required Arguments arguments}) { |
| final String op = name.text; |
| |
| // TODO(kallentu): Handle all constant toString methods. |
| if (receiver is PrimitiveConstant && |
| op == 'toString' && |
| enableConstFunctions) { |
| return new StringConstant(receiver.value.toString()); |
| } |
| |
| // Handle == and != first (it's common between all types). Since `a != b` is |
| // parsed as `!(a == b)` it is handled implicitly through ==. |
| if (positionalArguments.length == 1 && op == '==') { |
| final Constant right = positionalArguments[0]; |
| return _handleEquals(node, receiver, right); |
| } |
| |
| // This is a white-listed set of methods we need to support on constants. |
| if (receiver is StringConstant) { |
| if (positionalArguments.length == 1) { |
| final Constant other = positionalArguments[0]; |
| switch (op) { |
| case '+': |
| if (other is StringConstant) { |
| return canonicalize( |
| new StringConstant(receiver.value + other.value)); |
| } |
| return createErrorConstant( |
| node, |
| templateConstEvalInvalidBinaryOperandType.withArguments( |
| '+', |
| receiver, |
| typeEnvironment.coreTypes.stringLegacyRawType, |
| other.getType(_staticTypeContext!), |
| isNonNullableByDefault)); |
| case '[]': |
| if (enableConstFunctions) { |
| int? index = intFolder.asInt(other); |
| if (index != null) { |
| if (index < 0 || index >= receiver.value.length) { |
| return new _AbortDueToThrowConstant( |
| node, new RangeError.index(index, receiver.value)); |
| } |
| return canonicalize(new StringConstant(receiver.value[index])); |
| } |
| return createErrorConstant( |
| node, |
| templateConstEvalInvalidBinaryOperandType.withArguments( |
| '[]', |
| receiver, |
| typeEnvironment.coreTypes.intNonNullableRawType, |
| other.getType(_staticTypeContext!), |
| isNonNullableByDefault)); |
| } |
| } |
| } |
| } else if (intFolder.isInt(receiver)) { |
| if (positionalArguments.length == 0) { |
| return canonicalize(intFolder.foldUnaryOperator(node, op, receiver)); |
| } else if (positionalArguments.length == 1) { |
| final Constant other = positionalArguments[0]; |
| if (intFolder.isInt(other)) { |
| return canonicalize( |
| intFolder.foldBinaryOperator(node, op, receiver, other)); |
| } else if (other is DoubleConstant) { |
| if ((op == '|' || op == '&' || op == '^') || |
| (op == '<<' || op == '>>' || op == '>>>')) { |
| return createErrorConstant( |
| node, |
| templateConstEvalInvalidBinaryOperandType.withArguments( |
| op, |
| other, |
| typeEnvironment.coreTypes.intLegacyRawType, |
| other.getType(_staticTypeContext!), |
| isNonNullableByDefault)); |
| } |
| num receiverValue = (receiver as PrimitiveConstant<num>).value; |
| return canonicalize(evaluateBinaryNumericOperation( |
| op, receiverValue, other.value, node)); |
| } |
| return createErrorConstant( |
| node, |
| templateConstEvalInvalidBinaryOperandType.withArguments( |
| op, |
| receiver, |
| typeEnvironment.coreTypes.numLegacyRawType, |
| other.getType(_staticTypeContext!), |
| isNonNullableByDefault)); |
| } |
| } else if (receiver is DoubleConstant) { |
| if ((op == '|' || op == '&' || op == '^') || |
| (op == '<<' || op == '>>' || op == '>>>')) { |
| return createErrorConstant( |
| node, |
| templateConstEvalInvalidBinaryOperandType.withArguments( |
| op, |
| receiver, |
| typeEnvironment.coreTypes.intLegacyRawType, |
| receiver.getType(_staticTypeContext!), |
| isNonNullableByDefault)); |
| } |
| if (positionalArguments.length == 0) { |
| switch (op) { |
| case 'unary-': |
| return canonicalize(new DoubleConstant(-receiver.value)); |
| } |
| } else if (positionalArguments.length == 1) { |
| final Constant other = positionalArguments[0]; |
| |
| if (other is IntConstant || other is DoubleConstant) { |
| final num value = (other as PrimitiveConstant<num>).value; |
| return canonicalize( |
| evaluateBinaryNumericOperation(op, receiver.value, value, node)); |
| } |
| return createErrorConstant( |
| node, |
| templateConstEvalInvalidBinaryOperandType.withArguments( |
| op, |
| receiver, |
| typeEnvironment.coreTypes.numLegacyRawType, |
| other.getType(_staticTypeContext!), |
| isNonNullableByDefault)); |
| } |
| } else if (receiver is BoolConstant) { |
| if (positionalArguments.length == 1) { |
| final Constant other = positionalArguments[0]; |
| if (other is BoolConstant) { |
| switch (op) { |
| case '|': |
| return canonicalize( |
| new BoolConstant(receiver.value || other.value)); |
| case '&': |
| return canonicalize( |
| new BoolConstant(receiver.value && other.value)); |
| case '^': |
| return canonicalize( |
| new BoolConstant(receiver.value != other.value)); |
| } |
| } |
| } |
| } else if (receiver is NullConstant) { |
| return createErrorConstant(node, messageConstEvalNullValue); |
| } else if (receiver is ListConstant && enableConstFunctions) { |
| if (positionalArguments.length == 1) { |
| final Constant other = positionalArguments[0]; |
| switch (op) { |
| case '[]': |
| int? index = intFolder.asInt(other); |
| if (index != null) { |
| if (index < 0 || index >= receiver.entries.length) { |
| return new _AbortDueToThrowConstant( |
| node, new RangeError.index(index, receiver.entries)); |
| } |
| return receiver.entries[index]; |
| } |
| return createErrorConstant( |
| node, |
| templateConstEvalInvalidBinaryOperandType.withArguments( |
| '[]', |
| receiver, |
| typeEnvironment.coreTypes.intNonNullableRawType, |
| other.getType(_staticTypeContext!), |
| isNonNullableByDefault)); |
| case 'add': |
| if (receiver is MutableListConstant) { |
| receiver.entries.add(other); |
| return receiver; |
| } |
| return new _AbortDueToThrowConstant(node, new UnsupportedError(op)); |
| } |
| } |
| } else if (receiver is MapConstant && enableConstFunctions) { |
| if (positionalArguments.length == 1) { |
| final Constant other = positionalArguments[0]; |
| switch (op) { |
| case '[]': |
| for (ConstantMapEntry entry in receiver.entries) { |
| if (entry.key == other) { |
| return entry.value; |
| } |
| } |
| return new NullConstant(); |
| } |
| } |
| } else if (enableConstFunctions) { |
| // Evaluate type arguments of the method invoked. |
| List<DartType>? typeArguments = _evaluateTypeArguments(node, arguments); |
| if (typeArguments == null) { |
| AbortConstant error = _gotError!; |
| _gotError = null; |
| return error; |
| } |
| assert(_gotError == null); |
| // ignore: unnecessary_null_comparison |
| assert(typeArguments != null); |
| |
| // Evaluate named arguments of the method invoked. |
| final Map<String, Constant>? namedArguments = |
| _evaluateNamedArguments(arguments); |
| if (namedArguments == null) { |
| AbortConstant error = _gotError!; |
| _gotError = null; |
| return error; |
| } |
| assert(_gotError == null); |
| // ignore: unnecessary_null_comparison |
| assert(namedArguments != null); |
| |
| if (receiver is FunctionValue && name == Name.callName) { |
| return _handleFunctionInvocation(receiver.function, typeArguments, |
| positionalArguments, namedArguments, |
| functionEnvironment: receiver.environment); |
| } else if (receiver is InstanceConstant) { |
| final Class instanceClass = receiver.classNode; |
| assert(typeEnvironment.hierarchy is ClassHierarchy); |
| final Member member = (typeEnvironment.hierarchy as ClassHierarchy) |
| .getDispatchTarget(instanceClass, name)!; |
| final FunctionNode? function = member.function; |
| |
| // TODO(kallentu): Implement [Object] class methods which have backend |
| // specific functions that cannot be run by the constant evaluator. |
| final bool isObjectMember = member.enclosingClass != null && |
| member.enclosingClass!.name == "Object"; |
| if (function != null && !isObjectMember) { |
| // TODO(johnniwinther): Make [typeArguments] and [namedArguments] |
| // required and non-nullable. |
| return withNewInstanceBuilder(instanceClass, typeArguments, () { |
| final EvaluationEnvironment newEnv = new EvaluationEnvironment(); |
| for (int i = 0; i < instanceClass.typeParameters.length; i++) { |
| newEnv.addTypeParameterValue( |
| instanceClass.typeParameters[i], receiver.typeArguments[i]); |
| } |
| |
| // Ensure that fields are visible for instance access. |
| receiver.fieldValues.forEach((Reference fieldRef, Constant value) => |
| instanceBuilder!.setFieldValue(fieldRef.asField, value)); |
| return _handleFunctionInvocation(function, receiver.typeArguments, |
| positionalArguments, namedArguments, |
| functionEnvironment: newEnv); |
| }); |
| } |
| |
| switch (op) { |
| case 'toString': |
| // Default value for toString() of instances. |
| return new StringConstant( |
| "Instance of '${receiver.classReference.toStringInternal()}'"); |
| } |
| } |
| } |
| |
| return createErrorConstant( |
| node, |
| templateConstEvalInvalidMethodInvocation.withArguments( |
| op, receiver, isNonNullableByDefault)); |
| } |
| |
| @override |
| Constant visitMethodInvocation(MethodInvocation node) { |
| // We have no support for generic method invocation at the moment. |
| if (node.arguments.types.isNotEmpty && !enableConstFunctions) { |
| return createInvalidExpressionConstant(node, "generic method invocation"); |
| } |
| |
| // We have no support for method invocation with named arguments at the |
| // moment. |
| if (node.arguments.named.isNotEmpty && !enableConstFunctions) { |
| return createInvalidExpressionConstant( |
| node, "method invocation with named arguments"); |
| } |
| |
| final Constant receiver = _evaluateSubexpression(node.receiver); |
| if (receiver is AbortConstant) return receiver; |
| |
| final List<Constant>? positionalArguments = |
| _evaluatePositionalArguments(node.arguments); |
| |
| if (positionalArguments == null) { |
| AbortConstant error = _gotError!; |
| _gotError = null; |
| return error; |
| } |
| assert(_gotError == null); |
| // ignore: unnecessary_null_comparison |
| assert(positionalArguments != null); |
| |
| if (shouldBeUnevaluated) { |
| return unevaluated( |
| node, |
| new MethodInvocation( |
| extract(receiver), |
| node.name, |
| unevaluatedArguments( |
| positionalArguments, {}, node.arguments.types), |
| node.interfaceTarget) |
| ..fileOffset = node.fileOffset |
| ..flags = node.flags); |
| } |
| |
| return _handleInvocation(node, node.name, receiver, positionalArguments, |
| arguments: node.arguments); |
| } |
| |
| @override |
| Constant visitLogicalExpression(LogicalExpression node) { |
| final Constant left = _evaluateSubexpression(node.left); |
| if (left is AbortConstant) return left; |
| if (shouldBeUnevaluated) { |
| enterLazy(); |
| Constant right = _evaluateSubexpression(node.right); |
| if (right is AbortConstant) return right; |
| leaveLazy(); |
| return unevaluated( |
| node, |
| new LogicalExpression( |
| extract(left), node.operatorEnum, extract(right))); |
| } |
| switch (node.operatorEnum) { |
| case LogicalExpressionOperator.OR: |
| if (left is BoolConstant) { |
| if (left.value) return trueConstant; |
| |
| final Constant right = _evaluateSubexpression(node.right); |
| if (right is AbortConstant) return right; |
| if (right is BoolConstant || right is UnevaluatedConstant) { |
| return right; |
| } |
| |
| return createErrorConstant( |
| node, |
| templateConstEvalInvalidBinaryOperandType.withArguments( |
| logicalExpressionOperatorToString(node.operatorEnum), |
| left, |
| typeEnvironment.coreTypes.boolLegacyRawType, |
| right.getType(_staticTypeContext!), |
| isNonNullableByDefault)); |
| } |
| return createErrorConstant( |
| node, |
| templateConstEvalInvalidMethodInvocation.withArguments( |
| logicalExpressionOperatorToString(node.operatorEnum), |
| left, |
| isNonNullableByDefault)); |
| case LogicalExpressionOperator.AND: |
| if (left is BoolConstant) { |
| if (!left.value) return falseConstant; |
| |
| final Constant right = _evaluateSubexpression(node.right); |
| if (right is AbortConstant) return right; |
| if (right is BoolConstant || right is UnevaluatedConstant) { |
| return right; |
| } |
| |
| return createErrorConstant( |
| node, |
| templateConstEvalInvalidBinaryOperandType.withArguments( |
| logicalExpressionOperatorToString(node.operatorEnum), |
| left, |
| typeEnvironment.coreTypes.boolLegacyRawType, |
| right.getType(_staticTypeContext!), |
| isNonNullableByDefault)); |
| } |
| return createErrorConstant( |
| node, |
| templateConstEvalInvalidMethodInvocation.withArguments( |
| logicalExpressionOperatorToString(node.operatorEnum), |
| left, |
| isNonNullableByDefault)); |
| default: |
| // Probably unreachable. |
| return createErrorConstant( |
| node, |
| templateConstEvalInvalidMethodInvocation.withArguments( |
| logicalExpressionOperatorToString(node.operatorEnum), |
| left, |
| isNonNullableByDefault)); |
| } |
| } |
| |
| @override |
| Constant visitConditionalExpression(ConditionalExpression node) { |
| final Constant condition = _evaluateSubexpression(node.condition); |
| if (condition is AbortConstant) return condition; |
| if (condition == trueConstant) { |
| return _evaluateSubexpression(node.then); |
| } else if (condition == falseConstant) { |
| return _evaluateSubexpression(node.otherwise); |
| } else if (shouldBeUnevaluated) { |
| enterLazy(); |
| Constant then = _evaluateSubexpression(node.then); |
| if (then is AbortConstant) return then; |
| Constant otherwise = _evaluateSubexpression(node.otherwise); |
| if (otherwise is AbortConstant) return otherwise; |
| leaveLazy(); |
| return unevaluated( |
| node, |
| new ConditionalExpression(extract(condition), extract(then), |
| extract(otherwise), node.staticType)); |
| } else { |
| return createErrorConstant( |
| node.condition, |
| templateConstEvalInvalidType.withArguments( |
| condition, |
| typeEnvironment.coreTypes.boolLegacyRawType, |
| condition.getType(_staticTypeContext!), |
| isNonNullableByDefault)); |
| } |
| } |
| |
| @override |
| Constant visitInstanceGet(InstanceGet node) { |
| if (node.receiver is ThisExpression) { |
| // Probably unreachable unless trying to evaluate non-const stuff as |
| // const. |
| // Access "this" during instance creation. |
| if (instanceBuilder == null) { |
| return createErrorConstant(node, messageNotAConstantExpression); |
| } |
| |
| for (final MapEntry<Field, Constant> entry |
| in instanceBuilder!.fields.entries) { |
| final Field field = entry.key; |
| if (field.name == node.name) { |
| return entry.value; |
| } |
| } |
| |
| // Meant as a "stable backstop for situations where Fasta fails to |
| // rewrite various erroneous constructs into invalid expressions". |
| // Probably unreachable. |
| return createInvalidExpressionConstant(node, |
| 'Could not evaluate field get ${node.name} on incomplete instance'); |
| } |
| |
| final Constant receiver = _evaluateSubexpression(node.receiver); |
| if (receiver is AbortConstant) return receiver; |
| if (receiver is StringConstant && node.name.text == 'length') { |
| return canonicalize(intFolder.makeIntConstant(receiver.value.length)); |
| } else if (shouldBeUnevaluated) { |
| return unevaluated( |
| node, |
| new InstanceGet(node.kind, extract(receiver), node.name, |
| resultType: node.resultType, |
| interfaceTarget: node.interfaceTarget)); |
| } else if (receiver is NullConstant) { |
| return createErrorConstant(node, messageConstEvalNullValue); |
| } else if (receiver is ListConstant && enableConstFunctions) { |
| switch (node.name.text) { |
| case 'first': |
| if (receiver.entries.isEmpty) { |
| return new _AbortDueToThrowConstant( |
| node, new StateError('No element')); |
| } |
| return receiver.entries.first; |
| case 'isEmpty': |
| return new BoolConstant(receiver.entries.isEmpty); |
| case 'isNotEmpty': |
| return new BoolConstant(receiver.entries.isNotEmpty); |
| // TODO(kallentu): case 'iterator' |
| case 'last': |
| if (receiver.entries.isEmpty) { |
| return new _AbortDueToThrowConstant( |
| node, new StateError('No element')); |
| } |
| return receiver.entries.last; |
| case 'length': |
| return new IntConstant(receiver.entries.length); |
| // TODO(kallentu): case 'reversed' |
| case 'single': |
| if (receiver.entries.isEmpty) { |
| return new _AbortDueToThrowConstant( |
| node, new StateError('No element')); |
| } else if (receiver.entries.length > 1) { |
| return new _AbortDueToThrowConstant( |
| node, new StateError('Too many elements')); |
| } |
| return receiver.entries.single; |
| } |
| } else if (receiver is InstanceConstant && enableConstFunctions) { |
| for (final MapEntry<Reference, Constant> entry |
| in receiver.fieldValues.entries) { |
| final Field field = entry.key.asField; |
| if (field.name == node.name) { |
| return entry.value; |
| } |
| } |
| } |
| return createErrorConstant( |
| node, |
| templateConstEvalInvalidPropertyGet.withArguments( |
| node.name.text, receiver, isNonNullableByDefault)); |
| } |
| |
| @override |
| Constant visitDynamicGet(DynamicGet node) { |
| final Constant receiver = _evaluateSubexpression(node.receiver); |
| if (receiver is AbortConstant) return receiver; |
| if (receiver is StringConstant && node.name.text == 'length') { |
| return canonicalize(intFolder.makeIntConstant(receiver.value.length)); |
| } else if (shouldBeUnevaluated) { |
| return unevaluated( |
| node, new DynamicGet(node.kind, extract(receiver), node.name)); |
| } else if (receiver is NullConstant) { |
| return createErrorConstant(node, messageConstEvalNullValue); |
| } |
| return createErrorConstant( |
| node, |
| templateConstEvalInvalidPropertyGet.withArguments( |
| node.name.text, receiver, isNonNullableByDefault)); |
| } |
| |
| @override |
| Constant visitInstanceTearOff(InstanceTearOff node) { |
| final Constant receiver = _evaluateSubexpression(node.receiver); |
| if (receiver is AbortConstant) return receiver; |
| return createErrorConstant( |
| node, |
| templateConstEvalInvalidPropertyGet.withArguments( |
| node.name.text, receiver, isNonNullableByDefault)); |
| } |
| |
| @override |
| Constant visitFunctionTearOff(FunctionTearOff node) { |
| final Constant receiver = _evaluateSubexpression(node.receiver); |
| if (receiver is AbortConstant) return receiver; |
| return createErrorConstant( |
| node, |
| templateConstEvalInvalidPropertyGet.withArguments( |
| Name.callName.text, receiver, isNonNullableByDefault)); |
| } |
| |
| @override |
| Constant visitPropertyGet(PropertyGet node) { |
| if (node.receiver is ThisExpression) { |
| // Probably unreachable unless trying to evaluate non-const stuff as |
| // const. |
| // Access "this" during instance creation. |
| if (instanceBuilder == null) { |
| return createErrorConstant(node, messageNotAConstantExpression); |
| } |
| |
| for (final MapEntry<Field, Constant> entry |
| in instanceBuilder!.fields.entries) { |
| final Field field = entry.key; |
| if (field.name == node.name) { |
| return entry.value; |
| } |
| } |
| |
| // Meant as a "stable backstop for situations where Fasta fails to |
| // rewrite various erroneous constructs into invalid expressions". |
| // Probably unreachable. |
| return createInvalidExpressionConstant(node, |
| 'Could not evaluate field get ${node.name} on incomplete instance'); |
| } |
| |
| final Constant receiver = _evaluateSubexpression(node.receiver); |
| if (receiver is AbortConstant) return receiver; |
| if (receiver is StringConstant && node.name.text == 'length') { |
| return canonicalize(intFolder.makeIntConstant(receiver.value.length)); |
| } else if (shouldBeUnevaluated) { |
| return unevaluated(node, |
| new PropertyGet(extract(receiver), node.name, node.interfaceTarget)); |
| } else if (receiver is NullConstant) { |
| return createErrorConstant(node, messageConstEvalNullValue); |
| } else if (receiver is ListConstant && enableConstFunctions) { |
| switch (node.name.text) { |
| case 'first': |
| if (receiver.entries.isEmpty) { |
| return new _AbortDueToThrowConstant( |
| node, new StateError('No element')); |
| } |
| return receiver.entries.first; |
| case 'isEmpty': |
| return new BoolConstant(receiver.entries.isEmpty); |
| case 'isNotEmpty': |
| return new BoolConstant(receiver.entries.isNotEmpty); |
| // TODO(kallentu): case 'iterator' |
| case 'last': |
| if (receiver.entries.isEmpty) { |
| return new _AbortDueToThrowConstant( |
| node, new StateError('No element')); |
| } |
| return receiver.entries.last; |
| case 'length': |
| return new IntConstant(receiver.entries.length); |
| // TODO(kallentu): case 'reversed' |
| case 'single': |
| if (receiver.entries.isEmpty) { |
| return new _AbortDueToThrowConstant( |
| node, new StateError('No element')); |
| } else if (receiver.entries.length > 1) { |
| return new _AbortDueToThrowConstant( |
| node, new StateError('Too many elements')); |
| } |
| return receiver.entries.single; |
| } |
| } else if (receiver is InstanceConstant && enableConstFunctions) { |
| for (final MapEntry<Reference, Constant> entry |
| in receiver.fieldValues.entries) { |
| final Field field = entry.key.asField; |
| if (field.name == node.name) { |
| return entry.value; |
| } |
| } |
| } |
| return createErrorConstant( |
| node, |
| templateConstEvalInvalidPropertyGet.withArguments( |
| node.name.text, receiver, isNonNullableByDefault)); |
| } |
| |
| @override |
| Constant visitLet(Let node) { |
| Constant value = _evaluateSubexpression(node.variable.initializer!); |
| if (value is AbortConstant) return value; |
| env.addVariableValue(node.variable, value); |
| return _evaluateSubexpression(node.body); |
| } |
| |
| @override |
| Constant visitVariableGet(VariableGet node) { |
| // Not every variable which a [VariableGet] refers to must be marked as |
| // constant. For example function parameters as well as constructs |
| // desugared to [Let] expressions are ok. |
| // |
| // TODO(kustermann): The heuristic of allowing all [VariableGet]s on [Let] |
| // variables might allow more than it should. |
| final VariableDeclaration variable = node.variable; |
| if (enableConstFunctions) { |
| return env.lookupVariable(variable) ?? |
| createErrorConstant( |
| node, |
| templateConstEvalGetterNotFound |
| .withArguments(variable.name ?? '')); |
| } else { |
| if (variable.parent is Let || _isFormalParameter(variable)) { |
| return env.lookupVariable(node.variable) ?? |
| createErrorConstant( |
| node, |
| templateConstEvalNonConstantVariableGet |
| .withArguments(variable.name ?? '')); |
| } |
| if (variable.isConst) { |
| return _evaluateSubexpression(variable.initializer!); |
| } |
| } |
| return createInvalidExpressionConstant( |
| node, 'Variable get of a non-const variable.'); |
| } |
| |
| @override |
| Constant visitVariableSet(VariableSet node) { |
| if (enableConstFunctions) { |
| final VariableDeclaration variable = node.variable; |
| Constant value = _evaluateSubexpression(node.value); |
| if (value is AbortConstant) return value; |
| return env.updateVariableValue(variable, value) ?? |
| createInvalidExpressionConstant( |
| node, 'Variable set of an unknown value.'); |
| } |
| return defaultExpression(node); |
| } |
| |
| /// Computes the constant for [expression] de
|