| // Copyright (c) 2015, 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:analyzer/dart/ast/ast.dart'; | |
| import 'package:analyzer/dart/ast/visitor.dart'; | |
| import 'package:analyzer/dart/element/element.dart'; | |
| import 'package:analyzer/src/generated/constant.dart'; | |
| import 'package:analyzer/error/listener.dart' | |
| show AnalysisErrorListener, ErrorReporter; | |
| import 'package:analyzer/src/generated/engine.dart'; | |
| import 'package:analyzer/src/generated/source.dart' show Source; | |
| import 'package:analyzer/src/dart/ast/ast.dart'; | |
| /// True is the expression can be evaluated multiple times without causing | |
| /// code execution. This is true for final fields. This can be true for local | |
| /// variables, if: | |
| /// | |
| /// * they are not assigned within the [context] scope. | |
| /// * they are not assigned in a function closure anywhere. | |
| /// | |
| /// This method is used to avoid creating temporaries in cases where we know | |
| /// we can safely re-evaluate [node] multiple times in [context]. This lets | |
| /// us generate prettier code. | |
| /// | |
| /// This method is conservative: it should never return `true` unless it is | |
| /// certain the [node] is stateless, because generated code may rely on the | |
| /// correctness of a `true` value. However it may return `false` for things | |
| /// that are in fact, stateless. | |
| bool isStateless(FunctionBody function, Expression node, [AstNode context]) { | |
| // `this` and `super` cannot be reassigned. | |
| if (node is ThisExpression || node is SuperExpression) return true; | |
| if (node is Identifier) { | |
| var e = node.staticElement; | |
| if (e is PropertyAccessorElement) { | |
| e = (e as PropertyAccessorElement).variable; | |
| } | |
| if (e is VariableElement && !e.isSynthetic) { | |
| if (e.isFinal) return true; | |
| if (e is LocalVariableElement || e is ParameterElement) { | |
| // make sure the local isn't mutated in the context. | |
| return !isPotentiallyMutated(function, e, context); | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| /// Returns true if the local variable is potentially mutated within [context]. | |
| /// This accounts for closures that may have been created outside of [context]. | |
| bool isPotentiallyMutated(FunctionBody function, VariableElement e, | |
| [AstNode context]) { | |
| if (function is FunctionBodyImpl && function.localVariableInfo == null) { | |
| // TODO(jmesserly): this is a caching bug in Analyzer. They don't restore | |
| // this info in some cases. | |
| return true; | |
| } | |
| if (function.isPotentiallyMutatedInClosure(e)) return true; | |
| if (function.isPotentiallyMutatedInScope(e)) { | |
| // Need to visit the context looking for assignment to this local. | |
| if (context != null) { | |
| var visitor = new _AssignmentFinder(e); | |
| context.accept(visitor); | |
| return visitor._potentiallyMutated; | |
| } | |
| return true; | |
| } | |
| return false; | |
| } | |
| /// Adapted from VariableResolverVisitor. Finds an assignment to a given | |
| /// local variable. | |
| class _AssignmentFinder extends RecursiveAstVisitor { | |
| final VariableElement _variable; | |
| bool _potentiallyMutated = false; | |
| _AssignmentFinder(this._variable); | |
| @override | |
| visitSimpleIdentifier(SimpleIdentifier node) { | |
| // Ignore if qualified. | |
| AstNode parent = node.parent; | |
| if (parent is PrefixedIdentifier && identical(parent.identifier, node)) { | |
| return; | |
| } | |
| if (parent is PropertyAccess && identical(parent.propertyName, node)) { | |
| return; | |
| } | |
| if (parent is MethodInvocation && identical(parent.methodName, node)) { | |
| return; | |
| } | |
| if (parent is ConstructorName) return; | |
| if (parent is Label) return; | |
| if (node.inSetterContext() && node.staticElement == _variable) { | |
| _potentiallyMutated = true; | |
| } | |
| } | |
| } | |
| class ConstFieldVisitor { | |
| final ConstantVisitor constantVisitor; | |
| ConstFieldVisitor(AnalysisContext context, {Source dummySource}) | |
| : constantVisitor = new ConstantVisitor( | |
| new ConstantEvaluationEngine( | |
| context.typeProvider, context.declaredVariables), | |
| new ErrorReporter( | |
| AnalysisErrorListener.NULL_LISTENER, dummySource)); | |
| // TODO(jmesserly): this is used to determine if the field initialization is | |
| // side effect free. We should make the check more general, as things like | |
| // list/map literals/regexp are also side effect free and fairly common | |
| // to use as field initializers. | |
| bool isFieldInitConstant(VariableDeclaration field) => | |
| field.initializer == null || computeConstant(field) != null; | |
| DartObject computeConstant(VariableDeclaration field) { | |
| // If the constant is already computed by ConstantEvaluator, just return it. | |
| VariableElement element = field.element; | |
| var result = element.computeConstantValue(); | |
| if (result != null) return result; | |
| // ConstantEvaluator will not compute constants for non-const fields, | |
| // so run ConstantVisitor for those to figure out if the initializer is | |
| // actually a constant (and therefore side effect free to evaluate). | |
| assert(!field.isConst); | |
| var initializer = field.initializer; | |
| if (initializer == null) return null; | |
| return initializer.accept(constantVisitor); | |
| } | |
| } |