| // 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:core' hide MapEntry; |
| |
| 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/legacy_erasure.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, |
| templateConstEvalInvalidType, |
| templateConstEvalInvalidBinaryOperandType, |
| templateConstEvalInvalidEqualsOperandType, |
| templateConstEvalInvalidMethodInvocation, |
| templateConstEvalInvalidPropertyGet, |
| templateConstEvalInvalidStaticInvocation, |
| templateConstEvalInvalidStringInterpolationOperand, |
| templateConstEvalInvalidSymbolName, |
| templateConstEvalKeyImplementsEqual, |
| templateConstEvalNonConstantVariableGet, |
| 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, |
| {bool evaluateAnnotations, |
| bool desugarSets, |
| bool enableTripleShift, |
| bool errorOnUnevaluatedConstant, |
| CoreTypes coreTypes, |
| ClassHierarchy hierarchy}) { |
| assert(evaluateAnnotations != null); |
| assert(desugarSets != null); |
| assert(enableTripleShift != null); |
| assert(errorOnUnevaluatedConstant != null); |
| coreTypes ??= new CoreTypes(component); |
| hierarchy ??= new ClassHierarchy(component, coreTypes); |
| |
| final TypeEnvironment typeEnvironment = |
| new TypeEnvironment(coreTypes, hierarchy); |
| |
| transformLibraries(component.libraries, backend, environmentDefines, |
| typeEnvironment, errorReporter, evaluationMode, |
| desugarSets: desugarSets, |
| enableTripleShift: enableTripleShift, |
| errorOnUnevaluatedConstant: errorOnUnevaluatedConstant, |
| evaluateAnnotations: evaluateAnnotations); |
| return component; |
| } |
| |
| void transformLibraries( |
| List<Library> libraries, |
| ConstantsBackend backend, |
| Map<String, String> environmentDefines, |
| TypeEnvironment typeEnvironment, |
| ErrorReporter errorReporter, |
| EvaluationMode evaluationMode, |
| {bool evaluateAnnotations, |
| bool desugarSets, |
| bool enableTripleShift, |
| bool errorOnUnevaluatedConstant}) { |
| assert(evaluateAnnotations != null); |
| assert(desugarSets != null); |
| assert(enableTripleShift != null); |
| assert(errorOnUnevaluatedConstant != null); |
| final ConstantsTransformer constantsTransformer = new ConstantsTransformer( |
| backend, |
| environmentDefines, |
| evaluateAnnotations, |
| desugarSets, |
| enableTripleShift, |
| errorOnUnevaluatedConstant, |
| typeEnvironment, |
| errorReporter, |
| evaluationMode); |
| for (final Library library in libraries) { |
| constantsTransformer.convertLibrary(library); |
| } |
| } |
| |
| void transformProcedure( |
| Procedure procedure, |
| ConstantsBackend backend, |
| Map<String, String> environmentDefines, |
| TypeEnvironment typeEnvironment, |
| ErrorReporter errorReporter, |
| EvaluationMode evaluationMode, |
| {bool evaluateAnnotations: true, |
| bool desugarSets: false, |
| bool enableTripleShift: false, |
| bool errorOnUnevaluatedConstant: false}) { |
| assert(evaluateAnnotations != null); |
| assert(desugarSets != null); |
| assert(enableTripleShift != null); |
| assert(errorOnUnevaluatedConstant != null); |
| final ConstantsTransformer constantsTransformer = new ConstantsTransformer( |
| backend, |
| environmentDefines, |
| evaluateAnnotations, |
| desugarSets, |
| enableTripleShift, |
| errorOnUnevaluatedConstant, |
| typeEnvironment, |
| errorReporter, |
| evaluationMode); |
| constantsTransformer.visitProcedure(procedure); |
| } |
| |
| enum EvaluationMode { |
| weak, |
| agnostic, |
| strong, |
| } |
| |
| class ConstantWeakener extends ComputeOnceConstantVisitor<Constant> { |
| ConstantEvaluator _evaluator; |
| |
| ConstantWeakener(this._evaluator); |
| |
| CoreTypes get _coreTypes => _evaluator.coreTypes; |
| |
| 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 = rawLegacyErasure(_coreTypes, node.keyType); |
| DartType valueType = rawLegacyErasure(_coreTypes, node.valueType); |
| 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 = rawLegacyErasure(_coreTypes, node.typeArgument); |
| 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 = rawLegacyErasure(_coreTypes, node.typeArgument); |
| 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 = |
| rawLegacyErasure(_coreTypes, node.typeArguments[index]); |
| if (typeArgument != null) { |
| typeArguments ??= node.typeArguments.toList(growable: false); |
| typeArguments[index] = typeArgument; |
| } |
| } |
| Map<Reference, Constant> fieldValues; |
| for (Reference reference in node.fieldValues.keys) { |
| Constant value = visitConstant(node.fieldValues[reference]); |
| if (value != null) { |
| fieldValues ??= new Map<Reference, Constant>.from(node.fieldValues); |
| fieldValues[reference] = value; |
| } |
| } |
| if (typeArguments != null || fieldValues != null) { |
| return new InstanceConstant(node.classReference, |
| typeArguments ?? node.typeArguments, fieldValues ?? node.fieldValues); |
| } |
| return null; |
| } |
| |
| @override |
| Constant visitPartialInstantiationConstant( |
| PartialInstantiationConstant node) { |
| List<DartType> types; |
| for (int index = 0; index < node.types.length; index++) { |
| DartType type = rawLegacyErasure(_coreTypes, node.types[index]); |
| if (type != null) { |
| types ??= node.types.toList(growable: false); |
| types[index] = type; |
| } |
| } |
| if (types != null) { |
| return new PartialInstantiationConstant(node.tearOffConstant, types); |
| } |
| return null; |
| } |
| |
| @override |
| Constant visitTearOffConstant(TearOffConstant node) => null; |
| |
| @override |
| Constant visitTypeLiteralConstant(TypeLiteralConstant node) { |
| DartType type = rawLegacyErasure(_coreTypes, node.type); |
| if (type != null) { |
| return new TypeLiteralConstant(type); |
| } |
| return null; |
| } |
| |
| @override |
| Constant visitUnevaluatedConstant(UnevaluatedConstant node) => null; |
| } |
| |
| class ConstantsTransformer extends Transformer { |
| final ConstantsBackend backend; |
| final ConstantEvaluator constantEvaluator; |
| final TypeEnvironment typeEnvironment; |
| StaticTypeContext _staticTypeContext; |
| |
| final bool evaluateAnnotations; |
| final bool desugarSets; |
| final bool enableTripleShift; |
| final bool errorOnUnevaluatedConstant; |
| |
| ConstantsTransformer( |
| this.backend, |
| Map<String, String> environmentDefines, |
| this.evaluateAnnotations, |
| this.desugarSets, |
| this.enableTripleShift, |
| this.errorOnUnevaluatedConstant, |
| this.typeEnvironment, |
| ErrorReporter errorReporter, |
| EvaluationMode evaluationMode) |
| : constantEvaluator = new ConstantEvaluator( |
| backend, environmentDefines, typeEnvironment, errorReporter, |
| desugarSets: desugarSets, |
| enableTripleShift: enableTripleShift, |
| 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); |
| |
| transformList(library.dependencies, this, library); |
| transformList(library.parts, this, library); |
| transformList(library.typedefs, this, library); |
| transformList(library.classes, this, library); |
| transformList(library.procedures, this, library); |
| transformList(library.fields, this, 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) { |
| constantEvaluator.withNewEnvironment(() { |
| transformAnnotations(node.annotations, node); |
| }); |
| return node; |
| } |
| |
| @override |
| LibraryDependency visitLibraryDependency(LibraryDependency node) { |
| constantEvaluator.withNewEnvironment(() { |
| transformAnnotations(node.annotations, node); |
| }); |
| return node; |
| } |
| |
| @override |
| Class visitClass(Class node) { |
| StaticTypeContext oldStaticTypeContext = _staticTypeContext; |
| _staticTypeContext = new StaticTypeContext.forAnnotations( |
| node.enclosingLibrary, typeEnvironment); |
| constantEvaluator.withNewEnvironment(() { |
| transformAnnotations(node.annotations, node); |
| transformList(node.fields, this, node); |
| transformList(node.typeParameters, this, node); |
| transformList(node.constructors, this, node); |
| transformList(node.procedures, this, node); |
| transformList(node.redirectingFactoryConstructors, this, node); |
| }); |
| _staticTypeContext = oldStaticTypeContext; |
| return node; |
| } |
| |
| @override |
| Procedure visitProcedure(Procedure node) { |
| StaticTypeContext oldStaticTypeContext = _staticTypeContext; |
| _staticTypeContext = new StaticTypeContext(node, typeEnvironment); |
| constantEvaluator.withNewEnvironment(() { |
| transformAnnotations(node.annotations, node); |
| node.function = node.function.accept<TreeNode>(this)..parent = node; |
| }); |
| _staticTypeContext = oldStaticTypeContext; |
| return node; |
| } |
| |
| @override |
| Constructor visitConstructor(Constructor node) { |
| StaticTypeContext oldStaticTypeContext = _staticTypeContext; |
| _staticTypeContext = new StaticTypeContext(node, typeEnvironment); |
| constantEvaluator.withNewEnvironment(() { |
| transformAnnotations(node.annotations, node); |
| transformList(node.initializers, this, node); |
| node.function = node.function.accept<TreeNode>(this)..parent = node; |
| }); |
| _staticTypeContext = oldStaticTypeContext; |
| return node; |
| } |
| |
| @override |
| Typedef visitTypedef(Typedef node) { |
| constantEvaluator.withNewEnvironment(() { |
| transformAnnotations(node.annotations, node); |
| transformList(node.typeParameters, this, node); |
| transformList(node.typeParametersOfFunctionType, this, node); |
| transformList(node.positionalParameters, this, node); |
| transformList(node.namedParameters, this, node); |
| }); |
| return node; |
| } |
| |
| @override |
| RedirectingFactoryConstructor visitRedirectingFactoryConstructor( |
| RedirectingFactoryConstructor node) { |
| // Currently unreachable as the compiler doesn't produce |
| // RedirectingFactoryConstructor. |
| StaticTypeContext oldStaticTypeContext = _staticTypeContext; |
| _staticTypeContext = new StaticTypeContext(node, typeEnvironment); |
| constantEvaluator.withNewEnvironment(() { |
| transformAnnotations(node.annotations, node); |
| transformList(node.typeParameters, this, node); |
| transformList(node.positionalParameters, this, node); |
| transformList(node.namedParameters, this, node); |
| }); |
| _staticTypeContext = oldStaticTypeContext; |
| return node; |
| } |
| |
| @override |
| TypeParameter visitTypeParameter(TypeParameter node) { |
| 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) { |
| transformList(node.typeParameters, this, node); |
| final int positionalParameterCount = node.positionalParameters.length; |
| for (int i = 0; i < positionalParameterCount; ++i) { |
| final VariableDeclaration variable = node.positionalParameters[i]; |
| transformAnnotations(variable.annotations, variable); |
| if (variable.initializer != null) { |
| variable.initializer = |
| evaluateAndTransformWithContext(variable, variable.initializer) |
| ..parent = variable; |
| } |
| } |
| for (final VariableDeclaration variable in node.namedParameters) { |
| transformAnnotations(variable.annotations, variable); |
| if (variable.initializer != null) { |
| variable.initializer = |
| evaluateAndTransformWithContext(variable, variable.initializer) |
| ..parent = variable; |
| } |
| } |
| if (node.body != null) { |
| node.body = node.body.accept<TreeNode>(this)..parent = node; |
| } |
| return node; |
| } |
| |
| @override |
| VariableDeclaration visitVariableDeclaration(VariableDeclaration node) { |
| transformAnnotations(node.annotations, node); |
| |
| if (node.initializer != null) { |
| if (node.isConst) { |
| final Constant constant = evaluateWithContext(node, node.initializer); |
| constantEvaluator.env.addVariableValue(node, constant); |
| node.initializer = makeConstantExpression(constant, node.initializer) |
| ..parent = node; |
| |
| // If this constant is inlined, remove it. |
| if (!keepLocals && shouldInline(node.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 null; |
| } |
| } |
| } else { |
| node.initializer = node.initializer.accept<TreeNode>(this) |
| ..parent = node; |
| } |
| } |
| return node; |
| } |
| |
| @override |
| Field visitField(Field node) { |
| StaticTypeContext oldStaticTypeContext = _staticTypeContext; |
| _staticTypeContext = new StaticTypeContext(node, typeEnvironment); |
| Field field = constantEvaluator.withNewEnvironment(() { |
| if (node.isConst) { |
| transformAnnotations(node.annotations, node); |
| node.initializer = |
| evaluateAndTransformWithContext(node, node.initializer) |
| ..parent = node; |
| |
| // If this constant is inlined, remove it. |
| if (!keepFields && shouldInline(node.initializer)) { |
| return null; |
| } |
| } else { |
| transformAnnotations(node.annotations, node); |
| if (node.initializer != null) { |
| node.initializer = node.initializer.accept<TreeNode>(this) |
| ..parent = node; |
| } |
| } |
| return node; |
| }); |
| _staticTypeContext = oldStaticTypeContext; |
| return field; |
| } |
| |
| // Handle use-sites of constants (and "inline" constant expressions): |
| |
| @override |
| Expression visitSymbolLiteral(SymbolLiteral node) { |
| return makeConstantExpression( |
| constantEvaluator.evaluate(_staticTypeContext, node), node); |
| } |
| |
| @override |
| Expression visitStaticGet(StaticGet node) { |
| 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); |
| } |
| |
| @override |
| SwitchCase visitSwitchCase(SwitchCase node) { |
| transformExpressions(node.expressions, node); |
| return super.visitSwitchCase(node); |
| } |
| |
| @override |
| SwitchStatement visitSwitchStatement(SwitchStatement node) { |
| SwitchStatement result = super.visitSwitchStatement(node); |
| Library library = constantEvaluator.libraryOf(node); |
| 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)) { |
| Uri uri = constantEvaluator.getFileUri(caseExpression); |
| int offset = constantEvaluator.getFileOffset(uri, caseExpression); |
| constantEvaluator.errorReporter.report( |
| templateConstEvalCaseImplementsEqual |
| .withArguments(caseExpression.constant, |
| constantEvaluator.isNonNullableByDefault) |
| .withLocation(uri, offset, noLength), |
| null); |
| } |
| } else { |
| // If caseExpression is not ConstantExpression, an error is reported |
| // elsewhere. |
| } |
| } |
| } |
| } |
| return result; |
| } |
| |
| @override |
| Expression visitVariableGet(VariableGet node) { |
| 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); |
| } |
| |
| @override |
| Expression visitListLiteral(ListLiteral node) { |
| if (node.isConst) { |
| return evaluateAndTransformWithContext(node, node); |
| } |
| return super.visitListLiteral(node); |
| } |
| |
| @override |
| Expression visitListConcatenation(ListConcatenation node) { |
| return evaluateAndTransformWithContext(node, node); |
| } |
| |
| @override |
| Expression visitSetLiteral(SetLiteral node) { |
| if (node.isConst) { |
| return evaluateAndTransformWithContext(node, node); |
| } |
| return super.visitSetLiteral(node); |
| } |
| |
| @override |
| Expression visitSetConcatenation(SetConcatenation node) { |
| return evaluateAndTransformWithContext(node, node); |
| } |
| |
| @override |
| Expression visitMapLiteral(MapLiteral node) { |
| if (node.isConst) { |
| return evaluateAndTransformWithContext(node, node); |
| } |
| return super.visitMapLiteral(node); |
| } |
| |
| @override |
| Expression visitMapConcatenation(MapConcatenation node) { |
| return evaluateAndTransformWithContext(node, node); |
| } |
| |
| @override |
| Expression visitConstructorInvocation(ConstructorInvocation node) { |
| if (node.isConst) { |
| return evaluateAndTransformWithContext(node, node); |
| } |
| return super.visitConstructorInvocation(node); |
| } |
| |
| @override |
| Expression visitStaticInvocation(StaticInvocation node) { |
| if (node.isConst) { |
| return evaluateAndTransformWithContext(node, node); |
| } |
| return super.visitStaticInvocation(node); |
| } |
| |
| @override |
| Expression visitConstantExpression(ConstantExpression node) { |
| 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 extends RecursiveVisitor<Constant> { |
| final ConstantsBackend backend; |
| final NumberSemantics numberSemantics; |
| 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 desugarSets; |
| final Field unmodifiableSetMap; |
| |
| final bool enableTripleShift; |
| |
| final bool Function(DartType) isInstantiated = |
| new IsInstantiatedVisitor().isInstantiated; |
| |
| final Map<Constant, Constant> canonicalizationCache; |
| final Map<Node, Object> nodeCache; |
| final CloneVisitorNotMembers cloner = new CloneVisitorNotMembers(); |
| |
| 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; // Any children that were left unevaluated? |
| int lazyDepth; // 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; |
| |
| ConstantWeakener _weakener; |
| |
| ConstantEvaluator(this.backend, this.environmentDefines, this.typeEnvironment, |
| this.errorReporter, |
| {this.desugarSets = false, |
| this.enableTripleShift = false, |
| this.errorOnUnevaluatedConstant = false, |
| this.evaluationMode: EvaluationMode.weak}) |
| : numberSemantics = backend.numberSemantics, |
| coreTypes = typeEnvironment.coreTypes, |
| canonicalizationCache = <Constant, Constant>{}, |
| nodeCache = <Node, Constant>{}, |
| env = new EvaluationEnvironment(), |
| unmodifiableSetMap = desugarSets |
| ? typeEnvironment.coreTypes.index |
| .getMember('dart:collection', '_UnmodifiableSet', '_map') |
| : null { |
| 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.nullClass: 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 type; |
| case EvaluationMode.weak: |
| return legacyErasure(coreTypes, type); |
| } |
| throw new UnsupportedError( |
| "Unexpected evaluation mode: ${evaluationMode}."); |
| } |
| |
| List<DartType> convertTypes(List<DartType> types) { |
| switch (evaluationMode) { |
| case EvaluationMode.strong: |
| case EvaluationMode.agnostic: |
| return types; |
| case EvaluationMode.weak: |
| return types |
| .map((DartType type) => legacyErasure(coreTypes, type)) |
| .toList(); |
| } |
| throw new UnsupportedError( |
| "Unexpected evaluation mode: ${evaluationMode}."); |
| } |
| |
| Uri getFileUri(TreeNode node) { |
| while (node != null && node is! FileUriNode) { |
| node = node.parent; |
| } |
| return (node as FileUriNode)?.fileUri; |
| } |
| |
| 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 Uri uri = getFileUri(result.node); |
| final int fileOffset = getFileOffset(uri, result.node); |
| final LocatedMessage locatedMessageActualError = |
| result.message.withLocation(uri, fileOffset, noLength); |
| |
| final List<LocatedMessage> contextMessages = <LocatedMessage>[ |
| locatedMessageActualError |
| ]; |
| if (result.context != null) contextMessages.addAll(result.context); |
| if (contextNode != null && contextNode != result.node) { |
| final Uri uri = getFileUri(contextNode); |
| final int fileOffset = getFileOffset(uri, contextNode); |
| contextMessages.add( |
| messageConstEvalContext.withLocation(uri, fileOffset, noLength)); |
| } |
| |
| { |
| final Uri uri = getFileUri(node); |
| final int fileOffset = getFileOffset(uri, node); |
| final LocatedMessage locatedMessage = messageConstEvalStartingPoint |
| .withLocation(uri, fileOffset, noLength); |
| 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); |
| } |
| throw "Unexpected error constant"; |
| } |
| if (result is UnevaluatedConstant) { |
| if (errorOnUnevaluatedConstant) { |
| return createErrorConstant(node, messageConstEvalUnevaluated); |
| } |
| return new UnevaluatedConstant( |
| removeRedundantFileUriExpressions(result.expression)); |
| } |
| return result; |
| } |
| |
| /// 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)); |
| } |
| |
| /// 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. |
| if (nodeCache.containsKey(node)) { |
| result = nodeCache[node]; |
| if (result == null) { |
| // [null] is a sentinel value only used when still evaluating the same |
| // node. |
| return createErrorConstant(node, messageConstEvalCircularity); |
| } |
| } else { |
| nodeCache[node] = null; |
| result = node.accept(this); |
| if (result is AbortConstant) { |
| nodeCache.remove(node); |
| return result; |
| } else { |
| nodeCache[node] = result; |
| } |
| } |
| } else { |
| bool sentinelInserted = false; |
| if (nodeCache.containsKey(node)) { |
| if (nodeCache[node] == null) { |
| // 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; |
| } |
| result = node.accept(this); |
| if (sentinelInserted) { |
| nodeCache.remove(node); |
| } |
| if (result is AbortConstant) { |
| return result; |
| } |
| } |
| seenUnevaluatedChild = wasUnevaluated || result is UnevaluatedConstant; |
| return result; |
| } |
| |
| Constant _evaluateNullableSubexpression(Expression node) { |
| if (node == null) return nullConstant; |
| return _evaluateSubexpression(node); |
| } |
| |
| @override |
| Constant defaultTreeNode(Node 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) { |
| final DartType type = _evaluateDartType(node, node.type); |
| if (type == null && _gotError != null) { |
| AbortConstant error = _gotError; |
| _gotError = null; |
| return error; |
| } |
| assert(_gotError == null); |
| assert(type != null); |
| return canonicalize(new TypeLiteralConstant(type)); |
| } |
| |
| @override |
| Constant visitConstantExpression(ConstantExpression node) { |
| Constant constant = node.constant; |
| Constant result = constant; |
| if (constant is UnevaluatedConstant) { |
| result = _evaluateSubexpression(constant.expression); |
| if (result is AbortConstant) return result; |
| } |
| // 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) { |
| return createInvalidExpressionConstant(node, "Non-constant list literal"); |
| } |
| final ListConstantBuilder builder = |
| new ListConstantBuilder(node, convertType(node.typeArgument), this); |
| for (Expression element in node.expressions) { |
| AbortConstant error = builder.add(element); |
| if (error != null) return error; |
| } |
| 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); |
| for (Expression element in node.expressions) { |
| AbortConstant error = builder.add(element); |
| if (error != null) return error; |
| } |
| 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); |
| for (MapEntry element in node.entries) { |
| AbortConstant error = builder.add(element); |
| if (error != null) return error; |
| } |
| 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) { |
| return createInvalidExpressionConstant(node, "Function literal"); |
| } |
| |
| @override |
| Constant visitConstructorInvocation(ConstructorInvocation node) { |
| if (!node.isConst) { |
| 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 && _gotError != null) { |
| AbortConstant error = _gotError; |
| _gotError = null; |
| return error; |
| } |
| assert(_gotError == null); |
| assert(positionals != null); |
| |
| final Map<String, Constant> named = _evaluateNamedArguments(node.arguments); |
| if (named == null && _gotError != null) { |
| AbortConstant error = _gotError; |
| _gotError = null; |
| return error; |
| } |
| assert(_gotError == null); |
| assert(named != null); |
| |
| bool isSymbol = klass == coreTypes.internalSymbolClass; |
| if (isSymbol && shouldBeUnevaluated) { |
| return unevaluated( |
| node, |
| new ConstructorInvocation(constructor, |
| unevaluatedArguments(positionals, named, node.arguments.types), |
| isConst: true)); |
| } |
| |
| // Special case the dart:core's Symbol class here and convert it to a |
| // [SymbolConstant]. For invalid values we report a compile-time error. |
| if (isSymbol) { |
| final Constant nameValue = positionals.single; |
| |
| if (nameValue is StringConstant && isValidSymbolName(nameValue.value)) { |
| return canonicalize(new SymbolConstant(nameValue.value, null)); |
| } |
| return createErrorConstant( |
| node.arguments.positional.first, |
| templateConstEvalInvalidSymbolName.withArguments( |
| nameValue, isNonNullableByDefault)); |
| } |
| |
| List<DartType> types = _evaluateTypeArguments(node, node.arguments); |
| if (types == null && _gotError != null) { |
| AbortConstant error = _gotError; |
| _gotError = null; |
| return error; |
| } |
| assert(_gotError == null); |
| 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); |
| if (error != null) return error; |
| leaveLazy(); |
| return unevaluated(node, instanceBuilder.buildUnevaluatedInstance()); |
| } |
| AbortConstant error = handleConstructorInvocation( |
| constructor, typeArguments, positionals, named); |
| 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) { |
| // 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; |
| node.fieldValues.forEach((Reference fieldRef, Expression value) { |
| if (error != null) return; |
| Constant constant = _evaluateSubexpression(value); |
| if (constant is AbortConstant) { |
| error ??= constant; |
| return; |
| } |
| instanceBuilder.setFieldValue(fieldRef.asField, constant); |
| }); |
| if (error != null) return error; |
| node.unusedArguments.forEach((Expression value) { |
| if (error != null) return; |
| Constant constant = _evaluateSubexpression(value); |
| if (constant is AbortConstant) { |
| error ??= constant; |
| return; |
| } |
| 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 List<String> operatorNames = const <String>[ |
| '+', |
| '-', |
| '*', |
| '/', |
| '%', |
| '~/', |
| '&', |
| '|', |
| '^', |
| '~', |
| '<<', |
| '>>', |
| '>>>', |
| '<', |
| '<=', |
| '>', |
| '>=', |
| '==', |
| '[]', |
| '[]=', |
| 'unary-' |
| ]; |
| |
| 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 List<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) { |
| return withNewEnvironment(() { |
| final Class klass = constructor.enclosingClass; |
| final FunctionNode function = constructor.function; |
| |
| // 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 && _gotError != null) { |
| AbortConstant error = _gotError; |
| _gotError = null; |
| return error; |
| } |
| assert(_gotError == null); |
| assert(types != null); |
| |
| List<Constant> positionalArguments = |
| _evaluatePositionalArguments(init.arguments); |
| if (positionalArguments == null && _gotError != null) { |
| AbortConstant error = _gotError; |
| _gotError = null; |
| return error; |
| } |
| assert(_gotError == null); |
| assert(positionalArguments != null); |
| Map<String, Constant> namedArguments = |
| _evaluateNamedArguments(init.arguments); |
| if (namedArguments == null && _gotError != null) { |
| AbortConstant error = _gotError; |
| _gotError = null; |
| return error; |
| } |
| assert(_gotError == null); |
| assert(namedArguments != null); |
| error = handleConstructorInvocation( |
| init.target, types, positionalArguments, namedArguments); |
| 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 && _gotError != null) { |
| AbortConstant error = _gotError; |
| _gotError = null; |
| return error; |
| } |
| assert(_gotError == null); |
| assert(positionalArguments != null); |
| |
| Map<String, Constant> namedArguments = |
| _evaluateNamedArguments(init.arguments); |
| if (namedArguments == null && _gotError != null) { |
| AbortConstant error = _gotError; |
| _gotError = null; |
| return error; |
| } |
| assert(_gotError == null); |
| assert(namedArguments != null); |
| |
| error = handleConstructorInvocation( |
| init.target, typeArguments, positionalArguments, namedArguments); |
| 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)); |
| } |
| 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 visitMethodInvocation(MethodInvocation node) { |
| // We have no support for generic method invocation atm. |
| if (node.arguments.named.isNotEmpty) { |
| return createInvalidExpressionConstant(node, "generic method invocation"); |
| } |
| |
| final Constant receiver = _evaluateSubexpression(node.receiver); |
| if (receiver is AbortConstant) return receiver; |
| final List<Constant> arguments = |
| _evaluatePositionalArguments(node.arguments); |
| |
| if (arguments == null && _gotError != null) { |
| AbortConstant error = _gotError; |
| _gotError = null; |
| return error; |
| } |
| assert(_gotError == null); |
| assert(arguments != null); |
| |
| if (shouldBeUnevaluated) { |
| return unevaluated( |
| node, |
| new MethodInvocation(extract(receiver), node.name, |
| unevaluatedArguments(arguments, {}, node.arguments.types))); |
| } |
| |
| final String op = node.name.text; |
| |
| // Handle == and != first (it's common between all types). Since `a != b` is |
| // parsed as `!(a == b)` it is handled implicitly through ==. |
| if (arguments.length == 1 && op == '==') { |
| final Constant right = arguments[0]; |
| |
| if (receiver is NullConstant || |
| receiver is BoolConstant || |
| receiver is IntConstant || |
| receiver is DoubleConstant || |
| receiver is StringConstant || |
| right is NullConstant) { |
| // [DoubleConstant] uses [identical] to determine equality, so we need |
| // to take the special cases into account. |
| return doubleSpecialCases(receiver, right) ?? |
| makeBoolConstant(receiver == right); |
| } else { |
| return createErrorConstant( |
| node, |
| templateConstEvalInvalidEqualsOperandType.withArguments(receiver, |
| receiver.getType(_staticTypeContext), isNonNullableByDefault)); |
| } |
| } |
| |
| // This is a white-listed set of methods we need to support on constants. |
| if (receiver is StringConstant) { |
| if (arguments.length == 1) { |
| switch (op) { |
| case '+': |
| final Constant other = arguments[0]; |
| 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)); |
| } |
| } |
| } else if (intFolder.isInt(receiver)) { |
| if (arguments.length == 0) { |
| return canonicalize(intFolder.foldUnaryOperator(node, op, receiver)); |
| } else if (arguments.length == 1) { |
| final Constant other = arguments[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 (arguments.length == 0) { |
| switch (op) { |
| case 'unary-': |
| return canonicalize(new DoubleConstant(-receiver.value)); |
| } |
| } else if (arguments.length == 1) { |
| final Constant other = arguments[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 (arguments.length == 1) { |
| final Constant other = arguments[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); |
| } |
| |
| return createErrorConstant( |
| node, |
| templateConstEvalInvalidMethodInvocation.withArguments( |
| op, receiver, isNonNullableByDefault)); |
| } |
| |
| @override |
| Constant visitLogicalExpression(LogicalExpression node) { |
| final Constant left = _evaluateSubexpression(node.left); |
| if (left is AbortConstant) return left; |
| if (shouldBeUnevaluated) { |
| enterLazy(); |
| Constant right = _evaluateSubexpression(node.right); |
| if (right is AbortConstant) return right; |
| leaveLazy(); |
| return unevaluated( |
| node, |
| new LogicalExpression( |
| extract(left), node.operatorEnum, extract(right))); |
| } |
| switch (node.operatorEnum) { |
| case LogicalExpressionOperator.OR: |
| if (left is BoolConstant) { |
| if (left.value) return trueConstant; |
| |
| final Constant right = _evaluateSubexpression(node.right); |
| if (right is AbortConstant) return right; |
| if (right is BoolConstant || right is UnevaluatedConstant) { |
| return right; |
| } |
| |
| return createErrorConstant( |
| node, |
| templateConstEvalInvalidBinaryOperandType.withArguments( |
| logicalExpressionOperatorToString(node.operatorEnum), |
| left, |
| typeEnvironment.coreTypes.boolLegacyRawType, |
| right.getType(_staticTypeContext), |
| isNonNullableByDefault)); |
| } |
| return createErrorConstant( |
| node, |
| templateConstEvalInvalidMethodInvocation.withArguments( |
| logicalExpressionOperatorToString(node.operatorEnum), |
| left, |
| isNonNullableByDefault)); |
| case LogicalExpressionOperator.AND: |
| if (left is BoolConstant) { |
| if (!left.value) return falseConstant; |
| |
| final Constant right = _evaluateSubexpression(node.right); |
| if (right is AbortConstant) return right; |
| if (right is BoolConstant || right is UnevaluatedConstant) { |
| return right; |
| } |
| |
| return createErrorConstant( |
| node, |
| templateConstEvalInvalidBinaryOperandType.withArguments( |
| logicalExpressionOperatorToString(node.operatorEnum), |
| left, |
| typeEnvironment.coreTypes.boolLegacyRawType, |
| right.getType(_staticTypeContext), |
| isNonNullableByDefault)); |
| } |
| return createErrorConstant( |
| node, |
| templateConstEvalInvalidMethodInvocation.withArguments( |
| logicalExpressionOperatorToString(node.operatorEnum), |
| left, |
| isNonNullableByDefault)); |
| default: |
| // Probably unreachable. |
| return createErrorConstant( |
| node, |
| templateConstEvalInvalidMethodInvocation.withArguments( |
| logicalExpressionOperatorToString(node.operatorEnum), |
| left, |
| isNonNullableByDefault)); |
| } |
| } |
| |
| @override |
| Constant visitConditionalExpression(ConditionalExpression node) { |
| final Constant condition = _evaluateSubexpression(node.condition); |
| if (condition is AbortConstant) return condition; |
| if (condition == trueConstant) { |
| return _evaluateSubexpression(node.then); |
| } else if (condition == falseConstant) { |
| return _evaluateSubexpression(node.otherwise); |
| } else if (shouldBeUnevaluated) { |
| enterLazy(); |
| Constant then = _evaluateSubexpression(node.then); |
| if (then is AbortConstant) return then; |
| Constant otherwise = _evaluateSubexpression(node.otherwise); |
| if (otherwise is AbortConstant) return otherwise; |
| leaveLazy(); |
| return unevaluated( |
| node, |
| new ConditionalExpression(extract(condition), extract(then), |
| extract(otherwise), node.staticType)); |
| } else { |
| return createErrorConstant( |
| node.condition, |
| templateConstEvalInvalidType.withArguments( |
| condition, |
| typeEnvironment.coreTypes.boolLegacyRawType, |
| condition.getType(_staticTypeContext), |
| isNonNullableByDefault)); |
| } |
| } |
| |
| @override |
| Constant 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 Field field in instanceBuilder.fields.keys) { |
| if (field.name == node.name) { |
| return instanceBuilder.fields[field]; |
| } |
| } |
| |
| // 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); |
| } |
| 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 (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.'); |
| } |
| |
| /// Computes the constant for [expression] defined in the context of [member]. |
| /// |
| /// This compute the constant as seen in the current evaluation mode even when |
| /// the constant is defined in a library compiled with the agnostic evaluation |
| /// mode. |
| Constant _evaluateExpressionInContext(Member member, Expression expression) { |
| StaticTypeContext oldStaticTypeContext = _staticTypeContext; |
| _staticTypeContext = new StaticTypeContext(member, typeEnvironment); |
| Constant constant = _evaluateSubexpression(expression); |
| if (constant is! AbortConstant) { |
| if (_staticTypeContext.nonNullableByDefaultCompiledMode == |
| NonNullableByDefaultCompiledMode.Agnostic && |
| evaluationMode == EvaluationMode.weak) { |
| constant = _weakener.visitConstant(constant) ?? constant; |
| } |
| } |
| _staticTypeContext = oldStaticTypeContext; |
| return constant; |
| } |
| |
| @override |
| Constant visitStaticGet(StaticGet node) { |
| return withNewEnvironment(() { |
| final Member target = node.target; |
| if (target is Field) { |
| if (target.isConst) { |
| return _evaluateExpressionInContext(target, target.initializer); |
| } |
| return createErrorConstant( |
| node, |
| templateConstEvalInvalidStaticInvocation |
| .withArguments(target.name.text)); |
| } else if (target is Procedure) { |
| if (target.kind == ProcedureKind.Method) { |
| return canonicalize(new TearOffConstant(target)); |
| } |
| return createErrorConstant( |
| node, |
| templateConstEvalInvalidStaticInvocation |
| .withArguments(target.name.text)); |
| } else { |
| return createInvalidExpressionConstant( |
| node, 'No support for ${target.runtimeType} in a static-get.'); |
| } |
| }); |
| } |
| |
| @override |
| Constant visitStringConcatenation(StringConcatenation node) { |
| final List<Object> concatenated = <Object>[new StringBuffer()]; |
| for (int i = 0; i < node.expressions.length; i++) { |
| Constant constant = _evaluateSubexpression(node.expressions[i]); |
| if (constant is AbortConstant) return constant; |
| if (constant is PrimitiveConstant<Object>) { |
| String value; |
| if (constant is DoubleConstant && intFolder.isInt(constant)) { |
| value = new BigInt.from(constant.value).toString(); |
| } else { |
| value = constant.value.toString(); |
| } |
| Object last = concatenated.last; |
| if (last is StringBuffer) { |
| last.write(value); |
| } else { |
| concatenated.add(new StringBuffer(value)); |
| } |
| } else if (shouldBeUnevaluated) { |
| // The constant is either unevaluated or a non-primitive in an |
| // unevaluated context. In both cases we defer the evaluation and/or |
| // error reporting till later. |
| concatenated.add(constant); |
| } else { |
| return createErrorConstant( |
| node, |
| templateConstEvalInvalidStringInterpolationOperand.withArguments( |
| constant, isNonNullableByDefault)); |
| } |
| } |
| if (concatenated.length > 1) { |
| final List<Expression> expressions = |
| new List<Expression>(concatenated.length); |
| for (int i = 0; i < concatenated.length; i++) { |
| Object value = concatenated[i]; |
| if (value is StringBuffer) { |
| expressions[i] = new ConstantExpression( |
| canonicalize(new StringConstant(value.toString()))); |
| } else { |
| // The value is either unevaluated constant or a non-primitive |
| // constant in an unevaluated expression. |
| expressions[i] = extract(value); |
| } |
| } |
| return unevaluated(node, new StringConcatenation(expressions)); |
| } |
| return canonicalize(new StringConstant(concatenated.single.toString())); |
| } |
| |
| Constant _getFromEnvironmentDefaultValue(Procedure target) { |
| VariableDeclaration variable = target.function.namedParameters |
| .singleWhere((v) => v.name == 'defaultValue'); |
| return variable.initializer != null |
| ? _evaluateExpressionInContext(target, variable.initializer) |
| : |
| // Not reachable unless a defaultValue in fromEnvironment in dart:core |
| // becomes null. |
| nullConstant; |
| } |
| |
| Constant _handleFromEnvironment( |
| Procedure target, StringConstant name, Map<String, Constant> named) { |
| String value = environmentDefines[name.value]; |
| Constant defaultValue = named["defaultValue"]; |
| if (target.enclosingClass == coreTypes.boolClass) { |
| Constant boolConstant; |
| if (value == "true") { |
| boolConstant = trueConstant; |
| } else if (value == "false") { |
| boolConstant = falseConstant; |
| } else if (defaultValue != null) { |
| if (defaultValue is BoolConstant) { |
| boolConstant = makeBoolConstant(defaultValue.value); |
| } else if (defaultValue is NullConstant) { |
| boolConstant = nullConstant; |
| } else { |
| // Probably unreachable. |
| boolConstant = falseConstant; |
| } |
| } else { |
| boolConstant = _getFromEnvironmentDefaultValue(target); |
| } |
| return boolConstant; |
| } else if (target.enclosingClass == coreTypes.intClass) { |
| int intValue = value != null ? int.tryParse(value) : null; |
| Constant intConstant; |
| if (intValue != null) { |
| bool negated = value.startsWith('-'); |
| intConstant = intFolder.makeIntConstant(intValue, unsigned: !negated); |
| } else if (defaultValue != null) { |
| if (intFolder.isInt(defaultValue)) { |
| intConstant = defaultValue; |
| } else { |
| intConstant = nullConstant; |
| } |
| } else { |
| intConstant = _getFromEnvironmentDefaultValue(target); |
| } |
| return canonicalize(intConstant); |
| } else if (target.enclosingClass == coreTypes.stringClass) { |
| Constant stringConstant; |
| if (value != null) { |
| stringConstant = canonicalize(new StringConstant(value)); |
| } else if (defaultValue != null) { |
| if (defaultValue is StringConstant) { |
| stringConstant = defaultValue; |
| } else { |
| stringConstant = nullConstant; |
| } |
| } else { |
| stringConstant = _getFromEnvironmentDefaultValue(target); |
| } |
| return stringConstant; |
| } |
| // Unreachable until fromEnvironment is added to other classes in dart:core |
| // than bool, int and String. |
| throw new UnsupportedError( |
| 'Unexpected fromEnvironment constructor: $target'); |
| } |
| |
| Constant _handleHasEnvironment(StringConstant name) { |
| return environmentDefines.containsKey(name.value) |
| ? trueConstant |
| : falseConstant; |
| } |
| |
| @override |
| Constant visitStaticInvocation(StaticInvocation node) { |
| final Procedure target = node.target; |
| final Arguments arguments = node.arguments; |
| final List<Constant> positionals = _evaluatePositionalArguments(arguments); |
| if (positionals == null && _gotError != null) { |
| AbortConstant error = _gotError; |
| _gotError = null; |
| return error; |
| } |
| assert(_gotError == null); |
| assert(positionals != null); |
| |
| final Map<String, Constant> named = _evaluateNamedArguments(arguments); |
| if (named == null && _gotError != null) { |
| AbortConstant error = _gotError; |
| _gotError = null; |
| return error; |
| } |
| assert(_gotError == null); |
| assert(named != null); |
| |
| if (shouldBeUnevaluated) { |
| return unevaluated( |
| node, |
| new StaticInvocation( |
| target, unevaluatedArguments(positionals, named, arguments.types), |
| isConst: true)); |
| } |
| if (target.kind == ProcedureKind.Factory) { |
| if (target.isConst && |
| target.enclosingLibrary == coreTypes.coreLibrary && |
| positionals.length == 1 && |
| (target.name.text == "fromEnvironment" || |
| target.name.text == "hasEnvironment")) { |
| if (environmentDefines != null) { |
| // Evaluate environment constant. |
| Constant name = positionals.single; |
| if (name is StringConstant) { |
| if (target.name.text == "fromEnvironment") { |
| return _handleFromEnvironment(target, name, named); |
| } else { |
| return _handleHasEnvironment(name); |
| } |
| } else if (name is NullConstant) { |
| return createErrorConstant(node, messageConstEvalNullValue); |
| } |
| } else { |
| // Leave environment constant unevaluated. |
| return unevaluated( |
| node, |
| new StaticInvocation(target, |
| unevaluatedArguments(positionals, named, arguments.types), |
| isConst: true)); |
| } |
| } |
| } else if (target.name.text == 'identical') { |
| // Ensure the "identical()" function comes from dart:core. |
| final TreeNode parent = target.parent; |
| if (parent is Library && parent == coreTypes.coreLibrary) { |
| final Constant left = positionals[0]; |
| final Constant right = positionals[1]; |
| |
| Constant evaluateIdentical() { |
| // Since we canonicalize constants during the evaluation, we can use |
| // identical here. |
| Constant result = makeBoolConstant(identical(left, right)); |
| if (evaluationMode == EvaluationMode.agnostic) { |
| Constant weakLeft = _weakener.visitConstant(left); |
| Constant weakRight = _weakener.visitConstant(right); |
| if (weakLeft != null || weakRight != null) { |
| Constant weakResult = makeBoolConstant( |
| identical(weakLeft ?? left, weakRight ?? right)); |
| if (!identical(result, weakResult)) { |
| return createErrorConstant(node, messageNonAgnosticConstant); |
| } |
| } |
| } |
| return result; |
| } |
| |
| if (targetingJavaScript) { |
| // In JavaScript, we lower [identical] to `===`, so we need to take |
| // the double special cases into account. |
| return doubleSpecialCases(left, right) ?? evaluateIdentical(); |
| } |
| return evaluateIdentical(); |
| } |
| } else if (target.isExtensionMember) { |
| return createErrorConstant(node, messageConstEvalExtension); |
| } |
| |
| String name = target.name.text; |
| if (target is Procedure && target.isFactory) { |
| if (name.isEmpty) { |
| name = target.enclosingClass.name; |
| } else { |
| name = '${target.enclosingClass.name}.${name}'; |
| } |
| } |
| return createInvalidExpressionConstant(node, "Invocation of $name"); |
| } |
| |
| @override |
| Constant visitAsExpression(AsExpression node) { |
| final Constant constant = _evaluateSubexpression(node.operand); |
| if (constant is AbortConstant) return constant; |
| if (shouldBeUnevaluated) { |
| return unevaluated( |
| node, |
| new AsExpression(extract(constant), env.substituteType(node.type)) |
| ..isForNonNullableByDefault = |
| _staticTypeContext.isNonNullableByDefault); |
| } |
| DartType type = _evaluateDartType(node, node.type); |
| if (type == null && _gotError != null) { |
| AbortConstant error = _gotError; |
| _gotError = null; |
| return error; |
| } |
| assert(_gotError == null); |
| assert(type != null); |
| return ensureIsSubtype(constant, type, node); |
| } |
| |
| @override |
| Constant visitIsExpression(IsExpression node) { |
| final Constant constant = _evaluateSubexpression(node.operand); |
| if (constant is AbortConstant) return constant; |
| if (shouldBeUnevaluated) { |
| return unevaluated( |
| node, |
| new IsExpression(extract(constant), node.type) |
| ..fileOffset = node.fileOffset |
| ..flags = node.flags); |
| } |
| |
| DartType type = _evaluateDartType(node, node.type); |
| if (type == null && _gotError != null) { |
| AbortConstant error = _gotError; |
| _gotError = null; |
| return error; |
| } |
| assert(_gotError == null); |
| assert(type != null); |
| |
| bool performIs(Constant constant, {bool strongMode}) { |
| assert(strongMode != null); |
| if (strongMode) { |
| return isSubtype(constant, type, SubtypeCheckMode.withNullabilities); |
| } else { |
| // In weak checking mode: if e evaluates to a value v and v has runtime |
| // type S, an instance check e is T occurring in a legacy library or an |
| // opted-in library is evaluated as follows: |
| // |
| // If v is null and T is a legacy type, |
| // return LEGACY_SUBTYPE(T, NULL) || LEGACY_SUBTYPE(Object, T) |
| // If v is null and T is not a legacy type, |
| // return NNBD_SUBTYPE(NULL, T) |
| // Otherwise return LEGACY_SUBTYPE(S, T) |
| if (constant is NullConstant) { |
| if (type.nullability == Nullability.legacy) { |
| // `null is Null` is handled below. |
| return typeEnvironment.isSubtypeOf(type, typeEnvironment.nullType, |
| SubtypeCheckMode.ignoringNullabilities) || |
| typeEnvironment.isSubtypeOf(typeEnvironment.objectLegacyRawType, |
| type, SubtypeCheckMode.ignoringNullabilities); |
| } else { |
| return typeEnvironment.isSubtypeOf(typeEnvironment.nullType, type, |
| SubtypeCheckMode.withNullabilities); |
| } |
| } |
| return isSubtype( |
| constant, type, SubtypeCheckMode.ignoringNullabilities); |
| } |
| } |
| |
| switch (evaluationMode) { |
| case EvaluationMode.strong: |
| return makeBoolConstant(performIs(constant, strongMode: true)); |
| case EvaluationMode.agnostic: |
| bool strongResult = performIs(constant, strongMode: true); |
| Constant weakConstant = _weakener.visitConstant(constant) ?? constant; |
| bool weakResult = performIs(weakConstant, strongMode: false); |
| if (strongResult != weakResult) { |
| return createErrorConstant(node, messageNonAgnosticConstant); |
| } |
| return makeBoolConstant(strongResult); |
| case EvaluationMode.weak: |
| return makeBoolConstant(performIs(constant, strongMode: false)); |
| } |
| throw new UnsupportedError("Unexpected evaluation mode $evaluationMode"); |
| } |
| |
| @override |
| Constant visitNot(Not node) { |
| final Constant constant = _evaluateSubexpression(node.operand); |
| if (constant is AbortConstant) return constant; |
| if (constant is BoolConstant) { |
| return makeBoolConstant(constant != trueConstant); |
| } |
| if (shouldBeUnevaluated) { |
| return unevaluated(node, new Not(extract(constant))); |
| } |
| return createErrorConstant( |
| node, |
| templateConstEvalInvalidType.withArguments( |
| constant, |
| typeEnvironment.coreTypes.boolLegacyRawType, |
| constant.getType(_staticTypeContext), |
| isNonNullableByDefault)); |
| } |
| |
| @override |
| Constant visitNullCheck(NullCheck node) { |
| final Constant constant = _evaluateSubexpression(node.operand); |
| if (constant is AbortConstant) return constant; |
| if (constant is NullConstant) { |
| return createErrorConstant(node, messageConstEvalNonNull); |
| } |
| if (shouldBeUnevaluated) { |
| return unevaluated(node, new NullCheck(extract(constant))); |
| } |
| return constant; |
| } |
| |
| @override |
| Constant visitSymbolLiteral(SymbolLiteral node) { |
| final Reference libraryReference = |
| node.value.startsWith('_') ? libraryOf(node).reference : null; |
| return canonicalize(new SymbolConstant(node.value, libraryReference)); |
| } |
| |
| @override |
| Constant visitInstantiation(Instantiation node) { |
| final Constant constant = _evaluateSubexpression(node.expression); |
| if (constant is AbortConstant) return constant; |
| if (shouldBeUnevaluated) { |
| return unevaluated( |
| node, |
| new Instantiation(extract(constant), |
| node.typeArguments.map((t) => env.substituteType(t)).toList())); |
| } |
| if (constant is TearOffConstant) { |
| if (node.typeArguments.length == |
| constant.procedure.function.typeParameters.length) { |
| List<DartType> types = _evaluateDartTypes(node, node.typeArguments); |
| if (types == null && _gotError != null) { |
| AbortConstant error = _gotError; |
| _gotError = null; |
| return error; |
| } |
| assert(_gotError == null); |
| assert(types != null); |
| |
| final List<DartType> typeArguments = convertTypes(types); |
| return canonicalize( |
| new PartialInstantiationConstant(constant, typeArguments)); |
| } |
| // Probably unreachable. |
| return createInvalidExpressionConstant( |
| node, |
| 'The number of type arguments supplied in the partial instantiation ' |
| 'does not match the number of type arguments of the $constant.'); |
| } |
| // The inner expression in an instantiation can never be null, since |
| // instantiations are only inferred on direct references to declarations. |
| // Probably unreachable. |
| return createInvalidExpressionConstant( |
| node, 'Only tear-off constants can be partially instantiated.'); |
| } |
| |
| @override |
| Constant visitCheckLibraryIsLoaded(CheckLibraryIsLoaded node) { |
| return createErrorConstant( |
| node, templateConstEvalDeferredLibrary.withArguments(node.import.name)); |
| } |
| |
| // Helper methods: |
| |
| /// If both constants are DoubleConstant whose values would give different |
| /// results from == and [identical], return the result of ==. Otherwise |
| /// return null. |
| Constant doubleSpecialCases(Constant a, Constant b) { |
| if (a is DoubleConstant && b is DoubleConstant) { |
| if (a.value.isNaN && b.value.isNaN) return falseConstant; |
| if (a.value == 0.0 && b.value == 0.0) return trueConstant; |
| } |
| |
| if (a is DoubleConstant && b is IntConstant) { |
| return makeBoolConstant(a.value == b.value); |
| } |
| |
| if (a is IntConstant && b is DoubleConstant) { |
| return makeBoolConstant(a.value == b.value); |
| } |
| return null; |
| } |
| |
| bool hasPrimitiveEqual(Constant constant) { |
| if (intFolder.isInt(constant)) return true; |
| DartType type = constant.getType(_staticTypeContext); |
| return !(type is InterfaceType && !classHasPrimitiveEqual(type.classNode)); |
| } |
| |
| bool classHasPrimitiveEqual(Class klass) { |
| bool cached = primitiveEqualCache[klass]; |
| if (cached != null) return cached; |
| for (Procedure procedure in klass.procedures) { |
| if (procedure.kind == ProcedureKind.Operator && |
| procedure.name.text == '==' && |
| !procedure.isAbstract && |
| !procedure.isForwardingStub) { |
| return primitiveEqualCache[klass] = false; |
| } |
| } |
| if (klass.supertype == null) return true; // To be on the safe side |
| return primitiveEqualCache[klass] = |
| classHasPrimitiveEqual(klass.supertype.classNode); |
| } |
| |
| BoolConstant makeBoolConstant(bool value) => |
| value ? trueConstant : falseConstant; |
| |
| bool isSubtype(Constant constant, DartType type, SubtypeCheckMode mode) { |
| DartType constantType = constant.getType(_staticTypeContext); |
| if (mode == SubtypeCheckMode.ignoringNullabilities) { |
| constantType = rawLegacyErasure(coreTypes, constantType) ?? constantType; |
| } |
| bool result = typeEnvironment.isSubtypeOf(constantType, type, mode); |
| if (targetingJavaScript && !result) { |
| if (constantType is InterfaceType && |
| constantType.classNode == typeEnvironment.coreTypes.intClass) { |
| // Probably unreachable. |
| // With JS semantics, an integer is also a double. |
| result = typeEnvironment.isSubtypeOf( |
| new InterfaceType(typeEnvironment.coreTypes.doubleClass, |
| constantType.nullability, const <DartType>[]), |
| type, |
| mode); |
| } else if (intFolder.isInt(constant)) { |
| // With JS semantics, an integer valued double is also an int. |
| result = typeEnvironment.isSubtypeOf( |
| new InterfaceType(typeEnvironment.coreTypes.intClass, |
| constantType.nullability, const <DartType>[]), |
| type, |
| mode); |
| } |
| } |
| return result; |
| } |
| |
| /// Note that this returns an error-constant on error and as such the |
| /// return value should be checked. |
| Constant ensureIsSubtype(Constant constant, DartType type, TreeNode node) { |
| bool result; |
| switch (evaluationMode) { |
| case EvaluationMode.strong: |
| result = isSubtype(constant, type, SubtypeCheckMode.withNullabilities); |
| break; |
| case EvaluationMode.agnostic: |
| bool strongResult = |
| isSubtype(constant, type, SubtypeCheckMode.withNullabilities); |
| Constant weakConstant = _weakener.visitConstant(constant) ?? constant; |
| bool weakResult = isSubtype( |
| weakConstant, type, SubtypeCheckMode.ignoringNullabilities); |
| if (strongResult != weakResult) { |
| return createErrorConstant(node, messageNonAgnosticConstant); |
| } |
| result = strongResult; |
| break; |
| case EvaluationMode.weak: |
| result = |
| isSubtype(constant, type, SubtypeCheckMode.ignoringNullabilities); |
| break; |
| } |
| if (!result) { |
| return createErrorConstant( |
| node, |
| templateConstEvalInvalidType.withArguments(constant, type, |
| constant.getType(_staticTypeContext), isNonNullableByDefault)); |
| } |
| return constant; |
| } |
| |
| /// Returns the types on success and null on failure. |
| /// Note that on failure an errorConstant is saved in [_gotError]. |
| List<DartType> _evaluateTypeArguments(TreeNode node, Arguments arguments) { |
| return _evaluateDartTypes(node, arguments.types); |
| } |
| |
| /// Returns the types on success and null on failure. |
| /// Note that on failure an errorConstant is saved in [_gotError]. |
| List<DartType> _evaluateSuperTypeArguments(TreeNode node, Supertype type) { |
| return _evaluateDartTypes(node, type.typeArguments); |
| } |
| |
| /// Upon failure in certain procedure calls (e.g. [_evaluateDartTypes]) the |
| /// "error"-constant is saved here. Normally this should be null. |
| /// Once a caller calls such a procedure and it gives an error here, |
| /// the caller should fetch it an null-out this variable. |
| AbortConstant _gotError; |
| |
| /// Returns the types on success and null on failure. |
| /// Note that on failure an errorConstant is saved in [_gotError]. |
| List<DartType> _evaluateDartTypes(TreeNode node, List<DartType> types) { |
| // TODO: Once the frontend guarantees that there are no free type variables |
| // left over after substitution, we can enable this shortcut again: |
| // if (env.isEmpty) return types; |
| List<DartType> result = |
| new List<DartType>.filled(types.length, null, growable: true); |
| for (int i = 0; i < types.length; i++) { |
| DartType type = _evaluateDartType(node, types[i]); |
| if (type == null && _gotError != null) { |
| return null; |
| } |
| assert(_gotError == null); |
| assert(type != null); |
| result[i] = type; |
| } |
| return result; |
| } |
| |
| /// Returns the type on success and null on failure. |
| /// Note that on failure an errorConstant is saved in [_gotError]. |
| DartType _evaluateDartType(TreeNode node, DartType type) { |
| final DartType result = env.substituteType(type); |
| |
| if (!isInstantiated(result)) { |
| _gotError = createErrorConstant( |
| node, |
| templateConstEvalFreeTypeParameter.withArguments( |
| type, isNonNullableByDefault)); |
| return null; |
| } |
| |
| return result; |
| } |
| |
| /// Returns the types on success and null on failure. |
| /// Note that on failure an errorConstant is saved in [_gotError]. |
| List<Constant> _evaluatePositionalArguments(Arguments arguments) { |
| List<Constant> result = new List<Constant>.filled( |
| arguments.positional.length, null, |
| growable: true); |
| for (int i = 0; i < arguments.positional.length; i++) { |
| Constant constant = _evaluateSubexpression(arguments.positional[i]); |
| if (constant is AbortConstant) { |
| _gotError = constant; |
| return null; |
| } |
| result[i] = constant; |
| } |
| return result; |
| } |
| |
| /// Returns the arguments on success and null on failure. |
| /// Note that on failure an errorConstant is saved in [_gotError]. |
| Map<String, Constant> _evaluateNamedArguments(Arguments arguments) { |
| if (arguments.named.isEmpty) return const <String, Constant>{}; |
| |
| final Map<String, Constant> named = {}; |
| arguments.named.forEach((NamedExpression pair) { |
| if (_gotError != null) return null; |
| Constant constant = _evaluateSubexpression(pair.value); |
| if (constant is AbortConstant) { |
| _gotError = constant; |
| return null; |
| } |
| named[pair.name] = constant; |
| }); |
| if (_gotError != null) return null; |
| return named; |
| } |
| |
| Arguments unevaluatedArguments(List<Constant> positionalArgs, |
| Map<String, Constant> namedArgs, List<DartType> types) { |
| final List<Expression> positional = |
| new List<Expression>(positionalArgs.length); |
| final List<NamedExpression> named = |
| new List<NamedExpression>(namedArgs.length); |
| for (int i = 0; i < positionalArgs.length; ++i) { |
| positional[i] = extract(positionalArgs[i]); |
| } |
| int i = 0; |
| namedArgs.forEach((String name, Constant value) { |
| named[i++] = new NamedExpression(name, extract(value)); |
| }); |
| return new Arguments(positional, named: named, types: types); |
| } |
| |
| Constant canonicalize(Constant constant) { |
| return canonicalizationCache.putIfAbsent(constant, () => constant); |
| } |
| |
| T withNewInstanceBuilder<T>( |
| Class klass, List<DartType> typeArguments, T fn()) { |
| InstanceBuilder old = instanceBuilder; |
| instanceBuilder = new InstanceBuilder(this, klass, typeArguments); |
| T result = fn(); |
| instanceBuilder = old; |
| return result; |
| } |
| |
| T withNewEnvironment<T>(T fn()) { |
| final EvaluationEnvironment oldEnv = env; |
| env = new EvaluationEnvironment(); |
| T result = fn(); |
| env = oldEnv; |
| return result; |
| } |
| |
| /// Binary operation between two operands, at least one of which is a double. |
| Constant evaluateBinaryNumericOperation( |
| String op, num a, num b, TreeNode node) { |
| switch (op) { |
| case '+': |
| return new DoubleConstant(a + b); |
| case '-': |
| return new DoubleConstant(a - b); |
| case '*': |
| return new DoubleConstant(a * b); |
| case '/': |
| return new DoubleConstant(a / b); |
| case '~/': |
| if (b == 0) { |
| return createErrorConstant( |
| node, templateConstEvalZeroDivisor.withArguments(op, '$a')); |
| } |
| return intFolder.truncatingDivide(node, a, b); |
| case '%': |
| return new DoubleConstant(a % b); |
| } |
| |
| switch (op) { |
| case '<': |
| return makeBoolConstant(a < b); |
| case '<=': |
| return makeBoolConstant(a <= b); |
| case '>=': |
| return makeBoolConstant(a >= b); |
| case '>': |
| return makeBoolConstant(a > b); |
| } |
| |
| // Probably unreachable. |
| return createInvalidExpressionConstant( |
| node, "Unexpected binary numeric operation '$op'."); |
| } |
| |
| Library libraryOf(TreeNode node) { |
| // The tree structure of the kernel AST ensures we always have an enclosing |
| // library. |
| while (true) { |
| if (node is Library) return node; |
| node = node.parent; |
| } |
| } |
| } |
| |
| /// Holds the necessary information for a constant object, namely |
| /// * the [klass] being instantiated |
| /// * the [typeArguments] used for the instantiation |
| /// * the [fields] the instance will obtain (all fields from the |
| /// instantiated [klass] up to the [Object] klass). |
| class InstanceBuilder { |
| ConstantEvaluator evaluator; |
| |
| /// The class of the new instance. |
| final Class klass; |
| |
| /// The values of the type parameters of the new instance. |
| final List<DartType> typeArguments; |
| |
| /// The field values of the new instance. |
| final Map<Field, Constant> fields = <Field, Constant>{}; |
| |
| final List<AssertStatement> asserts = <AssertStatement>[]; |
| |
| final List<Expression> unusedArguments = <Expression>[]; |
| |
| InstanceBuilder(this.evaluator, this.klass, this.typeArguments); |
| |
| void setFieldValue(Field field, Constant constant) { |
| fields[field] = constant; |
| } |
| |
| InstanceConstant buildInstance() { |
| assert(asserts.isEmpty); |
| final Map<Reference, Constant> fieldValues = <Reference, Constant>{}; |
| fields.forEach((Field field, Constant value) { |
| assert(value is! UnevaluatedConstant); |
| fieldValues[field.reference] = value; |
| }); |
| assert(unusedArguments.isEmpty); |
| return new InstanceConstant(klass.reference, typeArguments, fieldValues); |
| } |
| |
| InstanceCreation buildUnevaluatedInstance() { |
| final Map<Reference, Expression> fieldValues = <Reference, Expression>{}; |
| fields.forEach((Field field, Constant value) { |
| fieldValues[field.reference] = evaluator.extract(value); |
| }); |
| return new InstanceCreation( |
| klass.reference, typeArguments, fieldValues, asserts, unusedArguments); |
| } |
| } |
| |
| /// Holds an environment of type parameters, parameters and variables. |
| class EvaluationEnvironment { |
| /// The values of the type parameters in scope. |
| final Map<TypeParameter, DartType> _typeVariables = |
| <TypeParameter, DartType>{}; |
| |
| /// The values of the parameters/variables in scope. |
| final Map<VariableDeclaration, Constant> _variables = |
| <VariableDeclaration, Constant>{}; |
| |
| /// The variables that hold unevaluated constants. |
| /// |
| /// Variables are removed from this set when looked up, leaving only the |
| /// unread variables at the end. |
| final Set<VariableDeclaration> _unreadUnevaluatedVariables = |
| new Set<VariableDeclaration>(); |
| |
| /// Whether the current environment is empty. |
| bool get isEmpty => _typeVariables.isEmpty && _variables.isEmpty; |
| |
| void addTypeParameterValue(TypeParameter parameter, DartType value) { |
| assert(!_typeVariables.containsKey(parameter)); |
| _typeVariables[parameter] = value; |
| } |
| |
| void addVariableValue(VariableDeclaration variable, Constant value) { |
| _variables[variable] = value; |
| if (value is UnevaluatedConstant) { |
| _unreadUnevaluatedVariables.add(variable); |
| } |
| |