| // Copyright (c) 2018, 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. |
| |
| import 'package:kernel/core_types.dart'; |
| import 'package:kernel/kernel.dart'; |
| import 'package:kernel/transformations/constants.dart'; |
| import 'package:kernel/type_environment.dart'; |
| import 'kernel_helpers.dart'; |
| |
| /// Implements constant evaluation for dev compiler: |
| /// |
| /// [isConstant] determines if an expression is constant. |
| /// [evaluate] computes the value of a constant expression, if available. |
| class DevCompilerConstants { |
| final _ConstantVisitor _visitor; |
| final _ConstantEvaluator _evaluator; |
| |
| DevCompilerConstants( |
| TypeEnvironment types, Map<String, String> declaredVariables) |
| : _visitor = _ConstantVisitor(types.coreTypes), |
| _evaluator = _ConstantEvaluator(types, declaredVariables); |
| |
| /// Determines if an expression is constant. |
| bool isConstant(Expression e) => _visitor.isConstant(e); |
| |
| /// Evaluates [e] to find its constant value, returning `null` if evaluation |
| /// failed, or if the constant was unavailable. |
| /// |
| /// Returns [NullConstant] to represent the `null` value. |
| /// |
| /// To avoid performance costs associated with try+catch on invalid constant |
| /// evaluation, call this after [isConstant] is known to be true. |
| Constant evaluate(Expression e, {bool cache = false}) { |
| if (e == null) return null; |
| |
| try { |
| var result = cache ? _evaluator.evaluate(e) : e.accept(_evaluator); |
| return identical(result, _evaluator.unavailableConstant) ? null : result; |
| } on _AbortCurrentEvaluation { |
| // TODO(jmesserly): the try+catch is necessary because the front end is |
| // not issuing sufficient errors, so the constant evaluation can fail. |
| // |
| // It can also be caused by methods in the evaluator that don't understand |
| // unavailable constants. |
| return null; |
| } on NoSuchMethodError { |
| // TODO(jmesserly): this is probably the same issue as above, but verify |
| // that it's fixed once Kernel does constant evaluation. |
| return null; |
| } |
| } |
| |
| /// If [node] is an annotation with a field named `name`, returns that field's |
| /// value. |
| /// |
| /// This assumes the `name` field is populated from a named argument `name:` |
| /// or from the first positional argument. |
| /// |
| /// For example: |
| /// |
| /// class MyAnnotation { |
| /// final String name; |
| /// // ... |
| /// const MyAnnotation(this.name/*, ... other params ... */); |
| /// } |
| /// |
| /// @MyAnnotation('FooBar') |
| /// main() { ... } |
| /// |
| /// Given the node for `@MyAnnotation('FooBar')` this will return `'FooBar'`. |
| String getNameFromAnnotation(ConstructorInvocation node) { |
| if (node == null) return null; |
| |
| // TODO(jmesserly): this does not use the normal evaluation engine, because |
| // it won't work if we don't have the const constructor body available. |
| // |
| // We may need to address this in the kernel outline files. |
| Expression first; |
| var named = node.arguments.named; |
| if (named.isNotEmpty) { |
| first = |
| named.firstWhere((n) => n.name == 'name', orElse: () => null)?.value; |
| } |
| var positional = node.arguments.positional; |
| if (positional.isNotEmpty) first ??= positional[0]; |
| if (first != null) { |
| first = _followConstFields(first); |
| if (first is StringLiteral) return first.value; |
| } |
| return null; |
| } |
| |
| Expression _followConstFields(Expression expr) { |
| if (expr is StaticGet) { |
| var target = expr.target; |
| if (target is Field) { |
| return _followConstFields(target.initializer); |
| } |
| } |
| return expr; |
| } |
| } |
| |
| /// Finds constant expressions as defined in Dart language spec 4th ed, |
| /// 16.1 Constants. |
| class _ConstantVisitor extends ExpressionVisitor<bool> { |
| final CoreTypes coreTypes; |
| _ConstantVisitor(this.coreTypes); |
| |
| bool isConstant(Expression e) => e.accept(this); |
| |
| defaultExpression(node) => false; |
| defaultBasicLiteral(node) => true; |
| visitTypeLiteral(node) => true; // TODO(jmesserly): deferred libraries? |
| visitSymbolLiteral(node) => true; |
| visitListLiteral(node) => node.isConst; |
| visitMapLiteral(node) => node.isConst; |
| visitStaticInvocation(node) { |
| return node.isConst || |
| node.target == coreTypes.identicalProcedure && |
| node.arguments.positional.every(isConstant) || |
| isFromEnvironmentInvocation(coreTypes, node) && |
| node.arguments.positional.every(isConstant) && |
| node.arguments.named.every((n) => isConstant(n.value)); |
| } |
| |
| visitDirectMethodInvocation(node) { |
| return node.receiver is BasicLiteral && |
| isOperatorMethodName(node.name.name) && |
| node.arguments.positional.every((p) => p is BasicLiteral); |
| } |
| |
| visitMethodInvocation(node) { |
| return node.receiver is BasicLiteral && |
| isOperatorMethodName(node.name.name) && |
| node.arguments.positional.every((p) => p is BasicLiteral); |
| } |
| |
| visitConstructorInvocation(node) => node.isConst; |
| visitStringConcatenation(node) => |
| node.expressions.every((e) => e is BasicLiteral); |
| visitStaticGet(node) { |
| var target = node.target; |
| return target is Procedure || target is Field && target.isConst; |
| } |
| |
| visitVariableGet(node) => node.variable.isConst; |
| visitNot(node) { |
| var operand = node.operand; |
| return operand is BoolLiteral || |
| operand is DirectMethodInvocation && |
| visitDirectMethodInvocation(operand) || |
| operand is MethodInvocation && visitMethodInvocation(operand); |
| } |
| |
| visitLogicalExpression(node) => |
| node.left is BoolLiteral && node.right is BoolLiteral; |
| visitConditionalExpression(node) => |
| node.condition is BoolLiteral && |
| node.then is BoolLiteral && |
| node.otherwise is BoolLiteral; |
| |
| visitLet(Let node) { |
| var init = node.variable.initializer; |
| return (init == null || isConstant(init)) && isConstant(node.body); |
| } |
| } |
| |
| /// The visitor that evaluates constants, building on Kernel's |
| /// [ConstantEvaluator] class (used by the VM) and fixing some of its behavior |
| /// to work better for DDC. |
| // |
| // TODO(jmesserly): make some changes in the base class to make it a better fit |
| // for compilers like DDC? |
| class _ConstantEvaluator extends ConstantEvaluator { |
| final Map<String, String> declaredVariables; |
| |
| /// Used to denote an unavailable constant value from another module |
| /// |
| // TODO(jmesserly): this happens when we try to evaluate constant values from |
| // an external library, that was from an outline kernel file. The kernel file |
| // does not contain the initializer value of the constant. |
| final Constant unavailableConstant; |
| |
| _ConstantEvaluator(TypeEnvironment types, this.declaredVariables, |
| {bool enableAsserts: false}) |
| : unavailableConstant = InstanceConstant(null, [], {}), |
| super(_ConstantsBackend(types.coreTypes), types, types.coreTypes, |
| enableAsserts, |
| errorReporter: const _ErrorReporter()) { |
| env = EvaluationEnvironment(); |
| } |
| |
| @override |
| visitVariableGet(node) { |
| // The base evaluator expects that variable declarations are visited during |
| // the transformation step, so it doesn't handle constant variables. |
| // Instead handle them here. |
| if (node.variable.isConst) { |
| return evaluate(node.variable.initializer); |
| } |
| // Fall back to the base evaluator for other cases (e.g. parameters of a |
| // constant constructor). |
| return super.visitVariableGet(node); |
| } |
| |
| @override |
| visitStaticGet(StaticGet node) { |
| // Handle unavailable field constants. This happens if an external library |
| // only has its outline available. |
| var target = node.target; |
| if (target is Field && |
| target.isConst && |
| target.isInExternalLibrary && |
| target.initializer == null) { |
| return unavailableConstant; |
| } |
| return super.visitStaticGet(node); |
| } |
| |
| @override |
| visitConstructorInvocation(ConstructorInvocation node) { |
| // Handle unavailable constructor bodies. |
| // This happens if an external library only has its outline available. |
| var target = node.target; |
| if (target.isConst && |
| target.isInExternalLibrary && |
| target.function.body is EmptyStatement && |
| target.initializers.isEmpty) { |
| return unavailableConstant; |
| } |
| return super.visitConstructorInvocation(node); |
| } |
| |
| @override |
| visitStaticInvocation(node) { |
| // Handle int/bool/String.fromEnvironment constructors. |
| // |
| // (The VM handles this via its `native` calls and implements it in |
| // VmConstantsBackend.buildConstantForNative.) |
| var target = node.target; |
| if (isFromEnvironmentInvocation(coreTypes, node)) { |
| var firstArg = evaluatePositionalArguments(node.arguments)[0]; |
| var defaultArg = evaluateNamedArguments(node.arguments)['defaultValue']; |
| |
| var varName = (firstArg as StringConstant).value; |
| var value = declaredVariables[varName]; |
| var targetClass = target.enclosingClass; |
| |
| if (targetClass == coreTypes.stringClass) { |
| if (value != null) return canonicalize(StringConstant(value)); |
| return defaultArg ?? nullConstant; |
| } else if (targetClass == coreTypes.intClass) { |
| var intValue = int.tryParse(value ?? ''); |
| if (intValue != null) return canonicalize(IntConstant(intValue)); |
| return defaultArg ?? nullConstant; |
| } else if (targetClass == coreTypes.boolClass) { |
| if (value == "true") return trueConstant; |
| if (value == "false") return falseConstant; |
| return defaultArg ?? falseConstant; |
| } |
| } |
| return super.visitStaticInvocation(node); |
| } |
| |
| @override |
| evaluateBinaryNumericOperation(String op, num a, num b, TreeNode node) { |
| // Use doubles to match JS number semantics. |
| return super |
| .evaluateBinaryNumericOperation(op, a.toDouble(), b.toDouble(), node); |
| } |
| |
| @override |
| canonicalize(Constant constant) { |
| if (constant is DoubleConstant) { |
| // Convert to an integer when possible (matching the runtime behavior |
| // of `is int`). |
| var d = constant.value; |
| if (d.isFinite) { |
| var i = d.toInt(); |
| if (d == i.toDouble()) return super.canonicalize(IntConstant(i)); |
| } |
| } |
| return super.canonicalize(constant); |
| } |
| } |
| |
| /// Implement the class for compiler specific behavior. |
| /// |
| /// This is mostly unused by DDC, because we don't use the global constant |
| /// transformer. |
| class _ConstantsBackend implements ConstantsBackend { |
| final CoreTypes coreTypes; |
| final Field symbolNameField; |
| |
| _ConstantsBackend(this.coreTypes) |
| : symbolNameField = coreTypes.internalSymbolClass.fields |
| .firstWhere((f) => f.name.name == '_name'); |
| |
| @override |
| buildConstantForNative(nativeName, typeArguments, positionalArguments, |
| namedArguments, context, node, errorReporter, abortEvaluation) { |
| throw StateError('unreachable'); // DDC does not use VM native syntax |
| } |
| |
| @override |
| lowerMapConstant(constant) => constant; |
| |
| @override |
| lowerListConstant(constant) => constant; |
| } |
| |
| class _ErrorReporter extends ErrorReporterBase { |
| const _ErrorReporter(); |
| |
| @override |
| report(context, message, node) => throw const _AbortCurrentEvaluation(); |
| } |
| |
| // TODO(jmesserly): this class is private in Kernel constants library, so |
| // we have our own version. |
| class _AbortCurrentEvaluation { |
| const _AbortCurrentEvaluation(); |
| } |