blob: 145219f293eeca48d261ae96c717abe792b87786 [file] [log] [blame]
// 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/resolver.dart' show TypeProvider;
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 = _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(
TypeProvider typeProvider, DeclaredVariables declaredVariables,
{Source dummySource})
: constantVisitor = ConstantVisitor(
ConstantEvaluationEngine(typeProvider, declaredVariables),
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.declaredElement;
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);
}
}