blob: 8b57cf37c2191b4cf9758a6e5cfc4fb293189b03 [file] [log] [blame]
// 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.
import 'package:kernel/ast.dart' as ir;
import 'package:kernel/core_types.dart' as ir;
import 'package:kernel/type_environment.dart' as ir;
import '../ir/constants.dart';
import 'closure.dart';
import 'scope.dart';
/// This builder walks the code to determine what variables are
/// assigned/captured/free at various points to build a [ClosureScopeModel] and
/// a [VariableScopeModel] that can respond to queries about how a particular
/// variable is being used at any point in the code.
class ScopeModelBuilder extends ir.Visitor<EvaluationComplexity>
with VariableCollectorMixin, ir.VisitorThrowingMixin<EvaluationComplexity> {
final Dart2jsConstantEvaluator _constantEvaluator;
late final ir.StaticTypeContext _staticTypeContext;
ir.TypeEnvironment get _typeEnvironment => _constantEvaluator.typeEnvironment;
ir.CoreTypes get _coreTypes => _typeEnvironment.coreTypes;
final ClosureScopeModel _model = ClosureScopeModel();
/// A map of each visited call node with the associated information about what
/// variables are captured/used. Each ir.Node key corresponds to a scope that
/// was encountered while visiting a closure (initially called through
/// [translateLazyInitializer] or [translateConstructorOrProcedure]).
Map<ir.Node, KernelCapturedScope> get _scopesCapturedInClosureMap =>
_model.capturedScopesMap;
/// A map of the nodes that we have flagged as necessary to generate closure
/// classes for in a later stage. We map that node to information ascertained
/// about variable usage in the surrounding scope.
Map<ir.TreeNode, KernelScopeInfo> get _closuresToGenerate =>
_model.closuresToGenerate;
/// The local variables that have been declared in the current scope.
// Initialized to a const value since it should be assigned in `enterNewScope`
// before collecting variables.
List<ir.Node /* ir.VariableDeclaration | TypeParameterTypeWithContext */ >
_scopeVariables = const [];
/// Pointer to the context in which this closure is executed.
/// For example, in the expression `var foo = () => 3 + i;`, the executable
/// context as we walk the nodes in that expression is the ir.Field `foo`.
ir.TreeNode? _executableContext;
/// A flag to indicate if we are currently inside a closure.
bool _isInsideClosure = false;
/// Pointer to the original node where this closure builder started.
ir.TreeNode? _outermostNode;
/// Keep track of the mutated local variables so that we don't need to box
/// non-mutated variables. We know these are only VariableDeclarations because
/// type variable types and `this` types can't be mutated!
final Set<ir.VariableDeclaration> _mutatedVariables = {};
/// The set of variables that are accessed in some form, whether they are
/// mutated or not.
final Set<
ir.Node /* ir.VariableDeclaration | TypeParameterTypeWithContext */ >
_capturedVariables = Set<ir.Node>();
/// If true, the visitor is currently traversing some nodes that are inside a
/// try block.
bool _inTry = false;
/// The current scope we are in.
KernelScopeInfo? __currentScopeInfo;
KernelScopeInfo get _currentScopeInfo => __currentScopeInfo!;
bool _hasThisLocal = false;
/// Keeps track of the number of boxes that we've created so that they each
/// have unique names.
int _boxCounter = 0;
/// The current usage of a type annotation.
///
/// This is updated in the visitor to distinguish between unconditional
/// type variable usage, such as type literals and is tests, and conditional
/// type variable usage, such as type argument in method invocations.
VariableUse? _currentTypeUsage;
ScopeModelBuilder(this._constantEvaluator);
ScopeModel computeModel(ir.Member node) {
if (node.isAbstract && !node.isExternal) {
return const ScopeModel(
initializerComplexity: EvaluationComplexity.lazy());
}
_staticTypeContext = ir.StaticTypeContext(node, _typeEnvironment);
if (node is ir.Constructor) {
_hasThisLocal = true;
} else if (node is ir.Procedure && node.kind == ir.ProcedureKind.Factory) {
_hasThisLocal = false;
} else if (node.isInstanceMember) {
_hasThisLocal = true;
} else {
_hasThisLocal = false;
}
EvaluationComplexity initializerComplexity =
const EvaluationComplexity.lazy();
if (node is ir.Field) {
if (node.initializer != null) {
initializerComplexity = node.accept(this);
} else {
initializerComplexity = const EvaluationComplexity.constant();
_model.scopeInfo = KernelScopeInfo(_hasThisLocal);
}
} else {
assert(node is ir.Procedure || node is ir.Constructor);
if (!(node is ir.Procedure && node.isRedirectingFactory)) {
// Skip redirecting factories: they contain invalid expressions only
// used to support internal CFE modular compilation.
node.accept(this);
}
}
return ScopeModel(
closureScopeModel: _model,
variableScopeModel: variableScopeModel,
initializerComplexity: initializerComplexity);
}
@override
EvaluationComplexity defaultNode(ir.Node node) =>
throw UnsupportedError('Unhandled node $node (${node.runtimeType})');
EvaluationComplexity visitNode(ir.Node node) {
return node.accept(this);
}
/// Tries to evaluate [node] as a constant expression.
///
/// If [node] it succeeds, an [EvaluationComplexity] containing the new
/// constant is returned. Otherwise a 'lazy' [EvaluationComplexity] is
/// returned, signaling that [node] is not a constant expression.
///
/// This method should be called in the visit methods of all expressions that
/// could potentially be constant to bubble up the constness of expressions.
///
/// For instance in `var a = 1 + 2` [visitIntLiteral] calls this method
/// for `1` and `2` to convert these from int literals to int constants, and
/// [visitMethodInvocation] call this method, when seeing that all of its
/// subexpressions are constant, and it itself therefore is potentially
/// constant, thus computing that `1 + 2` can be replaced by the int constant
/// `3`.
///
/// Note that [node] is _not_ replaced with a new constant expression. It is
/// the responsibility of the caller to do so. This is needed for performance
/// reasons since calling `TreeNode.replaceChild` searches linearly through
/// the children of the parent node, which lead to a O(n^2) complexity that
/// is severe and observable for instance for large list literals.
EvaluationComplexity _evaluateImplicitConstant(ir.Expression node) {
ir.Constant? constant = _constantEvaluator.evaluateOrNull(
_staticTypeContext, node,
requireConstant: false, replaceImplicitConstant: false);
if (constant != null) {
return EvaluationComplexity.constant(constant);
}
return const EvaluationComplexity.lazy();
}
/// The evaluation complexity of the last visited expression.
// TODO(48820): Pre-NNBD we gained some benefit from the `null` default
// value. It is too painful to add `!` after every access so this is
// initialized to a 'harmless' value. Should we add an invalid value
// 'ExpressionComplexity.invalid()` and check in the combiner and other
// use-sites that the value is not 'invalid'?
EvaluationComplexity _lastExpressionComplexity =
const EvaluationComplexity.constant();
/// Visit [node] and returns the corresponding `ConstantExpression` if [node]
/// evaluated to a constant.
///
/// This method stores the complexity of [node] in [_lastExpressionComplexity]
/// and sets the parent of the created `ConstantExpression` to the parent
/// of [node]. The caller must replace [node] within the parent node. This
/// is done to avoid calling `Node.replaceChild` which searches linearly
/// through the children nodes `node.parent` in order to replace `node` which
/// results in O(n^2) complexity of replacing elements in for instance a list
/// of `n` elements.
ir.Expression _handleExpression(ir.Expression node) {
_lastExpressionComplexity = visitNode(node);
if (_lastExpressionComplexity.isFreshConstant) {
return ir.ConstantExpression(_lastExpressionComplexity.constant!,
node.getStaticType(_staticTypeContext))
..fileOffset = node.fileOffset
..parent = node.parent;
}
return node;
}
/// Visit all [nodes] returning the combined complexity.
EvaluationComplexity visitNodes(List<ir.Node> nodes) {
EvaluationComplexity complexity = const EvaluationComplexity.constant();
for (ir.Node node in nodes) {
complexity = complexity.combine(visitNode(node));
}
return complexity;
}
/// Visit all [nodes] returning the combined complexity.
///
/// If subexpressions can be evaluated as constants, they are replaced by
/// constant expressions in [nodes].
EvaluationComplexity visitExpressions(List<ir.Expression> nodes) {
EvaluationComplexity combinedComplexity =
const EvaluationComplexity.constant();
for (int i = 0; i < nodes.length; i++) {
nodes[i] = _handleExpression(nodes[i]);
combinedComplexity =
combinedComplexity.combine(_lastExpressionComplexity);
}
return combinedComplexity;
}
/// Update the [CapturedScope] object corresponding to
/// this node if any variables are captured.
void attachCapturedScopeVariables(ir.TreeNode node) {
Set<ir.VariableDeclaration> capturedVariablesForScope =
Set<ir.VariableDeclaration>();
for (ir.Node variable in _scopeVariables) {
// No need to box non-assignable elements.
if (variable is ir.VariableDeclaration) {
if (variable.isConst) continue;
if (!_mutatedVariables.contains(variable)) continue;
if (_capturedVariables.contains(variable)) {
capturedVariablesForScope.add(variable);
}
}
}
if (!capturedVariablesForScope.isEmpty) {
assert(_model.scopeInfo != null);
KernelScopeInfo from = _model.scopeInfo!;
KernelCapturedScope capturedScope;
var nodeBox = NodeBox(getBoxName(), _executableContext!);
if (node is ir.ForStatement ||
node is ir.ForInStatement ||
node is ir.WhileStatement ||
node is ir.DoStatement) {
capturedScope = KernelCapturedLoopScope(
capturedVariablesForScope,
nodeBox,
[],
from.localsUsedInTryOrSync,
from.freeVariables,
from.freeVariablesForRti,
from.thisUsedAsFreeVariable,
from.thisUsedAsFreeVariableIfNeedsRti,
_hasThisLocal);
} else {
capturedScope = KernelCapturedScope(
capturedVariablesForScope,
nodeBox,
from.localsUsedInTryOrSync,
from.freeVariables,
from.freeVariablesForRti,
from.thisUsedAsFreeVariable,
from.thisUsedAsFreeVariableIfNeedsRti,
_hasThisLocal);
}
_model.scopeInfo = _scopesCapturedInClosureMap[node] = capturedScope;
}
}
/// Generate a unique name for the [_boxCounter]th box field.
///
/// The result is used as the name of [NodeBox]s and [BoxLocal]s, and must
/// therefore be unique to avoid breaking an invariant in the element model
/// (classes cannot declare multiple fields with the same name).
///
/// Also, the names should be distinct from real field names to prevent
/// clashes with selectors for those fields.
///
/// These names are not used in generated code, just as element name.
String getBoxName() {
return "_box_${_boxCounter++}";
}
/// Perform book-keeping with the current set of local variables that have
/// been seen thus far before entering this new scope.
void enterNewScope(ir.TreeNode node, void visitNewScope()) {
List<ir.Node> oldScopeVariables = _scopeVariables;
_scopeVariables = <ir.Node>[];
visitNewScope();
attachCapturedScopeVariables(node);
_mutatedVariables.removeAll(_scopeVariables);
_scopeVariables = oldScopeVariables;
}
@override
EvaluationComplexity visitNamedExpression(ir.NamedExpression node) {
throw UnsupportedError(
'NamedExpression should be handled through visitArguments');
}
@override
EvaluationComplexity visitTryCatch(ir.TryCatch node) {
bool oldInTry = _inTry;
_inTry = true;
visitInVariableScope(node, () {
visitNode(node.body);
});
visitNodes(node.catches);
_inTry = oldInTry;
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitTryFinally(ir.TryFinally node) {
bool oldInTry = _inTry;
_inTry = true;
visitInVariableScope(node, () {
visitNode(node.body);
});
visitNode(node.finalizer);
_inTry = oldInTry;
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitVariableGet(ir.VariableGet node) {
_markVariableAsUsed(node.variable, VariableUse.explicit);
// Don't visit `node.promotedType`.
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitVariableSet(ir.VariableSet node) {
_mutatedVariables.add(node.variable);
_markVariableAsUsed(node.variable, VariableUse.explicit);
visitInContext(node.variable.type, VariableUse.localType);
node.value = _handleExpression(node.value);
registerAssignedVariable(node.variable);
return const EvaluationComplexity.lazy();
}
void _handleVariableDeclaration(
ir.VariableDeclaration node, VariableUse usage) {
if (!node.isInitializingFormal) {
_scopeVariables.add(node);
}
visitInContext(node.type, usage);
if (node.initializer != null) {
node.initializer = _handleExpression(node.initializer!);
}
}
@override
EvaluationComplexity visitVariableDeclaration(ir.VariableDeclaration node) {
_handleVariableDeclaration(node, VariableUse.localType);
return const EvaluationComplexity.lazy();
}
/// Add this variable to the set of free variables if appropriate and add to
/// the tally of variables used in try or sync blocks.
/// If [onlyForRtiChecks] is true, add to the freeVariablesForRti set instead
/// of freeVariables as we will only use it if runtime type information is
/// checked.
void _markVariableAsUsed(
ir.Node /* VariableDeclaration | TypeParameterTypeWithContext */ variable,
VariableUse usage) {
assert(variable is ir.VariableDeclaration ||
variable is TypeVariableTypeWithContext);
assert((usage as dynamic) != null); // TODO(48820): Remove.
if (_isInsideClosure && !_inCurrentContext(variable)) {
// If the element is not declared in the current function and the element
// is not the closure itself we need to mark the element as free variable.
// Note that the check on [insideClosure] is not just an
// optimization: factories have type parameters as function
// parameters, and type parameters are declared in the class, not
// the factory.
if (usage == VariableUse.explicit) {
_currentScopeInfo.freeVariables.add(variable);
} else {
_currentScopeInfo.freeVariablesForRti
.putIfAbsent(variable as TypeVariableTypeWithContext,
() => Set<VariableUse>())
.add(usage);
}
}
if (_inTry && variable is ir.VariableDeclaration) {
_currentScopeInfo.localsUsedInTryOrSync.add(variable);
}
}
@override
EvaluationComplexity visitThisExpression(ir.ThisExpression thisExpression) {
if (_hasThisLocal) {
_registerNeedsThis(VariableUse.explicit);
}
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitTypeParameter(ir.TypeParameter typeParameter) {
TypeVariableTypeWithContext typeVariable(ir.Library library) =>
TypeVariableTypeWithContext(
ir.TypeParameterType.withDefaultNullabilityForLibrary(
typeParameter, library),
// If this typeParameter is part of a typedef then its parent is
// null because it has no context. Just pass in null for the
// context in that case.
typeParameter.parent?.parent);
ir.TreeNode? context = _executableContext;
if (_isInsideClosure && context is ir.Procedure && context.isFactory) {
// This is a closure in a factory constructor. Since there is no
// [:this:], we have to mark the type arguments as free variables to
// capture them in the closure.
_useTypeVariableAsLocal(
typeVariable(context.enclosingLibrary), _currentTypeUsage!);
}
if (context is ir.Member && context is! ir.Field) {
// In checked mode, using a type variable in a type annotation may lead
// to a runtime type check that needs to access the type argument and
// therefore the closure needs a this-element, if it is not in a field
// initializer; field initializers are evaluated in a context where
// the type arguments are available in locals.
if (_hasThisLocal) {
_registerNeedsThis(_currentTypeUsage!);
} else {
_useTypeVariableAsLocal(
typeVariable(context.enclosingLibrary), _currentTypeUsage!);
}
}
visitNode(typeParameter.bound);
return const EvaluationComplexity.constant();
}
/// Add `this` as a variable that needs to be accessed (and thus may become a
/// free/captured variable.
/// If [onlyIfNeedsRti] is true, set thisUsedAsFreeVariableIfNeedsRti to true
/// instead of thisUsedAsFreeVariable as we will only use `this` if runtime
/// type information is checked.
void _registerNeedsThis(VariableUse usage) {
if (_isInsideClosure) {
if (usage == VariableUse.explicit) {
_currentScopeInfo.thisUsedAsFreeVariable = true;
} else {
_currentScopeInfo.thisUsedAsFreeVariableIfNeedsRti.add(usage);
}
}
}
@override
EvaluationComplexity visitForInStatement(ir.ForInStatement node) {
// We need to set `inTry` to true if this is an async for-in because we
// desugar it into a try-finally in the SSA phase.
bool oldInTry = _inTry;
if (node.isAsync) {
_inTry = true;
}
enterNewScope(node, () {
visitNode(node.variable);
visitInVariableScope(node, () {
node.iterable = _handleExpression(node.iterable);
visitNode(node.body);
});
});
if (node.isAsync) {
_inTry = oldInTry;
}
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitWhileStatement(ir.WhileStatement node) {
enterNewScope(node, () {
visitInVariableScope(node, () {
node.condition = _handleExpression(node.condition);
visitNode(node.body);
});
});
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitDoStatement(ir.DoStatement node) {
enterNewScope(node, () {
visitInVariableScope(node, () {
visitNode(node.body);
node.condition = _handleExpression(node.condition);
});
});
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitForStatement(ir.ForStatement node) {
List<ir.VariableDeclaration> boxedLoopVariables =
<ir.VariableDeclaration>[];
enterNewScope(node, () {
// First visit initialized variables and update steps so we can easily
// check if a loop variable was captured in one of these subexpressions.
visitNodes(node.variables);
visitInVariableScope(node, () {
visitExpressions(node.updates);
});
// Loop variables that have not been captured yet can safely be flagged as
// non-mutated, because no nested function can observe the mutation.
for (ir.VariableDeclaration variable in node.variables) {
if (!_capturedVariables.contains(variable)) {
_mutatedVariables.remove(variable);
}
}
// Visit condition and body.
// This must happen after the above, so any loop variables mutated in the
// condition or body are indeed flagged as mutated.
visitInVariableScope(node, () {
if (node.condition != null) {
node.condition = _handleExpression(node.condition!);
}
visitNode(node.body);
});
// See if we have declared loop variables that need to be boxed.
for (ir.VariableDeclaration variable in node.variables) {
// Non-mutated variables should not be boxed. The _mutatedVariables set
// gets cleared when `enterNewScope` returns, so check it here.
if (_capturedVariables.contains(variable) &&
_mutatedVariables.contains(variable)) {
boxedLoopVariables.add(variable);
}
}
});
KernelCapturedScope? scope = _scopesCapturedInClosureMap[node];
if (scope != null) {
_scopesCapturedInClosureMap[node] = KernelCapturedLoopScope(
scope.boxedVariables,
scope.capturedVariablesAccessor,
boxedLoopVariables,
scope.localsUsedInTryOrSync,
scope.freeVariables,
scope.freeVariablesForRti,
scope.thisUsedAsFreeVariable,
scope.thisUsedAsFreeVariableIfNeedsRti,
scope.hasThisLocal);
}
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitSuperMethodInvocation(
ir.SuperMethodInvocation node) {
if (_hasThisLocal) {
_registerNeedsThis(VariableUse.explicit);
}
if (node.arguments.types.isNotEmpty) {
visitNodesInContext(node.arguments.types,
VariableUse.staticTypeArgument(node.interfaceTarget));
}
visitArguments(node.arguments);
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitSuperPropertySet(ir.SuperPropertySet node) {
if (_hasThisLocal) {
_registerNeedsThis(VariableUse.explicit);
}
node.value = _handleExpression(node.value);
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitSuperPropertyGet(ir.SuperPropertyGet node) {
if (_hasThisLocal) {
_registerNeedsThis(VariableUse.explicit);
}
return const EvaluationComplexity.lazy();
}
void visitInvokable(ir.TreeNode node, void f()) {
assert(node is ir.Member || node is ir.LocalFunction);
bool oldIsInsideClosure = _isInsideClosure;
ir.TreeNode? oldExecutableContext = _executableContext;
KernelScopeInfo? oldScopeInfo = __currentScopeInfo;
// _outermostNode is only null the first time we enter the body of the
// field, constructor, or method that is being analyzed.
_isInsideClosure = _outermostNode != null;
_executableContext = node;
__currentScopeInfo = KernelScopeInfo(_hasThisLocal);
if (_isInsideClosure) {
_closuresToGenerate[node] = _currentScopeInfo;
} else {
_outermostNode = node;
_model.scopeInfo = _currentScopeInfo;
}
enterNewScope(node, f);
KernelScopeInfo savedScopeInfo = _currentScopeInfo;
bool savedIsInsideClosure = _isInsideClosure;
// Restore old values.
_isInsideClosure = oldIsInsideClosure;
__currentScopeInfo = oldScopeInfo;
_executableContext = oldExecutableContext;
// Mark all free variables as captured and expect to encounter them in the
// outer function.
Iterable<ir.Node> freeVariables = savedScopeInfo.freeVariables;
assert(freeVariables.isEmpty || savedIsInsideClosure);
for (ir.Node freeVariable in freeVariables) {
_capturedVariables.add(freeVariable);
_markVariableAsUsed(freeVariable, VariableUse.explicit);
}
savedScopeInfo.freeVariablesForRti.forEach(
(TypeVariableTypeWithContext freeVariableForRti,
Set<VariableUse> useSet) {
for (VariableUse usage in useSet) {
_markVariableAsUsed(freeVariableForRti, usage);
}
});
if (_isInsideClosure && savedScopeInfo.thisUsedAsFreeVariable) {
_currentScopeInfo.thisUsedAsFreeVariable = true;
}
if (_isInsideClosure) {
_currentScopeInfo.thisUsedAsFreeVariableIfNeedsRti
.addAll(savedScopeInfo.thisUsedAsFreeVariableIfNeedsRti);
}
}
/// Return true if [variable]'s context is the same as the current executable
/// context.
bool _inCurrentContext(ir.Node variable) {
assert(variable is ir.VariableDeclaration ||
variable is TypeVariableTypeWithContext);
if (variable is TypeVariableTypeWithContext) {
return variable.context == _executableContext;
}
ir.TreeNode? node = variable as ir.TreeNode;
while (node != _outermostNode && node != _executableContext) {
node = node!.parent;
}
return node == _executableContext;
}
@override
EvaluationComplexity visitField(ir.Field node) {
_currentTypeUsage = VariableUse.fieldType;
late final EvaluationComplexity complexity;
visitInvokable(node, () {
assert(node.initializer != null);
node.initializer = _handleExpression(node.initializer!);
complexity = _lastExpressionComplexity;
});
_currentTypeUsage = null;
return complexity;
}
@override
EvaluationComplexity visitConstructor(ir.Constructor node) {
visitInvokable(node, () {
visitNodes(node.initializers);
visitNode(node.function);
});
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitProcedure(ir.Procedure node) {
visitInvokable(node, () {
visitNode(node.function);
});
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitFunctionExpression(ir.FunctionExpression node) {
visitInvokable(node, () {
visitInVariableScope(node, () {
visitNode(node.function);
});
});
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitFunctionDeclaration(ir.FunctionDeclaration node) {
visitInvokable(node, () {
visitInVariableScope(node, () {
visitNode(node.function);
});
});
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitDynamicType(ir.DynamicType node) =>
const EvaluationComplexity.constant();
@override
EvaluationComplexity visitNeverType(ir.NeverType node) =>
const EvaluationComplexity.lazy();
@override
EvaluationComplexity visitNullType(ir.NullType node) =>
const EvaluationComplexity.lazy();
@override
EvaluationComplexity visitInvalidType(ir.InvalidType node) =>
const EvaluationComplexity.lazy();
@override
EvaluationComplexity visitVoidType(ir.VoidType node) =>
const EvaluationComplexity.constant();
@override
EvaluationComplexity visitInterfaceType(ir.InterfaceType node) {
return visitNodes(node.typeArguments);
}
@override
EvaluationComplexity visitFutureOrType(ir.FutureOrType node) {
return visitNode(node.typeArgument);
}
@override
EvaluationComplexity visitFunctionType(ir.FunctionType node) {
EvaluationComplexity complexity = visitNode(node.returnType);
complexity = complexity.combine(visitNodes(node.positionalParameters));
complexity = complexity.combine(visitNodes(node.namedParameters));
return complexity.combine(visitNodes(node.typeParameters));
}
@override
EvaluationComplexity visitNamedType(ir.NamedType node) {
return visitNode(node.type);
}
@override
EvaluationComplexity visitTypeParameterType(ir.TypeParameterType node) {
_analyzeTypeVariable(node, _currentTypeUsage!);
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitIntersectionType(ir.IntersectionType node) {
_analyzeTypeVariable(node.left, _currentTypeUsage!);
return const EvaluationComplexity.lazy();
}
EvaluationComplexity visitInContext(ir.Node node, VariableUse use) {
VariableUse? oldCurrentTypeUsage = _currentTypeUsage;
_currentTypeUsage = use;
EvaluationComplexity complexity = visitNode(node);
_currentTypeUsage = oldCurrentTypeUsage;
return complexity;
}
EvaluationComplexity visitNodesInContext(
List<ir.Node> nodes, VariableUse use) {
VariableUse? oldCurrentTypeUsage = _currentTypeUsage;
_currentTypeUsage = use;
EvaluationComplexity complexity = visitNodes(nodes);
_currentTypeUsage = oldCurrentTypeUsage;
return complexity;
}
@override
EvaluationComplexity visitTypeLiteral(ir.TypeLiteral node) {
visitInContext(node.type, VariableUse.explicit);
return _evaluateImplicitConstant(node);
}
@override
EvaluationComplexity visitIsExpression(ir.IsExpression node) {
node.operand = _handleExpression(node.operand);
EvaluationComplexity complexity = _lastExpressionComplexity;
visitInContext(node.type, VariableUse.explicit);
if (complexity.isConstant) {
return _evaluateImplicitConstant(node);
}
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitAsExpression(ir.AsExpression node) {
node.operand = _handleExpression(node.operand);
EvaluationComplexity complexity = _lastExpressionComplexity;
visitInContext(node.type,
node.isTypeError ? VariableUse.implicitCast : VariableUse.explicit);
if (complexity.isConstant) {
return _evaluateImplicitConstant(node);
}
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitNullCheck(ir.NullCheck node) {
node.operand = _handleExpression(node.operand);
EvaluationComplexity complexity = _lastExpressionComplexity;
if (complexity.isConstant) {
return _evaluateImplicitConstant(node);
}
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitAwaitExpression(ir.AwaitExpression node) {
node.operand = _handleExpression(node.operand);
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitYieldStatement(ir.YieldStatement node) {
node.expression = _handleExpression(node.expression);
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitLoadLibrary(ir.LoadLibrary node) {
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitCheckLibraryIsLoaded(ir.CheckLibraryIsLoaded node) {
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitFunctionNode(ir.FunctionNode node) {
final parent = node.parent;
VariableUse parameterUsage = parent is ir.Member
? VariableUse.memberParameter(parent)
: VariableUse.localParameter(parent as ir.LocalFunction?);
visitNodesInContext(node.typeParameters, parameterUsage);
for (ir.VariableDeclaration declaration in node.positionalParameters) {
_handleVariableDeclaration(declaration, parameterUsage);
}
for (ir.VariableDeclaration declaration in node.namedParameters) {
_handleVariableDeclaration(declaration, parameterUsage);
}
visitInContext(
node.returnType,
parent is ir.Member
? VariableUse.memberReturnType(parent)
: VariableUse.localReturnType(parent as ir.LocalFunction));
if (node.body != null) {
visitNode(node.body!);
}
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitListLiteral(ir.ListLiteral node) {
EvaluationComplexity complexity =
visitInContext(node.typeArgument, VariableUse.listLiteral);
complexity = complexity.combine(visitExpressions(node.expressions));
if (node.isConst) {
return const EvaluationComplexity.constant();
} else {
return complexity.makeEager();
}
}
@override
EvaluationComplexity visitSetLiteral(ir.SetLiteral node) {
EvaluationComplexity complexity =
visitInContext(node.typeArgument, VariableUse.setLiteral);
complexity = complexity.combine(visitExpressions(node.expressions));
if (node.isConst) {
return const EvaluationComplexity.constant();
}
if (complexity.isLazy) return complexity;
if (node.expressions.every(_isWellBehavedEagerHashKey)) {
// Includes empty set literals.
return complexity.makeEager();
}
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitMapLiteral(ir.MapLiteral node) {
EvaluationComplexity complexity =
visitInContext(node.keyType, VariableUse.mapLiteral);
complexity = complexity
.combine(visitInContext(node.valueType, VariableUse.mapLiteral));
complexity = complexity.combine(visitNodes(node.entries));
if (node.isConst) {
return const EvaluationComplexity.constant();
}
if (node.entries.length > 10) return const EvaluationComplexity.lazy();
if (complexity.isLazy) return complexity;
if (node.entries
.map((entry) => entry.key)
.every(_isWellBehavedEagerHashKey)) {
// Includes empty map literals.
return complexity.makeEager();
}
return const EvaluationComplexity.lazy();
}
bool _isWellBehavedEagerHashKey(ir.Expression key) {
// Well-behaved eager keys for LinkedHashMap and LinkedHashSet must not
// indirectly use any lazy-initialized variables.
//
// TODO(45681): Improve the analysis. (1) Use static type of the [key]
// expression. (2) Use information about the class heirarchy and overloading
// of `get:hashCode` to detect safe implementations. This will pick up a lot
// of enum and enum-like classes.
if (key is ir.ConstantExpression) {
if (key.constant is ir.StringConstant) return true;
if (key.constant is ir.IntConstant) return true;
if (key.constant is ir.DoubleConstant) return true;
if (key.constant is ir.StaticTearOffConstant) return true;
}
return false;
}
@override
EvaluationComplexity visitMapLiteralEntry(ir.MapLiteralEntry node) {
node.key = _handleExpression(node.key);
EvaluationComplexity keyComplexity = _lastExpressionComplexity;
node.value = _handleExpression(node.value);
EvaluationComplexity valueComplexity = _lastExpressionComplexity;
return keyComplexity.combine(valueComplexity);
}
@override
EvaluationComplexity visitNullLiteral(ir.NullLiteral node) =>
_evaluateImplicitConstant(node);
@override
EvaluationComplexity visitStringLiteral(ir.StringLiteral node) =>
_evaluateImplicitConstant(node);
@override
EvaluationComplexity visitIntLiteral(ir.IntLiteral node) =>
_evaluateImplicitConstant(node);
@override
EvaluationComplexity visitDoubleLiteral(ir.DoubleLiteral node) =>
_evaluateImplicitConstant(node);
@override
EvaluationComplexity visitSymbolLiteral(ir.SymbolLiteral node) =>
_evaluateImplicitConstant(node);
@override
EvaluationComplexity visitBoolLiteral(ir.BoolLiteral node) =>
_evaluateImplicitConstant(node);
@override
EvaluationComplexity visitStringConcatenation(ir.StringConcatenation node) {
EvaluationComplexity complexity = visitExpressions(node.expressions);
if (complexity.isConstant) {
return _evaluateImplicitConstant(node);
}
return complexity;
}
@override
EvaluationComplexity visitStaticGet(ir.StaticGet node) {
ir.Member target = node.target;
if (target is ir.Field) {
return target.isConst
? const EvaluationComplexity.constant()
: EvaluationComplexity.eager(fields: <ir.Field>{target});
} else if (target is ir.Procedure &&
target.kind == ir.ProcedureKind.Method) {
return _evaluateImplicitConstant(node);
}
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitStaticTearOff(ir.StaticTearOff node) {
return _evaluateImplicitConstant(node);
}
@override
EvaluationComplexity visitStaticSet(ir.StaticSet node) {
node.value = _handleExpression(node.value);
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitStaticInvocation(ir.StaticInvocation node) {
if (node.arguments.types.isNotEmpty) {
VariableUse usage;
if (node.target.kind == ir.ProcedureKind.Factory) {
usage = VariableUse.constructorTypeArgument(node.target);
} else {
usage = VariableUse.staticTypeArgument(node.target);
}
visitNodesInContext(node.arguments.types, usage);
}
EvaluationComplexity complexity = visitArguments(node.arguments);
if (complexity.isConstant && node.target == _coreTypes.identicalProcedure) {
return _evaluateImplicitConstant(node);
}
return node.isConst
? const EvaluationComplexity.constant()
: const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitArguments(ir.Arguments node) {
EvaluationComplexity combinedComplexity = visitExpressions(node.positional);
for (int i = 0; i < node.named.length; i++) {
node.named[i].value = _handleExpression(node.named[i].value);
combinedComplexity =
combinedComplexity.combine(_lastExpressionComplexity);
}
return combinedComplexity;
}
@override
EvaluationComplexity visitConstructorInvocation(
ir.ConstructorInvocation node) {
ir.Constructor target = node.target;
ir.Class enclosingClass = target.enclosingClass;
// TODO(45681): Investigate if other initializers should be made eager.
// Lazily constructing cells pessimizes certain uses of late variables, so
// we ensure they get constructed eagerly.
if (enclosingClass == _coreTypes.cellClass) {
return EvaluationComplexity.eager();
}
if (node.arguments.types.isNotEmpty) {
visitNodesInContext(
node.arguments.types, VariableUse.constructorTypeArgument(target));
}
visitArguments(node.arguments);
return node.isConst
? const EvaluationComplexity.constant()
: const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitConditionalExpression(
ir.ConditionalExpression node) {
node.condition = _handleExpression(node.condition);
EvaluationComplexity conditionComplexity = _lastExpressionComplexity;
node.then = _handleExpression(node.then);
EvaluationComplexity thenComplexity = _lastExpressionComplexity;
node.otherwise = _handleExpression(node.otherwise);
EvaluationComplexity elseComplexity = _lastExpressionComplexity;
EvaluationComplexity complexity =
conditionComplexity.combine(thenComplexity).combine(elseComplexity);
if (complexity.isConstant) {
return _evaluateImplicitConstant(node);
}
// Don't visit `node.staticType`.
return complexity;
}
@override
EvaluationComplexity visitInstanceInvocation(ir.InstanceInvocation node) {
node.receiver = _handleExpression(node.receiver);
EvaluationComplexity receiverComplexity = _lastExpressionComplexity;
if (node.arguments.types.isNotEmpty) {
ir.TreeNode receiver = node.receiver;
assert(
!(receiver is ir.VariableGet &&
receiver.variable.parent is ir.LocalFunction),
"Unexpected local function invocation ${node} "
"(${node.runtimeType}).");
VariableUse usage = VariableUse.instanceTypeArgument(node);
visitNodesInContext(node.arguments.types, usage);
}
EvaluationComplexity complexity = visitArguments(node.arguments);
ir.Member interfaceTarget = node.interfaceTarget;
if (receiverComplexity.combine(complexity).isConstant &&
interfaceTarget is ir.Procedure &&
interfaceTarget.kind == ir.ProcedureKind.Operator) {
// Only operator invocations can be part of constant expressions so we
// only try to compute an implicit constant when the receiver and all
// arguments are constant - and are used in an operator call.
return _evaluateImplicitConstant(node);
}
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitInstanceGetterInvocation(
ir.InstanceGetterInvocation node) {
node.receiver = _handleExpression(node.receiver);
if (node.arguments.types.isNotEmpty) {
ir.TreeNode receiver = node.receiver;
assert(
!(receiver is ir.VariableGet &&
receiver.variable.parent is ir.LocalFunction),
"Unexpected local function invocation ${node} "
"(${node.runtimeType}).");
VariableUse usage = VariableUse.instanceTypeArgument(node);
visitNodesInContext(node.arguments.types, usage);
}
visitArguments(node.arguments);
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitDynamicInvocation(ir.DynamicInvocation node) {
node.receiver = _handleExpression(node.receiver);
if (node.arguments.types.isNotEmpty) {
ir.TreeNode receiver = node.receiver;
assert(
!(receiver is ir.VariableGet &&
receiver.variable.parent is ir.LocalFunction),
"Unexpected local function invocation ${node} "
"(${node.runtimeType}).");
VariableUse usage = VariableUse.instanceTypeArgument(node);
visitNodesInContext(node.arguments.types, usage);
}
visitArguments(node.arguments);
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitFunctionInvocation(ir.FunctionInvocation node) {
node.receiver = _handleExpression(node.receiver);
if (node.arguments.types.isNotEmpty) {
assert(
!(node.receiver is ir.VariableGet &&
((node.receiver as ir.VariableGet).variable.parent
is ir.LocalFunction)),
"Unexpected local function invocation ${node} "
"(${node.runtimeType}).");
VariableUse usage = VariableUse.instanceTypeArgument(node);
visitNodesInContext(node.arguments.types, usage);
}
visitArguments(node.arguments);
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitLocalFunctionInvocation(
ir.LocalFunctionInvocation node) {
_markVariableAsUsed(node.variable, VariableUse.explicit);
if (node.arguments.types.isNotEmpty) {
VariableUse usage =
VariableUse.localTypeArgument(node.localFunction, node);
visitNodesInContext(node.arguments.types, usage);
}
visitArguments(node.arguments);
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitEqualsNull(ir.EqualsNull node) {
node.expression = _handleExpression(node.expression);
EvaluationComplexity receiverComplexity = _lastExpressionComplexity;
if (receiverComplexity.isConstant) {
return _evaluateImplicitConstant(node);
}
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitEqualsCall(ir.EqualsCall node) {
node.left = _handleExpression(node.left);
EvaluationComplexity leftComplexity = _lastExpressionComplexity;
node.right = _handleExpression(node.right);
EvaluationComplexity rightComplexity = _lastExpressionComplexity;
if (leftComplexity.combine(rightComplexity).isConstant) {
return _evaluateImplicitConstant(node);
}
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitInstanceGet(ir.InstanceGet node) {
node.receiver = _handleExpression(node.receiver);
EvaluationComplexity complexity = _lastExpressionComplexity;
if (complexity.isConstant && node.name.text == 'length') {
return _evaluateImplicitConstant(node);
}
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitInstanceTearOff(ir.InstanceTearOff node) {
node.receiver = _handleExpression(node.receiver);
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitDynamicGet(ir.DynamicGet node) {
node.receiver = _handleExpression(node.receiver);
EvaluationComplexity complexity = _lastExpressionComplexity;
if (complexity.isConstant && node.name.text == 'length') {
return _evaluateImplicitConstant(node);
}
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitFunctionTearOff(ir.FunctionTearOff node) {
node.receiver = _handleExpression(node.receiver);
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitInstanceSet(ir.InstanceSet node) {
node.receiver = _handleExpression(node.receiver);
node.value = _handleExpression(node.value);
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitDynamicSet(ir.DynamicSet node) {
node.receiver = _handleExpression(node.receiver);
node.value = _handleExpression(node.value);
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitNot(ir.Not node) {
node.operand = _handleExpression(node.operand);
EvaluationComplexity complexity = _lastExpressionComplexity;
if (complexity.isConstant) {
return _evaluateImplicitConstant(node);
}
return complexity;
}
@override
EvaluationComplexity visitLogicalExpression(ir.LogicalExpression node) {
node.left = _handleExpression(node.left);
EvaluationComplexity leftComplexity = _lastExpressionComplexity;
node.right = _handleExpression(node.right);
EvaluationComplexity rightComplexity = _lastExpressionComplexity;
EvaluationComplexity complexity = leftComplexity.combine(rightComplexity);
if (complexity.isConstant) {
return _evaluateImplicitConstant(node);
}
return complexity;
}
@override
EvaluationComplexity visitLet(ir.Let node) {
visitNode(node.variable);
node.body = _handleExpression(node.body);
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitBlockExpression(ir.BlockExpression node) {
visitNode(node.body);
node.value = _handleExpression(node.value);
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitCatch(ir.Catch node) {
visitInContext(node.guard, VariableUse.explicit);
if (node.exception != null) {
visitNode(node.exception!);
}
if (node.stackTrace != null) {
visitNode(node.stackTrace!);
}
visitInVariableScope(node, () {
visitNode(node.body);
});
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitInstantiation(ir.Instantiation node) {
EvaluationComplexity typeArgumentsComplexity = visitNodesInContext(
node.typeArguments, VariableUse.instantiationTypeArgument(node));
node.expression = _handleExpression(node.expression);
EvaluationComplexity expressionComplexity = _lastExpressionComplexity;
EvaluationComplexity complexity =
typeArgumentsComplexity.combine(expressionComplexity);
if (complexity.isConstant) {
return _evaluateImplicitConstant(node);
}
return complexity;
}
@override
EvaluationComplexity visitThrow(ir.Throw node) {
node.expression = _handleExpression(node.expression);
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitRethrow(ir.Rethrow node) =>
const EvaluationComplexity.lazy();
@override
EvaluationComplexity visitBlock(ir.Block node) {
visitNodes(node.statements);
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitAssertStatement(ir.AssertStatement node) {
visitInVariableScope(node, () {
node.condition = _handleExpression(node.condition);
if (node.message != null) {
node.message = _handleExpression(node.message!);
}
});
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitReturnStatement(ir.ReturnStatement node) {
if (node.expression != null) {
node.expression = _handleExpression(node.expression!);
}
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitEmptyStatement(ir.EmptyStatement node) {
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitExpressionStatement(ir.ExpressionStatement node) {
node.expression = _handleExpression(node.expression);
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitSwitchStatement(ir.SwitchStatement node) {
node.expression = _handleExpression(node.expression);
visitInVariableScope(node, () {
visitNodes(node.cases);
});
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitSwitchCase(ir.SwitchCase node) {
visitNode(node.body);
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitContinueSwitchStatement(
ir.ContinueSwitchStatement node) {
registerContinueSwitch();
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitBreakStatement(ir.BreakStatement node) {
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitLabeledStatement(ir.LabeledStatement node) {
visitNode(node.body);
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitFieldInitializer(ir.FieldInitializer node) {
node.value = _handleExpression(node.value);
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitLocalInitializer(ir.LocalInitializer node) {
if (node.variable.initializer != null) {
node.variable.initializer = _handleExpression(node.variable.initializer!);
}
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitSuperInitializer(ir.SuperInitializer node) {
if (node.arguments.types.isNotEmpty) {
visitNodesInContext(node.arguments.types,
VariableUse.constructorTypeArgument(node.target));
}
visitArguments(node.arguments);
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitRedirectingInitializer(
ir.RedirectingInitializer node) {
if (node.arguments.types.isNotEmpty) {
visitNodesInContext(node.arguments.types,
VariableUse.constructorTypeArgument(node.target));
}
visitArguments(node.arguments);
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitAssertInitializer(ir.AssertInitializer node) {
visitNode(node.statement);
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitIfStatement(ir.IfStatement node) {
EvaluationComplexity conditionComplexity = visitNode(node.condition);
if (conditionComplexity.isFreshConstant) {}
visitNode(node.then);
if (node.otherwise != null) {
visitNode(node.otherwise!);
}
return const EvaluationComplexity.lazy();
}
@override
EvaluationComplexity visitConstantExpression(ir.ConstantExpression node) {
if (node.constant is ir.UnevaluatedConstant) {
node.constant = _constantEvaluator.evaluate(_staticTypeContext, node);
}
return const EvaluationComplexity.constant();
}
/// Returns true if the node is a field, or a constructor (factory or
/// generative).
bool _isFieldOrConstructor(ir.Node node) =>
node is ir.Constructor ||
node is ir.Field ||
(node is ir.Procedure && node.isFactory);
void _analyzeTypeVariable(ir.TypeParameterType type, VariableUse usage) {
assert((usage as dynamic) != null); // TODO(48820): Remove.
final outermost = _outermostNode;
if (outermost is ir.Member) {
TypeVariableTypeWithContext typeVariable =
TypeVariableTypeWithContext(type, outermost);
switch (typeVariable.kind) {
case TypeVariableKind.cls:
if (_isFieldOrConstructor(outermost)) {
// Class type variable used in a field or constructor.
_useTypeVariableAsLocal(typeVariable, usage);
} else {
// Class type variable used in a method.
_registerNeedsThis(usage);
}
break;
case TypeVariableKind.method:
case TypeVariableKind.local:
_useTypeVariableAsLocal(typeVariable, usage);
break;
case TypeVariableKind.function:
// The type variable is a function type variable, like `T` in
//
// List<void Function<T>(T)> list;
//
// which doesn't correspond to a captured local variable.
}
}
}
/// If [onlyForRtiChecks] is true, the variable will be added to a list
/// indicating it *may* be used only if runtime type information is checked.
void _useTypeVariableAsLocal(
TypeVariableTypeWithContext typeVariable, VariableUse usage) {
_markVariableAsUsed(typeVariable, usage);
}
}
enum ComplexityLevel {
constant,
potentiallyEager,
definitelyLazy,
}
class EvaluationComplexity {
final ComplexityLevel level;
final Set<ir.Field>? fields;
final ir.Constant? constant;
const EvaluationComplexity.constant([this.constant])
: level = ComplexityLevel.constant,
fields = null;
// TODO(johnniwinther): Use this to collect data on the size of the
// initializer.
EvaluationComplexity.eager({this.fields})
: level = ComplexityLevel.potentiallyEager,
constant = null;
const EvaluationComplexity.lazy()
: level = ComplexityLevel.definitelyLazy,
fields = null,
constant = null;
EvaluationComplexity combine(EvaluationComplexity other) {
if (identical(this, other)) {
return this;
} else if (isLazy || other.isLazy) {
return const EvaluationComplexity.lazy();
} else if (isEager || other.isEager) {
if (fields != null && other.fields != null) {
fields!.addAll(other.fields!);
return this;
} else if (fields != null) {
return this;
} else {
return other;
}
} else if (isConstant && other.isConstant) {
return const EvaluationComplexity.constant();
} else if (isEager) {
assert(other.isConstant);
return this;
} else {
assert(isConstant);
assert(other.isEager);
return other;
}
}
EvaluationComplexity makeEager() {
if (isLazy || isEager) {
return this;
} else {
return EvaluationComplexity.eager();
}
}
bool get isConstant => level == ComplexityLevel.constant;
bool get isFreshConstant => isConstant && constant != null;
bool get isEager => level == ComplexityLevel.potentiallyEager;
bool get isLazy => level == ComplexityLevel.definitelyLazy;
/// Returns a short textual representation used for testing.
String get shortText {
StringBuffer sb = StringBuffer();
switch (level) {
case ComplexityLevel.constant:
sb.write('constant');
break;
case ComplexityLevel.potentiallyEager:
sb.write('eager');
if (fields != null) {
sb.write('&fields=[');
List<String> names = fields!.map((f) => f.name.text).toList()..sort();
sb.write(names.join(','));
sb.write(']');
}
break;
case ComplexityLevel.definitelyLazy:
sb.write('lazy');
break;
default:
throw UnsupportedError("Unexpected complexity level $level");
}
return sb.toString();
}
@override
String toString() => 'InitializerComplexity($shortText)';
}