Flow analysis: rework of promotionInfo data structure.

This change updates `FlowModel.promotionInfo`, the primary data
structure used by flow analysis to track program state, so that
instead of being a `Map<int, PromotionModel<Type>>`, it is represented
by a new data structure called a `FlowLink`, an immutable data
structure describing program state in a way that's particularly
optimized for flow analysis's usage patterns.

Like a map, a `FlowLink` data structure represents a collection of
key/value pairs (where the keys are integers), however instead of
storing the keys and values in a hashtable, each `FlowLink` object
contains a single key/value pair and a pointer to a previous
`FlowLink` object. The value associated with a given key can be looked
up by starting with the current `FlowLink` and walking backwards
through the linked list of `previous` pointers until a matching key is
found. (An empty map is represented by `null`). This makes it an
`O(1)` operation to update the promotion state associated with a
single promotion key (an operation that flow analysis performs
frequently), since all that is required is a single allocation.

If the `previous` pointers are regarded as parent pointers, all the
`FlowLink` objects produced by a given run of flow analysis form a
tree that mirrors the dominator tree of the code being analyzed.

To optimize reads of `FlowLink` data structures, there is a
`FlowLinkReader` class that keeps track of a lookup table reflecting
the implicit map represented by a given `FlowLink` object; this table
can be updated to reflect a different `FlowLink` object in `O(n)`
time, where `n` is the number of edges between the two `FlowLink`
objects in the tree. Since flow analysis is based on a depth-first
traversal of the syntax tree of the code being analyzed, it has a high
degree of tree locality in the `FlowLink` objects it needs to be able
to read, so these `O(n)` updates do not consume much CPU.

The `FlowLinkReader` class is also able to compute a difference
between the program states represented by two `FlowLink` objects, in
`O(n)` time, where `n` is the number of edges between the two
`FlowLink` objects in the tree. This is used by flow analysis to
compute the program state after a control flow join, so that it does
not need to spend any time examining promotion keys that are unchanged
since the corresponding control flow split.

For more information about the `FlowLink` data structure and how it
works, see the comments in `flow_link.dart`.

This change improves the performance of CFE compilation fairly
substantially:

    instructions:u: -0.8167% +/- 0.0007% (-158214865.67 +/- 130252.25)
    branches:u: -0.4694% +/- 0.0009% (-18575169.00 +/- 37220.97)
    branch-misses:u: -1.0009% +/- 0.7189% (-575742.67 +/- 413521.70)
Change-Id: Ia87458ee599977e6efdc9f0e7aa283a41f84f616
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/326900
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Morgan :) <davidmorgan@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
This commit is contained in:
Paul Berry 2023-09-25 15:41:20 +00:00 committed by Commit Queue
parent de302d7f3b
commit 2887fe0b2c
7 changed files with 1101 additions and 406 deletions

View file

@ -7,6 +7,7 @@ import 'package:meta/meta.dart';
import '../type_inference/assigned_variables.dart';
import '../type_inference/promotion_key_store.dart';
import '../type_inference/type_operations.dart';
import 'flow_link.dart';
/// [PropertyTarget] representing an implicit reference to the target of the
/// innermost enclosing cascade expression.
@ -2017,31 +2018,14 @@ class FlowAnalysisDebug<Node extends Object, Statement extends Node,
class FlowModel<Type extends Object> {
final Reachability reachable;
/// For each promotable thing being tracked by flow analysis, the
/// corresponding model.
///
/// Flow analysis has no awareness of scope, so variables that are out of
/// scope are retained in the map until such time as their declaration no
/// longer dominates the control flow. So, for example, if a variable is
/// declared inside the `then` branch of an `if` statement, and the `else`
/// branch of the `if` statement ends in a `return` statement, then the
/// variable remains in the map after the `if` statement ends, even though the
/// variable is not in scope anymore. This should not have any effect on
/// analysis results for error-free code, because it is an error to refer to a
/// variable that is no longer in scope.
///
/// Keys are the unique integers assigned by
/// [_FlowAnalysisImpl._promotionKeyStore].
final Map<int, PromotionModel<Type> /*!*/ > promotionInfo;
/// [PromotionInfo] object tracking the [PromotionModel]s for each promotable
/// thing being tracked by flow analysis.
final PromotionInfo<Type>? promotionInfo;
/// Creates a state object with the given [reachable] status. All variables
/// are assumed to be unpromoted and already assigned, so joining another
/// state with this one will have no effect on it.
FlowModel(Reachability reachable)
: this.withInfo(
reachable,
const {},
);
FlowModel(Reachability reachable) : this.withInfo(reachable, null);
@visibleForTesting
FlowModel.withInfo(this.reachable, this.promotionInfo);
@ -2063,24 +2047,57 @@ class FlowModel<Type extends Object> {
/// assignments that occurred during the `try` block, to the extent that they
/// weren't invalidated by later assignments in the `finally` block.
FlowModel<Type> attachFinally(FlowModelHelper<Type> helper,
FlowModel<Type> beforeFinally, FlowModel<Type> afterFinally) {
{required FlowModel<Type> beforeFinally,
required FlowModel<Type> afterFinally,
required FlowModel<Type> ancestor}) {
// If nothing happened in the `finally` block, then nothing needs to be
// done.
if (beforeFinally == afterFinally) return this;
// If nothing happened in the `try` block, then no rebase is needed.
if (beforeFinally == ancestor && ancestor == this) return afterFinally;
// Code that follows the `try/finally` is reachable iff the end of the `try`
// block is reachable _and_ the end of the `finally` block is reachable.
Reachability newReachable = afterFinally.reachable.rebaseForward(reachable);
// Consider each promotion key that is common to all three models.
Map<int, PromotionModel<Type>> newPromotionInfo =
<int, PromotionModel<Type>>{};
bool promotionInfoMatchesThis = true;
bool promotionInfoMatchesAfterFinally = true;
FlowModel<Type> result = setReachability(newReachable);
List<(SsaNode<Type>?, SsaNode<Type>?)> fieldPromotionsToReapply = [];
for (MapEntry<int, PromotionModel<Type>> entry in promotionInfo.entries) {
int promotionKey = entry.key;
PromotionModel<Type> thisModel = entry.value;
var (
ancestor: PromotionInfo<Type>? ancestorInfo,
:List<FlowLinkDiffEntry<PromotionInfo<Type>>> entries
) = helper.reader.diff(promotionInfo, afterFinally.promotionInfo);
assert(ancestor.promotionInfo == ancestorInfo);
for (var FlowLinkDiffEntry(
key: int promotionKey,
:PromotionInfo<Type>? left,
:PromotionInfo<Type>? right
) in entries) {
PromotionModel<Type>? thisModel = left?.model;
PromotionModel<Type>? beforeFinallyModel =
beforeFinally.promotionInfo[promotionKey];
PromotionModel<Type>? afterFinallyModel =
afterFinally.promotionInfo[promotionKey];
beforeFinally.promotionInfo?.get(helper, promotionKey);
PromotionModel<Type>? afterFinallyModel = right?.model;
if (thisModel == null) {
if (afterFinallyModel == null) {
// This should never happen, because we are iterating through
// promotion keys that are different between the `this` and
// `afterFinally` models.
assert(false);
continue;
}
// The promotion key is in the `afterFinally` model but not in `this`
// model. This happens when either:
// - There is a variable declared inside the `finally` block, or:
// - A field is promoted inside the `finally` block that wasn't
// previously promoted (and isn't promoted in the `try` block).
//
// In the first case, it doesn't matter what we do, because the variable
// won't be in scope after the try/finally statement. But in the second
// case, we need to preserve the promotion from the `finally` block.
result =
result.updatePromotionInfo(helper, promotionKey, afterFinallyModel);
continue;
}
if (afterFinallyModel == null) {
// The promotion key is in `this` model but not in the `afterFinally`
// model. This happens when either:
@ -2091,8 +2108,7 @@ class FlowModel<Type extends Object> {
// In the first case, it doesn't matter what we do, because the variable
// won't be in scope after the try/finally statement. But in the second
// case, we need to preserve the promotion from the `try` block.
newPromotionInfo[promotionKey] = thisModel;
promotionInfoMatchesAfterFinally = false;
result = result.updatePromotionInfo(helper, promotionKey, thisModel);
continue;
}
// We can just use the "write captured" state from the `finally` block,
@ -2157,28 +2173,7 @@ class FlowModel<Type extends Object> {
newAssigned,
newUnassigned,
newSsaNode);
newPromotionInfo[promotionKey] = newModel;
if (!identical(newModel, thisModel)) promotionInfoMatchesThis = false;
if (!identical(newModel, afterFinallyModel)) {
promotionInfoMatchesAfterFinally = false;
}
}
for (var MapEntry(
key: int promotionKey,
value: PromotionModel<Type> afterFinallyModel
) in afterFinally.promotionInfo.entries) {
if (promotionInfo.containsKey(promotionKey)) continue;
// The promotion key is in the `afterFinally` model but not in `this`
// model. This happens when either:
// - There is a variable declared inside the `finally` block, or:
// - A field is promoted inside the `finally` block that wasn't
// previously promoted (and isn't promoted in the `try` block).
//
// In the first case, it doesn't matter what we do, because the variable
// won't be in scope after the try/finally statement. But in the second
// case, we need to preserve the promotion from the `finally` block.
newPromotionInfo[promotionKey] = afterFinallyModel;
promotionInfoMatchesThis = false;
result = result.updatePromotionInfo(helper, promotionKey, newModel);
}
for (var (SsaNode<Type>? thisSsaNode, SsaNode<Type>? afterFinallySsaNode)
in fieldPromotionsToReapply) {
@ -2186,25 +2181,15 @@ class FlowModel<Type extends Object> {
// Variable was write-captured, so no fields can be promoted anymore.
continue;
}
thisSsaNode._applyPropertyPromotions(
result = thisSsaNode._applyPropertyPromotions(
helper,
thisSsaNode,
afterFinallySsaNode,
beforeFinally.promotionInfo,
afterFinally.promotionInfo,
newPromotionInfo);
result);
}
assert(promotionInfoMatchesThis ==
_promotionInfosEqual(newPromotionInfo, promotionInfo));
assert(promotionInfoMatchesAfterFinally ==
_promotionInfosEqual(newPromotionInfo, afterFinally.promotionInfo));
if (promotionInfoMatchesThis) {
newPromotionInfo = promotionInfo;
} else if (promotionInfoMatchesAfterFinally) {
newPromotionInfo = afterFinally.promotionInfo;
}
return _identicalOrNew(this, afterFinally, newReachable, newPromotionInfo);
return result;
}
/// Updates the state to indicate that the given [writtenVariables] are no
@ -2229,24 +2214,26 @@ class FlowModel<Type extends Object> {
/// be able to remove this method.
FlowModel<Type> conservativeJoin(FlowModelHelper<Type> helper,
Iterable<int> writtenVariables, Iterable<int> capturedVariables) {
FlowModel<Type>? newModel;
FlowModel<Type> result = this;
for (int variableKey in writtenVariables) {
PromotionModel<Type>? info = promotionInfo[variableKey];
PromotionModel<Type>? info =
result.promotionInfo?.get(helper, variableKey);
if (info == null) continue;
PromotionModel<Type> newInfo =
info.discardPromotionsAndMarkNotUnassigned();
if (!identical(info, newInfo)) {
(newModel ??= _clone()).promotionInfo[variableKey] = newInfo;
result = result.updatePromotionInfo(helper, variableKey, newInfo);
}
}
for (int variableKey in capturedVariables) {
PromotionModel<Type>? info = promotionInfo[variableKey];
PromotionModel<Type>? info =
result.promotionInfo?.get(helper, variableKey);
if (info == null) continue;
if (!info.writeCaptured) {
(newModel ??= _clone()).promotionInfo[variableKey] =
info.writeCapture();
result = result.updatePromotionInfo(
helper, variableKey, info.writeCapture());
// Note: there's no need to discard dependent property promotions,
// because when deciding whether a property is promoted,
// [_FlowAnalysisImpl._handleProperty] checks whether the variable is
@ -2254,7 +2241,7 @@ class FlowModel<Type extends Object> {
}
}
return newModel ?? this;
return result;
}
/// Register a declaration of the variable whose key is [variableKey].
@ -2279,7 +2266,8 @@ class FlowModel<Type extends Object> {
/// in the target's [SsaNode._promotableProperties] map.
PromotionModel<Type> infoFor(FlowModelHelper<Type> helper, int promotionKey,
{required SsaNode<Type> ssaNode}) =>
promotionInfo[promotionKey] ?? new PromotionModel.fresh(ssaNode: ssaNode);
promotionInfo?.get(helper, promotionKey) ??
new PromotionModel.fresh(ssaNode: ssaNode);
/// Builds a [FlowModel] based on `this`, but extending the `tested` set to
/// include types from [other]. This is used at the bottom of certain kinds
@ -2289,27 +2277,25 @@ class FlowModel<Type extends Object> {
@visibleForTesting
FlowModel<Type> inheritTested(
FlowModelHelper<Type> helper, FlowModel<Type> other) {
Map<int, PromotionModel<Type>> newPromotionInfo =
<int, PromotionModel<Type>>{};
Map<int, PromotionModel<Type>> otherPromotionInfo = other.promotionInfo;
bool changed = false;
for (MapEntry<int, PromotionModel<Type>> entry in promotionInfo.entries) {
int promotionKey = entry.key;
PromotionModel<Type> promotionModel = entry.value;
PromotionModel<Type>? otherPromotionModel =
otherPromotionInfo[promotionKey];
FlowModel<Type> result = this;
for (var FlowLinkDiffEntry(
key: int promotionKey,
:PromotionInfo<Type>? left,
:PromotionInfo<Type>? right
) in helper.reader.diff(promotionInfo, other.promotionInfo).entries) {
PromotionModel<Type>? promotionModel = left?.model;
if (promotionModel == null) continue;
PromotionModel<Type>? otherPromotionModel = right?.model;
PromotionModel<Type> newPromotionModel = otherPromotionModel == null
? promotionModel
: PromotionModel.inheritTested(helper.typeOperations, promotionModel,
otherPromotionModel.tested);
newPromotionInfo[promotionKey] = newPromotionModel;
if (!identical(newPromotionModel, promotionModel)) changed = true;
}
if (changed) {
return new FlowModel<Type>.withInfo(reachable, newPromotionInfo);
} else {
return this;
if (!identical(newPromotionModel, promotionModel)) {
result =
result.updatePromotionInfo(helper, promotionKey, newPromotionModel);
}
}
return result;
}
/// Updates `this` flow model to account for any promotions and assignments
@ -2327,24 +2313,51 @@ class FlowModel<Type extends Object> {
// The rebased model is reachable iff both `this` and the new base are
// reachable.
Reachability newReachable = reachable.rebaseForward(base.reachable);
FlowModel<Type> result = base.setReachability(newReachable);
var (
:PromotionInfo<Type>? ancestor,
:List<FlowLinkDiffEntry<PromotionInfo<Type>>> entries
) = helper.reader.diff(promotionInfo, base.promotionInfo);
// If `this` matches the ancestor, then there are no state changes that need
// to be rewound and applied to `base`.
if (ancestor == promotionInfo) {
return result;
}
// If `base` matches the ancestor, then the act of rewinding `this` back to
// the ancestor, and then reapplying the rewound changes to `base`,
// reproduces `this` exactly (assuming reachability matches up properly).
if (base.promotionInfo == ancestor && reachable == newReachable) {
return this;
}
// Consider each promotion key in the new base model.
Map<int, PromotionModel<Type>> newPromotionInfo =
<int, PromotionModel<Type>>{};
bool promotionInfoMatchesThis = true;
bool promotionInfoMatchesBase = true;
for (MapEntry<int, PromotionModel<Type>> entry
in base.promotionInfo.entries) {
int promotionKey = entry.key;
PromotionModel<Type> baseModel = entry.value;
PromotionModel<Type>? thisModel = promotionInfo[promotionKey];
for (var FlowLinkDiffEntry(
key: int promotionKey,
:PromotionInfo<Type>? left,
:PromotionInfo<Type>? right
) in entries) {
PromotionModel<Type>? thisModel = left?.model;
if (thisModel == null) {
// Either this promotion key represents a variable that has newly come
// into scope since `thisModel`, or it represents a property that flow
// analysis became aware of since `thisModel`. In either case, the
// information in `baseModel` is up to date.
newPromotionInfo[promotionKey] = baseModel;
promotionInfoMatchesThis = false;
continue;
}
PromotionModel<Type>? baseModel = right?.model;
if (baseModel == null) {
// The promotion key exists in `this` model but not in the new `base`
// model. This happens when either:
// - The promotion key is associated with a local variable that was in
// scope at the time `this` model was created, but is no longer in
// scope as of the `base` model, or:
// - The promotion key is associated with a property that was promoted
// in `this` model.
//
// In the first case, it doesn't matter what we do, because the variable
// is no longer in scope. But in the second case, we need to preserve
// the promotion.
result = result.updatePromotionInfo(helper, promotionKey, thisModel);
continue;
}
// If the variable was write captured in either `this` or the new base,
@ -2387,41 +2400,15 @@ class FlowModel<Type extends Object> {
newAssigned,
newUnassigned,
newWriteCaptured ? null : baseModel.ssaNode);
newPromotionInfo[promotionKey] = newModel;
if (!identical(newModel, thisModel)) promotionInfoMatchesThis = false;
if (!identical(newModel, baseModel)) promotionInfoMatchesBase = false;
}
// Check promotion keys that exist in `this` model but not in the new `base`
// model. This happens when either:
// - The promotion key is associated with a local variable that was in scope
// at the time `this` model was created, but is no longer in scope as of
// the `base` model, or:
// - The promotion key is associated with a property that was promoted in
// `this` model.
//
// In the first case, it doesn't matter what we do, because the variable is
// no longer in scope. But in the second case, we need to preserve the
// promotion.
for (var MapEntry(
key: int promotionKey,
value: PromotionModel<Type> thisModel
) in promotionInfo.entries) {
if (!base.promotionInfo.containsKey(promotionKey)) {
newPromotionInfo[promotionKey] = thisModel;
promotionInfoMatchesBase = false;
}
}
assert(promotionInfoMatchesThis ==
_promotionInfosEqual(newPromotionInfo, promotionInfo));
assert(promotionInfoMatchesBase ==
_promotionInfosEqual(newPromotionInfo, base.promotionInfo));
if (promotionInfoMatchesThis) {
newPromotionInfo = promotionInfo;
} else if (promotionInfoMatchesBase) {
newPromotionInfo = base.promotionInfo;
result = result.updatePromotionInfo(helper, promotionKey, newModel);
}
return result;
}
return _identicalOrNew(this, base, newReachable, newPromotionInfo);
FlowModel<Type> setReachability(Reachability reachable) {
if (this.reachable == reachable) return this;
return new FlowModel<Type>.withInfo(reachable, promotionInfo);
}
/// Updates the state to indicate that the control flow path is unreachable.
@ -2566,7 +2553,11 @@ class FlowModel<Type extends Object> {
@visibleForTesting
FlowModel<Type> updatePromotionInfo(FlowModelHelper<Type> helper,
int promotionKey, PromotionModel<Type> model) {
return _clone()..promotionInfo[promotionKey] = model;
PromotionInfo<Type> newPromotionInfo = new PromotionInfo._(model,
key: promotionKey,
previous: promotionInfo,
previousForKey: helper.reader.get(promotionInfo, promotionKey));
return new FlowModel.withInfo(reachable, newPromotionInfo);
}
/// Updates the state to indicate that an assignment was made to [variable],
@ -2586,7 +2577,7 @@ class FlowModel<Type extends Object> {
{bool promoteToTypeOfInterest = true,
required Type unpromotedType}) {
FlowModel<Type>? newModel;
PromotionModel<Type>? infoForVar = promotionInfo[variableKey];
PromotionModel<Type>? infoForVar = promotionInfo?.get(helper, variableKey);
if (infoForVar != null) {
PromotionModel<Type> newInfoForVar = infoForVar.write(
nonPromotionReason, variableKey, writtenType, operations, newSsaNode,
@ -2600,12 +2591,6 @@ class FlowModel<Type extends Object> {
return newModel ?? this;
}
/// Makes a copy of `this` that can be safely edited.
FlowModel<Type> _clone() {
return new FlowModel<Type>.withInfo(
reachable, new Map<int, PromotionModel<Type>>.of(promotionInfo));
}
/// Common algorithm for [tryMarkNonNullable], [tryPromoteForTypeCast],
/// and [tryPromoteForTypeCheck]. Builds a [FlowModel] object describing the
/// effect of updating the [reference] by adding the [testedType] to the
@ -2682,94 +2667,59 @@ class FlowModel<Type extends Object> {
assert(
first.reachable.locallyReachable == second.reachable.locallyReachable);
assert(first.reachable.parent == second.reachable.parent);
Reachability newReachable = first.reachable;
Map<int, PromotionModel<Type>> newPromotionInfo =
FlowModel.joinPromotionInfo(
helper, first.promotionInfo, second.promotionInfo);
return FlowModel._identicalOrNew(
first, second, newReachable, newPromotionInfo);
return FlowModel.joinPromotionInfo(helper, first, second);
}
/// Joins two "promotion info" maps. See [join] for details.
@visibleForTesting
static Map<int, PromotionModel<Type>> joinPromotionInfo<Type extends Object>(
FlowModelHelper<Type> helper,
Map<int, PromotionModel<Type>> first,
Map<int, PromotionModel<Type>> second,
) {
if (identical(first, second)) return first;
if (first.isEmpty || second.isEmpty) {
return const {};
}
// TODO(jensj): How often is this empty?
Map<int, PromotionModel<Type>> newPromotionInfo =
<int, PromotionModel<Type>>{};
bool alwaysFirst = true;
bool alwaysSecond = true;
for (MapEntry<int, PromotionModel<Type>> entry in first.entries) {
int promotionKey = entry.key;
PromotionModel<Type>? secondModel = second[promotionKey];
if (secondModel == null) {
alwaysFirst = false;
} else {
PromotionModel<Type> joined = PromotionModel.join<Type>(
helper, entry.value, first, secondModel, second, newPromotionInfo);
newPromotionInfo[promotionKey] = joined;
if (!identical(joined, entry.value)) alwaysFirst = false;
if (!identical(joined, secondModel)) alwaysSecond = false;
}
}
if (alwaysFirst) return first;
if (alwaysSecond && newPromotionInfo.length == second.length) return second;
if (newPromotionInfo.isEmpty) return const {};
return newPromotionInfo;
}
/// Creates a new [FlowModel] object, unless it is equivalent to either
/// [first] or [second], in which case one of those objects is re-used.
static FlowModel<Type> _identicalOrNew<Type extends Object>(
static FlowModel<Type> joinPromotionInfo<Type extends Object>(
FlowModelHelper<Type> helper,
FlowModel<Type> first,
FlowModel<Type> second,
Reachability newReachable,
Map<int, PromotionModel<Type>> newPromotionInfo) {
if (first.reachable == newReachable &&
identical(first.promotionInfo, newPromotionInfo)) {
return first;
}
if (second.reachable == newReachable &&
identical(second.promotionInfo, newPromotionInfo)) {
return second;
}
FlowModel<Type> second) {
if (identical(first, second)) return first;
if (first.promotionInfo == null) return first;
if (second.promotionInfo == null) return second;
return new FlowModel<Type>.withInfo(newReachable, newPromotionInfo);
}
/// Determines whether the given "promotionInfo" maps are equivalent.
///
/// The equivalence check is shallow; if two models are not identical, we
/// return `false`.
static bool _promotionInfosEqual<Type extends Object>(
Map<int, PromotionModel<Type>> p1, Map<int, PromotionModel<Type>> p2) {
if (p1.length != p2.length) return false;
if (!p1.keys.toSet().containsAll(p2.keys)) return false;
for (MapEntry<int, PromotionModel<Type>> entry in p1.entries) {
PromotionModel<Type> p1Value = entry.value;
PromotionModel<Type>? p2Value = p2[entry.key];
if (!identical(p1Value, p2Value)) {
return false;
var (
:PromotionInfo<Type>? ancestor,
:List<FlowLinkDiffEntry<PromotionInfo<Type>>> entries
) = helper.reader.diff(first.promotionInfo, second.promotionInfo);
FlowModel<Type> newFlowModel =
new FlowModel.withInfo(first.reachable, ancestor);
for (var FlowLinkDiffEntry(
key: int promotionKey,
left: PromotionInfo<Type>? leftInfo,
right: PromotionInfo<Type>? rightInfo
) in entries) {
PromotionModel<Type>? firstModel = leftInfo?.model;
if (firstModel == null) {
continue;
}
PromotionModel<Type>? secondModel = rightInfo?.model;
if (secondModel == null) {
continue;
}
PromotionModel<Type> joined;
(joined, newFlowModel) = PromotionModel.join<Type>(helper, firstModel,
first.promotionInfo, secondModel, second.promotionInfo, newFlowModel);
newFlowModel =
newFlowModel.updatePromotionInfo(helper, promotionKey, joined);
}
return true;
return newFlowModel;
}
}
/// Interface used by [FlowModel] and [_Reference] methods to access
/// Convenience methods used by [FlowModel] and [_Reference] methods to access
/// variables in [_FlowAnalysisImpl].
@visibleForTesting
abstract class FlowModelHelper<Type extends Object> {
mixin FlowModelHelper<Type extends Object> {
/// [FlowLinkReader] object for efficiently looking up [PromotionModel]
/// objects in [FlowModel.promotionInfo] structures, or for computing the
/// difference between two [FlowModel.promotionInfo] structures.
final FlowLinkReader<PromotionInfo<Type>> reader =
new FlowLinkReader<PromotionInfo<Type>>();
/// Returns the client's representation of the type `bool`.
Type get boolType;
@ -2939,6 +2889,48 @@ class PatternVariableInfo<Variable> {
final Map<String, int> patternVariablePromotionKeys = {};
}
/// Map-like data structure recording the [PromotionModel]s for each promotable
/// thing (variable, property, `this`, or `super`) being tracked by flow
/// analysis.
///
/// Each instance of [PromotionInfo] is an immutable key/value pair binding a
/// single promotion [key] (a unique integer assigned by
/// [_FlowAnalysisImpl._promotionKeyStore] to track a particular promotable
/// thing) with an instance of [PromotionModel] describing the promotion state
/// of that thing.
///
/// Please see the documentation for [FlowLink] for more information about how
/// this data structure works.
///
/// Flow analysis has no awareness of scope, so variables that are out of
/// scope are retained in the map until such time as their declaration no
/// longer dominates the control flow. So, for example, if a variable is
/// declared inside the `then` branch of an `if` statement, and the `else`
/// branch of the `if` statement ends in a `return` statement, then the
/// variable remains in the map after the `if` statement ends, even though the
/// variable is not in scope anymore. This should not have any effect on
/// analysis results for error-free code, because it is an error to refer to a
/// variable that is no longer in scope.
@visibleForTesting
base class PromotionInfo<Type extends Object>
extends FlowLink<PromotionInfo<Type>> {
/// The [PromotionModel] associated with [key].
@visibleForTesting
final PromotionModel<Type> model;
PromotionInfo._(this.model,
{required super.key,
required super.previous,
required super.previousForKey});
/// Looks up the [PromotionModel] associated with [promotionKey] by walking
/// the linked list formed by [previous] to find the nearest link whose [key]
/// matches [promotionKey].
@visibleForTesting
PromotionModel<Type>? get(FlowModelHelper<Type> helper, int promotionKey) =>
helper.reader.get(this, promotionKey)?.model;
}
/// An instance of the [PromotionModel] class represents the information
/// gathered by flow analysis for a single variable or property at a single
/// point in the control flow of the function or method being analyzed.
@ -3307,20 +3299,20 @@ class PromotionModel<Type extends Object> {
/// Since properties of variables may be promoted, the caller must supply the
/// promotion info maps for the two flow control paths being joined
/// ([firstPromotionInfo] and [secondPromotionInfo]), as well as the promotion
/// info map being built for the join point ([newPromotionInfo]).
/// info map being built for the join point ([newFlowModel]).
///
/// If a non-null [propertySsaNode] is supplied, it is used as the SSA node
/// for the joined model, rather than joining the SSA nodes from `first` and
/// `second`. This avoids redundant join operations for properties, since
/// properties are joined recursively when this method is used on local
/// variables.
static PromotionModel<Type> join<Type extends Object>(
static (PromotionModel<Type>, FlowModel<Type>) join<Type extends Object>(
FlowModelHelper<Type> helper,
PromotionModel<Type> first,
Map<int, PromotionModel<Type>> firstPromotionInfo,
PromotionInfo<Type>? firstPromotionInfo,
PromotionModel<Type> second,
Map<int, PromotionModel<Type>> secondPromotionInfo,
Map<int, PromotionModel<Type>> newPromotionInfo,
PromotionInfo<Type>? secondPromotionInfo,
FlowModel<Type> newFlowModel,
{_PropertySsaNode<Type>? propertySsaNode}) {
TypeOperations<Type> typeOperations = helper.typeOperations;
List<Type>? newPromotedTypes = joinPromotedTypes(
@ -3333,13 +3325,25 @@ class PromotionModel<Type extends Object> {
List<Type> newTested = newWriteCaptured
? const []
: joinTested(first.tested, second.tested, typeOperations);
SsaNode<Type>? ssaNode = propertySsaNode;
if (ssaNode == null && !newWriteCaptured) {
ssaNode = SsaNode._join(helper, first.ssaNode!, firstPromotionInfo,
second.ssaNode!, secondPromotionInfo, newPromotionInfo);
SsaNode<Type>? newSsaNode = propertySsaNode;
if (newSsaNode == null && !newWriteCaptured) {
(newSsaNode, newFlowModel) = SsaNode._join(
helper,
first.ssaNode!,
firstPromotionInfo,
second.ssaNode!,
secondPromotionInfo,
newFlowModel);
}
return _identicalOrNew(first, second, newPromotedTypes, newTested,
newAssigned, newUnassigned, ssaNode);
PromotionModel<Type> newPromotionModel = _identicalOrNew(
first,
second,
newPromotedTypes,
newTested,
newAssigned,
newUnassigned,
newWriteCaptured ? null : newSsaNode);
return (newPromotionModel, newFlowModel);
}
/// Performs the portion of the "join" algorithm that applies to promotion
@ -3754,15 +3758,15 @@ class SsaNode<Type extends Object> {
/// [beforeFinallyInfo] is the promotion info map from the flow state at the
/// beginning of the `finally` block, and [afterFinallyInfo] is the promotion
/// info map from the flow state at the end of the `finally` block.
/// [newPromotionInfo] is the promotion info map for the flow state being
/// [newFlowModel] is the promotion info map for the flow state being
/// built (the flow state after the try/finally block).
void _applyPropertyPromotions<Type extends Object>(
FlowModel<Type> _applyPropertyPromotions<Type extends Object>(
FlowModelHelper<Type> helper,
SsaNode<Type> afterTrySsaNode,
SsaNode<Type> finallySsaNode,
Map<int, PromotionModel<Type>> beforeFinallyInfo,
Map<int, PromotionModel<Type>> afterFinallyInfo,
Map<int, PromotionModel<Type>> newPromotionInfo) {
PromotionInfo<Type>? beforeFinallyInfo,
PromotionInfo<Type>? afterFinallyInfo,
FlowModel<Type> newFlowModel) {
for (var MapEntry(
key: String propertyName,
value: _PropertySsaNode<Type> finallyPropertySsaNode
@ -3772,43 +3776,52 @@ class SsaNode<Type extends Object> {
// block by the conservative join in `tryFinallyStatement_finallyBegin`.
// So the property should have been unpromoted (and unknown) at the
// beginning of the `finally` block.
assert(beforeFinallyInfo[finallyPropertySsaNode.promotionKey] == null);
assert(
beforeFinallyInfo?.get(helper, finallyPropertySsaNode.promotionKey) ==
null);
// Therefore all we need to do is apply any promotions that are in force
// at the end of the `finally` block.
PromotionModel<Type>? afterFinallyModel =
afterFinallyInfo[finallyPropertySsaNode.promotionKey];
afterFinallyInfo?.get(helper, finallyPropertySsaNode.promotionKey);
_PropertySsaNode<Type> afterTryPropertySsaNode =
afterTrySsaNode._promotableProperties[propertyName] ??=
new _PropertySsaNode(helper.promotionKeyStore.makeTemporaryKey());
// Handle nested properties
_applyPropertyPromotions(
newFlowModel = _applyPropertyPromotions(
helper,
afterTryPropertySsaNode,
finallyPropertySsaNode,
beforeFinallyInfo,
afterFinallyInfo,
newPromotionInfo);
newFlowModel);
if (afterFinallyModel == null) continue;
List<Type>? afterFinallyPromotedTypes = afterFinallyModel.promotedTypes;
// The property was accessed in a promotion-relevant way in the `try`
// block, so we need to apply the promotions from the `finally` block to
// the flow model from the `try` block, and see what sticks.
PromotionModel<Type> newModel =
newPromotionInfo[afterTryPropertySsaNode.promotionKey] ??=
new PromotionModel.fresh(ssaNode: afterTryPropertySsaNode);
PromotionModel<Type>? newModel = newFlowModel.promotionInfo
?.get(helper, afterTryPropertySsaNode.promotionKey);
if (newModel == null) {
newModel = new PromotionModel.fresh(ssaNode: afterTryPropertySsaNode);
newFlowModel = newFlowModel.updatePromotionInfo(
helper, afterTryPropertySsaNode.promotionKey, newModel);
}
List<Type>? newPromotedTypes = newModel.promotedTypes;
List<Type>? rebasedPromotedTypes = PromotionModel.rebasePromotedTypes(
helper.typeOperations, afterFinallyPromotedTypes, newPromotedTypes);
if (!identical(newPromotedTypes, rebasedPromotedTypes)) {
newPromotionInfo[afterTryPropertySsaNode.promotionKey] =
newFlowModel = newFlowModel.updatePromotionInfo(
helper,
afterTryPropertySsaNode.promotionKey,
new PromotionModel<Type>(
promotedTypes: rebasedPromotedTypes,
tested: newModel.tested,
assigned: true,
unassigned: false,
ssaNode: newModel.ssaNode);
ssaNode: newModel.ssaNode));
}
}
return newFlowModel;
}
/// Joins the promotion information for the promotable properties of two SSA
@ -3818,14 +3831,14 @@ class SsaNode<Type extends Object> {
/// Since properties may themselves be promoted, the caller must supply the
/// promotion info maps for the two flow control paths being joined
/// ([firstPromotionInfo] and [secondPromotionInfo]), as well as the promotion
/// info map being built for the join point ([newPromotionInfo]).
void _joinProperties(
/// info map being built for the join point ([newFlowModel]).
FlowModel<Type> _joinProperties(
FlowModelHelper<Type> helper,
Map<String, _PropertySsaNode<Type>> first,
Map<int, PromotionModel<Type>> firstPromotionInfo,
PromotionInfo<Type>? firstPromotionInfo,
Map<String, _PropertySsaNode<Type>> second,
Map<int, PromotionModel<Type>> secondPromotionInfo,
final Map<int, PromotionModel<Type>> newPromotionInfo) {
PromotionInfo<Type>? secondPromotionInfo,
FlowModel<Type> newFlowModel) {
// If a property has been accessed along one of the two control flow paths
// being joined, but not the other, then it shouldn't be promoted after the
// join point, nor should any of its nested properties. So it is only
@ -3842,33 +3855,37 @@ class SsaNode<Type extends Object> {
// it might be promoted, so join the two promotion models to preserve the
// promotion.
PromotionModel<Type>? firstPromotionModel =
firstPromotionInfo[firstProperty.promotionKey];
firstPromotionInfo?.get(helper, firstProperty.promotionKey);
_PropertySsaNode<Type> propertySsaNode =
new _PropertySsaNode<Type>(newPromotionKey);
_promotableProperties[propertyName] = propertySsaNode;
if (firstPromotionModel != null) {
PromotionModel<Type>? secondPromotionModel =
secondPromotionInfo[secondProperty.promotionKey];
secondPromotionInfo?.get(helper, secondProperty.promotionKey);
if (secondPromotionModel != null) {
newPromotionInfo[newPromotionKey] = PromotionModel.join(
PromotionModel<Type> newPromotionModel;
(newPromotionModel, newFlowModel) = PromotionModel.join(
helper,
firstPromotionModel,
firstPromotionInfo,
secondPromotionModel,
secondPromotionInfo,
newPromotionInfo,
newFlowModel,
propertySsaNode: propertySsaNode);
newFlowModel = newFlowModel.updatePromotionInfo(
helper, newPromotionKey, newPromotionModel);
}
}
// Join any nested properties.
propertySsaNode._joinProperties(
newFlowModel = propertySsaNode._joinProperties(
helper,
firstProperty._promotableProperties,
firstPromotionInfo,
secondProperty._promotableProperties,
secondPromotionInfo,
newPromotionInfo);
newFlowModel);
}
return newFlowModel;
}
/// Joins the promotion information for two SSA nodes, [first] and [second].
@ -3877,18 +3894,28 @@ class SsaNode<Type extends Object> {
/// themselves be promoted, the caller must supply the promotion info maps for
/// the two flow control paths being joined ([firstPromotionInfo] and
/// [secondPromotionInfo]), as well as the promotion info map being built for
/// the join point ([newPromotionInfo]).
static SsaNode<Type> _join<Type extends Object>(
/// the join point ([newFlowModel]).
static (SsaNode<Type>, FlowModel<Type>) _join<Type extends Object>(
FlowModelHelper<Type> helper,
SsaNode<Type> first,
Map<int, PromotionModel<Type>> firstPromotionInfo,
PromotionInfo<Type>? firstPromotionInfo,
SsaNode<Type> second,
Map<int, PromotionModel<Type>> secondPromotionInfo,
Map<int, PromotionModel<Type>> newPromotionInfo) {
if (first == second) return first;
return new SsaNode(null)
.._joinProperties(helper, first._promotableProperties, firstPromotionInfo,
second._promotableProperties, secondPromotionInfo, newPromotionInfo);
PromotionInfo<Type>? secondPromotionInfo,
FlowModel<Type> newFlowModel) {
SsaNode<Type> ssaNode;
if (first == second) {
ssaNode = first;
} else {
ssaNode = new SsaNode(null);
newFlowModel = ssaNode._joinProperties(
helper,
first._promotableProperties,
firstPromotionInfo,
second._promotableProperties,
secondPromotionInfo,
newFlowModel);
}
return (ssaNode, newFlowModel);
}
}
@ -4126,9 +4153,9 @@ abstract class _EqualityCheckResult {
class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
Expression extends Node, Variable extends Object, Type extends Object>
with FlowModelHelper<Type>
implements
FlowAnalysis<Node, Statement, Expression, Variable, Type>,
FlowModelHelper<Type>,
_PropertyTargetHelper<Expression, Type> {
/// The [Operations], used to access types, check subtyping, and query
/// variable types.
@ -4251,8 +4278,9 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
@override
void assignMatchedPatternVariable(Variable variable, int promotionKey) {
int mergedKey = promotionKeyStore.keyForVariable(variable);
PromotionModel<Type> info = _current.promotionInfo[promotionKey] ??
new PromotionModel.fresh(ssaNode: new SsaNode(null));
PromotionModel<Type> info =
_current.promotionInfo?.get(this, promotionKey) ??
new PromotionModel.fresh(ssaNode: new SsaNode(null));
// Normally flow analysis is responsible for tracking whether variables are
// definitely assigned; however for variables appearing in patterns we
// have other logic to make sure that a value is definitely assigned (e.g.
@ -4392,7 +4420,7 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
_current = _current.updatePromotionInfo(
this,
destinationKey,
_current.promotionInfo[sourceKey] ??
_current.promotionInfo?.get(this, sourceKey) ??
new PromotionModel.fresh(ssaNode: new SsaNode(null)));
}
@ -4601,8 +4629,10 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
@override
Type getMatchedValueType() {
_PatternContext<Type> context = _stack.last as _PatternContext<Type>;
return _current.promotionInfo[context._matchedValueInfo.promotionKey]
?.promotedTypes?.last ??
return _current.promotionInfo
?.get(this, context._matchedValueInfo.promotionKey)
?.promotedTypes
?.last ??
context._matchedValueInfo._type;
}
@ -4751,7 +4781,8 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
@override
bool isAssigned(Variable variable) {
return _current.promotionInfo[promotionKeyStore.keyForVariable(variable)]
return _current.promotionInfo
?.get(this, promotionKeyStore.keyForVariable(variable))
?.assigned ??
false;
}
@ -4775,7 +4806,8 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
@override
bool isUnassigned(Variable variable) {
return _current.promotionInfo[promotionKeyStore.keyForVariable(variable)]
return _current.promotionInfo
?.get(this, promotionKeyStore.keyForVariable(variable))
?.unassigned ??
true;
}
@ -5036,8 +5068,10 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
@override
Type? promotedType(Variable variable) {
return _current.promotionInfo[promotionKeyStore.keyForVariable(variable)]
?.promotedTypes?.last;
return _current.promotionInfo
?.get(this, promotionKeyStore.keyForVariable(variable))
?.promotedTypes
?.last;
}
@override
@ -5078,10 +5112,12 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
// accessed through a new SSA node, and thus a new promotion key).
if (scrutineeReference != null &&
(scrutineeReference is _PropertyReference<Type> ||
_current.promotionInfo[matchedValueReference.promotionKey]!
_current.promotionInfo
?.get(this, matchedValueReference.promotionKey)!
.ssaNode ==
_current
.promotionInfo[scrutineeReference.promotionKey]?.ssaNode)) {
_current.promotionInfo
?.get(this, scrutineeReference.promotionKey)
?.ssaNode)) {
ifTrue = ifTrue
.tryPromoteForTypeCheck(this, scrutineeReference, knownType)
.ifTrue;
@ -5155,8 +5191,9 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
}
@override
SsaNode<Type>? ssaNodeForTesting(Variable variable) => _current
.promotionInfo[promotionKeyStore.keyForVariable(variable)]?.ssaNode;
SsaNode<Type>? ssaNodeForTesting(Variable variable) => _current.promotionInfo
?.get(this, promotionKeyStore.keyForVariable(variable))
?.ssaNode;
@override
bool switchStatement_afterCase() {
@ -5355,8 +5392,10 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
void tryFinallyStatement_end() {
_TryFinallyContext<Type> context =
_stack.removeLast() as _TryFinallyContext<Type>;
_current = context._afterBodyAndCatches!
.attachFinally(this, context._beforeFinally!, _current);
_current = context._afterBodyAndCatches!.attachFinally(this,
beforeFinally: context._beforeFinally!,
afterFinally: _current,
ancestor: context._previous);
}
@override
@ -5373,7 +5412,8 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
Type? variableRead(Expression expression, Variable variable) {
Type unpromotedType = operations.variableType(variable);
int variableKey = promotionKeyStore.keyForVariable(variable);
PromotionModel<Type>? promotionModel = _current.promotionInfo[variableKey];
PromotionModel<Type>? promotionModel =
_current.promotionInfo?.get(this, variableKey);
if (promotionModel == null) {
promotionModel = new PromotionModel.fresh(ssaNode: new SsaNode(null));
_current =
@ -5419,7 +5459,7 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
_Reference<Type>? referenceWithType = _expressionReference;
if (referenceWithType != null) {
PromotionModel<Type>? currentPromotionInfo =
_current.promotionInfo[referenceWithType.promotionKey];
_current.promotionInfo?.get(this, referenceWithType.promotionKey);
return _getNonPromotionReasons(referenceWithType, currentPromotionInfo);
}
}
@ -5430,7 +5470,7 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
Map<Type, NonPromotionReason> Function() whyNotPromotedImplicitThis(
Type staticType) {
PromotionModel<Type>? currentThisInfo =
_current.promotionInfo[promotionKeyStore.thisPromotionKey];
_current.promotionInfo?.get(this, promotionKeyStore.thisPromotionKey);
if (currentThisInfo == null) {
return () => {};
}
@ -5721,7 +5761,7 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
Type? promotedType;
if (isPromotable) {
PromotionModel<Type>? promotionInfo =
_current.promotionInfo[propertySsaNode.promotionKey];
_current.promotionInfo?.get(this, propertySsaNode.promotionKey);
if (promotionInfo != null) {
assert(promotionInfo.ssaNode == propertySsaNode);
}
@ -5824,9 +5864,11 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
// accessed through a new SSA node, and thus a new promotion key).
if (scrutineeReference != null &&
(scrutineeReference is _PropertyReference<Type> ||
_current.promotionInfo[matchedValueReference.promotionKey]!
_current.promotionInfo
?.get(this, matchedValueReference.promotionKey)!
.ssaNode ==
_current.promotionInfo[scrutineeReference.promotionKey]
_current.promotionInfo
?.get(this, scrutineeReference.promotionKey)
?.ssaNode)) {
ifNotNull =
ifNotNull.tryMarkNonNullable(this, scrutineeReference).ifTrue;
@ -5924,7 +5966,7 @@ class _FlowAnalysisImpl<Node extends Object, Statement extends Node,
TrivialVariableReference<Type> _variableReference(
int variableKey, Type unpromotedType) {
PromotionModel<Type> info = _current.promotionInfo[variableKey]!;
PromotionModel<Type> info = _current.promotionInfo!.get(this, variableKey)!;
return new TrivialVariableReference<Type>(
promotionKey: variableKey,
after: _current,

View file

@ -0,0 +1,351 @@
// Copyright (c) 2023, 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.
/// Abstract base class forming the basis for an efficient immutable map data
/// structure that can track program state.
///
/// Each instance of [FlowLink] represents a key/value pair, where the [key] is
/// a non-negative integer, and the value is stored in the derived class; a map
/// is formed by chaining together multiple [FlowLink] objects through
/// [previous] pointers. In this way, a collection of [FlowLink] objects be used
/// to model program state, with each [FlowLink] representing a change to a
/// single state variable, and the [previous] pointer pointing to the previous
/// state of the program. In this interpretation, `null` represents the initial
/// program state, in which all state variables take on their default values.
///
/// Multiple [FlowLink] objects are allowed to point to the same [previous]
/// object; in this way, all [FlowLink] objects implicitly form a tree, with
/// `null` at the root. In the interpretation where a collection of [FlowLink]
/// objects are used to model program state, and a single [FlowLink] represents
/// a change to a single state variable, the tree corresponds to the dominator
/// tree. There are no "child" pointers, so the tree may only be traversed in
/// the leaf-to-root direction, and once a branch is no longer needed it will be
/// reclaimed by the garbage collector.
///
/// The [FlowLinkReader] class may be used to efficiently look up map entries in
/// a given [FlowLink] object. It makes use of the fact that [key]s are
/// non-negative integers to maintain a current state in a list.
///
/// The generic parameter [Link] should be instantiated with the derived class.
abstract base class FlowLink<Link extends FlowLink<Link>> {
/// The integer key for this [FlowLink]. In the interpretation where a
/// collection of [FlowLink] objects are used to model program state, and a
/// single [FlowLink] represents a change to a single state variable, this key
/// tells which state variable has changed.
final int key;
/// Pointer allowing multiple [FlowLink] objects to be joined into a singly
/// linked list. In the interpretation where a collection of [FlowLink]
/// objects are used to model program state, and a single [FlowLink]
/// represents a change to a single state variable, this pointer points to the
/// state of the program prior to the change.
final Link? previous;
/// Pointer to the nearest [FlowLink] in the [previous] chain whose [key]
/// matches this one, or `null` if there is no matching [FlowLink]. This is
/// used by [FlowLinkReader] to quickly update its state representation when
/// traversing the implicit tree of [FlowLink] objects.
final Link? previousForKey;
/// The number of [previous] links that need to be traversed to reach `null`.
/// This is used by [FlowLinkReader] to quickly find the common ancestor of
/// two points in the implicit tree of [FlowLink] objects.
final int _depth;
/// Creates a new [FlowLink] object. Caller is required to satisfy the
/// invariant described in [previousForKey].
FlowLink(
{required this.key, required this.previous, required this.previousForKey})
: _depth = previous.depth + 1 {
assert(key >= 0);
assert(identical(previousForKey, _computePreviousForKey(key)));
}
/// Debug only: computes the correct value for [previousForKey], to check that
/// the caller supplied the appropriate value to the constructor.
Link? _computePreviousForKey(int key) {
Link? link = previous;
while (link != null) {
if (link.key == key) break;
link = link.previous;
}
return link;
}
}
/// Information about a difference between two program states, returned by
/// [FlowLinkReader.diff].
class FlowLinkDiffEntry<Link extends FlowLink<Link>> {
/// The key that differs between the [FlowLink] maps passed to
/// [FlowLinkReader.diff].
final int key;
/// During a diff operation, the first [FlowLink] associated with [key] that
/// was found while walking the [FlowLink.previous] chain for the `left`
/// argument to [FlowLinkReader.diff], or `null` if no such key has been found
/// yet.
Link? _firstLeft;
/// During a diff operation, the first [FlowLink] associated with [key] that
/// was found while walking the [FlowLink.previous] chain for the `right`
/// argument to [FlowLinkReader.diff], or `null` if no such key has been found
/// yet.
Link? _firstRight;
/// During a diff operation, the value of [FlowLink.previousForKey] that was
/// most recently encountered while walking the [FlowLink.previous] chains for
/// both the `left` and `right` arguments to [FlowLinkReader.diff].
Link? _previousForKey;
FlowLinkDiffEntry._(
{required this.key,
required Link? firstLeft,
required Link? firstRight,
required Link? previousForKey})
: _firstLeft = firstLeft,
_firstRight = firstRight,
_previousForKey = previousForKey;
/// The [FlowLink] associated with [key] in the common ancestor of the two
/// [FlowLink] maps passed to [FlowLinkReader.diff], or `null` if the common
/// ancestor doesn't associate any [FlowLink] with [key].
Link? get ancestor {
// This is called by a client after the `diff` operation has completed.
// Therefore, `_previousForKey` comes from the `FlowLink` that the `diff`
// operation visited last; i.e. the one closest to the common ancestor node.
// So it *is* the common ancestor for the given key.
return _previousForKey;
}
/// The [FlowLink] associated with [key] in the `left` [FlowLink] map passed
/// to [FlowLinkReader.diff], or `null` if the `left` [FlowLink] map doesn't
/// associate any [FlowLink] with [key].
Link? get left {
// This is called by a client after the `diff` operation has completed.
// Therefore, `_firstLeft` is either the first [FlowLink] encountered while
// traversing the linked list for the `left` side of the diff, or it's
// `null` and *no* [FlowLink] was encountered on the left side of the diff
// with the given key; in the latter situation, we may safely return
// `_previousForKey`, which is the common ancestor for the key.
return _firstLeft ?? _previousForKey;
}
/// The [FlowLink] associated with [key] in the `right` [FlowLink] map passed
/// to [FlowLinkReader.diff], or `null` if the `right` [FlowLink] map doesn't
/// associate any [FlowLink] with [key].
Link? get right {
// This is called by a client after the `diff` operation has completed.
// Therefore, `_firstRight` is either the first [FlowLink] encountered while
// traversing the linked list for the `right` side of the diff, or it's
// `null` and *no* [FlowLink] was encountered on the right side of the diff
// with the given key; in the latter situation, we may safely return
// `_previousForKey`, which is the common ancestor for the key.
return _firstRight ?? _previousForKey;
}
}
/// Efficient mechanism for looking up entries in the map formed implicitly by
/// a linked list of [FlowLink] objects, and for finding the difference between
/// two such maps.
///
/// This class works by maintaining a "current" pointer recording the [FlowLink]
/// that was most recently passed to [get], and a cache of the state of all
/// state variables implied by that [FlowLink] object. The cache can be updated
/// in O(n) time, where n is the number of tree edges between one state and
/// another. Accordingly, for maximum efficiency, the caller should try not to
/// jump around the tree too much in successive calls to [get].
class FlowLinkReader<Link extends FlowLink<Link>> {
/// The [FlowLink] pointer most recently passed to [_setCurrent].
Link? _current;
/// A cache of the lookup results that should be returned by [get] for each
/// possible integer key, for the [_current] link.
List<Link?> _cache = [];
/// Temporary scratch area used by [_diffCore]. Each non-null entry represents
/// an index into the list of entries that [_diffCore] will return; that entry
/// has the same integer key as the corresponding index into this list.
List<int?> _diffIndices = [];
/// Computes the difference between [FlowLink] states represented by [left]
/// and [right].
///
/// Two values are returned: the common ancestor of [left] and [right], and
/// a list of [FlowLinkDiffEntry] objects representing the difference among
/// [left], [right], and their common ancestor.
///
/// If [left] and [right] are identical, this method has time complexity
/// `O(1)`.
///
/// Otherwise, this method has time complexity `O(n)`, where `n` is the number
/// of edges between [left] and [right] in the implicit [FlowLink] tree.
({Link? ancestor, List<FlowLinkDiffEntry<Link>> entries}) diff(
Link? left, Link? right) {
if (identical(left, right)) {
return (ancestor: left, entries: const []);
}
List<FlowLinkDiffEntry<Link>> entries = [];
Link? ancestor = _diffCore(left, right, entries);
return (ancestor: ancestor, entries: entries);
}
/// Looks up the first entry in the linked list formed by [FlowLink.previous],
/// starting at [link], whose key matches [key]. If there is no such entry,
/// `null` is returned.
///
/// If [link] is `null` or matches [_current], this method has time
/// complexity `O(1). In this circumstance, [_current] is unchanged.
///
/// Otherwise, this method has time complexity `O(n)`, where `n` is the number
/// of edges between [_current] and [link] in the implicit [FlowLink] tree. In
/// this circumstance, [_current] is set to [link].
Link? get(Link? link, int key) {
if (link == null) {
return null;
}
_setCurrent(link);
return _cache.get(key);
}
/// The core algorithm used by [diff] and [_setCurrent]. Computes a difference
/// between [left] and [right], adding diff entries to [entries]. The return
/// value is the common ancestor of [left] and [right].
Link? _diffCore(
Link? left, Link? right, List<FlowLinkDiffEntry<Link>> entries) {
// The core strategy is to traverse the implicit [FlowLink] tree, starting
// at `left` and `right`, and taking single steps through the linked list
// formed by `FlowLink.previous`, until a common ancestor is found. For each
// step, an entry is added to the `entries` list for the corresponding key
// (or a previously made entry is updated), and `_diffIndices` is modified
// to keep track of which keys have corresponding entries.
// Takes a single step from `left` through the linked list formed by
// `FlowLink.previous`, updating `entries` and `_diffIndices` as
// appropriate.
Link? stepLeft(Link left) {
int key = left.key;
int? index = _diffIndices.get(key);
if (index == null) {
// No diff entry has been created for this key yet, so create one.
_diffIndices.set(key, entries.length);
entries.add(new FlowLinkDiffEntry<Link>._(
key: key,
firstLeft: left,
firstRight: null,
previousForKey: left.previousForKey));
} else {
// A diff entry for this key has already been created, so update it.
entries[index]._firstLeft ??= left;
entries[index]._previousForKey = left.previousForKey;
}
return left.previous;
}
// Takes a single step from `right` through the linked list formed by
// `FlowLink.previous`, updating `entries` and `_diffIndices` as
// appropriate.
Link? stepRight(Link right) {
int key = right.key;
int? index = _diffIndices.get(key);
if (index == null) {
_diffIndices.set(key, entries.length);
entries.add(new FlowLinkDiffEntry<Link>._(
key: key,
firstLeft: null,
firstRight: right,
previousForKey: right.previousForKey));
} else {
entries[index]._firstRight ??= right;
entries[index]._previousForKey = right.previousForKey;
}
return right.previous;
}
// Walk `left` and `right` back to their common ancestor.
int leftDepth = left.depth;
int rightDepth = right.depth;
try {
if (leftDepth > rightDepth) {
do {
// `left.depth > right.depth`, therefore `left.depth > 0`, so
// `left != null`.
left = stepLeft(left!);
leftDepth--;
assert(leftDepth == left.depth);
} while (leftDepth > rightDepth);
} else {
while (rightDepth > leftDepth) {
// `right.depth > left.depth`, therefore `right.depth > 0`, so
// `right != null`.
right = stepRight(right!);
rightDepth--;
assert(rightDepth == right.depth);
}
}
while (!identical(left, right)) {
assert(left.depth == right.depth);
// The only possible value of type `FlowLink?` with a depth of `0` is
// `null`. Therefore, since `left.depth == right.depth`, `left`
// and `right` must either be both `null` or both non-`null`. Since
// they're not identical to one another, it follows that they're both
// non-`null`.
left = stepLeft(left!);
right = stepRight(right!);
}
} finally {
// Clear `_diffIndices` for the next call to this method. Note that we
// don't really expect an exception to occur above, but if one were to
// occur, and we left non-null data in `_diffIndices`, that would produce
// very confusing behavior on future invocations of this method. So to be
// on the safe side, we do this clean up logic is in a `finally`
// clause.
for (int i = 0; i < entries.length; i++) {
FlowLinkDiffEntry<Link> entry = entries[i];
// Since `_diffIndices` was constructed as an index into `entries`, we
// know that `_diffIndices[entry.key] == i`.
assert(_diffIndices[entry.key] == i);
// Therefore, there's no need to use `_diffIndices.set`, since we
// already know that `entry.key < _diffIndices.length`.
_diffIndices[entry.key] = null;
}
}
return left;
}
/// Sets [_current] to [value], updating [_cache] in the process.
void _setCurrent(Link? value) {
if (identical(value, _current)) return;
List<FlowLinkDiffEntry<Link>> entries = [];
_diffCore(_current, value, entries);
for (FlowLinkDiffEntry<Link> entry in entries) {
_cache.set(entry.key, entry.right);
}
_current = value;
}
}
extension on FlowLink<dynamic>? {
/// Gets the `_depth` of `this`, or `0` if `this` is `null`.
int get depth {
FlowLink<dynamic>? self = this;
if (self == null) return 0;
return self._depth;
}
}
extension<T extends Object> on List<T?> {
/// Looks up the `index`th entry in `this` in a safe way, returning `null` if
/// `index` is out of range.
T? get(int index) => index < length ? this[index] : null;
/// Stores `value` in the `index`th entry of `this`, increasing the length of
/// `this` if necessary.
void set(int index, T? value) {
while (index >= length) {
add(null);
}
this[index] = value;
}
}

View file

@ -24,7 +24,7 @@ Expression implicitThis_whyNotPromoted(String staticType,
/// Test harness for creating flow analysis tests. This class implements all
/// the [Operations] needed by flow analysis, as well as other methods needed
/// for testing.
class FlowAnalysisTestHarness extends Harness implements FlowModelHelper<Type> {
class FlowAnalysisTestHarness extends Harness with FlowModelHelper<Type> {
@override
final PromotionKeyStore<Var> promotionKeyStore = PromotionKeyStore();

View file

@ -2,7 +2,11 @@
// 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 'dart:core' as core;
import 'dart:core';
import 'package:_fe_analyzer_shared/src/flow_analysis/flow_analysis.dart';
import 'package:_fe_analyzer_shared/src/flow_analysis/flow_link.dart';
import 'package:_fe_analyzer_shared/src/type_inference/assigned_variables.dart';
import 'package:test/test.dart';
@ -1415,9 +1419,14 @@ main() {
getSsaNodes((nodes) {
var info = nodes[x]!.expressionInfo!;
var key = h.promotionKeyStore.keyForVariable(y);
expect(info.after.promotionInfo[key]!.promotedTypes, null);
expect(info.ifTrue.promotionInfo[key]!.promotedTypes, null);
expect(info.ifFalse.promotionInfo[key]!.promotedTypes!.single.type,
expect(info.after.promotionInfo!.get(h, key)!.promotedTypes, null);
expect(info.ifTrue.promotionInfo!.get(h, key)!.promotedTypes, null);
expect(
info.ifFalse.promotionInfo!
.get(h, key)!
.promotedTypes!
.single
.type,
'int');
}),
]);
@ -3535,7 +3544,7 @@ main() {
var s1 = FlowModel<Type>(Reachability.initial);
var s2 = s1._tryPromoteForTypeCheck(h, intQVar, 'int').ifTrue;
expect(s2.reachable.overallReachable, true);
expect(s2.promotionInfo, {
expect(s2.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(intQVar):
_matchVariableModel(chain: ['int'], ofInterest: ['int'])
});
@ -3571,7 +3580,7 @@ main() {
.ifTrue;
var s2 = s1._tryPromoteForTypeCheck(h, objectQVar, 'int').ifTrue;
expect(s2.reachable.overallReachable, true);
expect(s2.promotionInfo, {
expect(s2.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(objectQVar): _matchVariableModel(
chain: ['int?', 'int'], ofInterest: ['int?', 'int'])
});
@ -3586,7 +3595,9 @@ main() {
var s = FlowModel<Type>(Reachability.initial)._write(
h, null, objectQVar, Type('Object?'), new SsaNode<Type>(null));
expect(s.promotionInfo[h.promotionKeyStore.keyForVariable(objectQVar)],
expect(
s.promotionInfo
?.get(h, h.promotionKeyStore.keyForVariable(objectQVar)),
isNull);
});
@ -3626,12 +3637,12 @@ main() {
._declare(h, objectQVar, true)
._tryPromoteForTypeCheck(h, objectQVar, 'int')
.ifTrue;
expect(s1.promotionInfo,
expect(s1.promotionInfo.unwrap(h),
contains(h.promotionKeyStore.keyForVariable(objectQVar)));
var s2 = s1._write(h, _MockNonPromotionReason(), objectQVar,
Type('int?'), new SsaNode<Type>(null));
expect(s2.reachable.overallReachable, true);
expect(s2.promotionInfo, {
expect(s2.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(objectQVar): _matchVariableModel(
chain: null,
ofInterest: isEmpty,
@ -3647,7 +3658,7 @@ main() {
.ifTrue
._tryPromoteForTypeCheck(h, objectQVar, 'int')
.ifTrue;
expect(s1.promotionInfo, {
expect(s1.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(objectQVar): _matchVariableModel(
chain: ['num?', 'int'],
ofInterest: ['num?', 'int'],
@ -3657,7 +3668,7 @@ main() {
var s2 = s1._write(h, _MockNonPromotionReason(), objectQVar,
Type('num'), new SsaNode<Type>(null));
expect(s2.reachable.overallReachable, true);
expect(s2.promotionInfo, {
expect(s2.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(objectQVar): _matchVariableModel(
chain: ['num?', 'num'],
ofInterest: ['num?', 'int'],
@ -3675,7 +3686,7 @@ main() {
.ifTrue
._tryPromoteForTypeCheck(h, objectQVar, 'int')
.ifTrue;
expect(s1.promotionInfo, {
expect(s1.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(objectQVar): _matchVariableModel(
chain: ['num?', 'num', 'int'],
ofInterest: ['num?', 'num', 'int'],
@ -3685,7 +3696,7 @@ main() {
var s2 = s1._write(h, _MockNonPromotionReason(), objectQVar,
Type('num'), new SsaNode<Type>(null));
expect(s2.reachable.overallReachable, true);
expect(s2.promotionInfo, {
expect(s2.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(objectQVar): _matchVariableModel(
chain: ['num?', 'num'],
ofInterest: ['num?', 'num', 'int'],
@ -3701,7 +3712,7 @@ main() {
.ifTrue
._tryPromoteForTypeCheck(h, objectQVar, 'num')
.ifTrue;
expect(s1.promotionInfo, {
expect(s1.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(objectQVar): _matchVariableModel(
chain: ['num?', 'num'],
ofInterest: ['num?', 'num'],
@ -3712,7 +3723,7 @@ main() {
h, null, objectQVar, Type('num'), new SsaNode<Type>(null));
expect(s2.reachable.overallReachable, true);
expect(s2.promotionInfo, isNot(same(s1.promotionInfo)));
expect(s2.promotionInfo, {
expect(s2.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(objectQVar): _matchVariableModel(
chain: ['num?', 'num'],
ofInterest: ['num?', 'num'],
@ -3728,7 +3739,7 @@ main() {
.ifTrue
._tryPromoteForTypeCheck(h, objectQVar, 'num')
.ifTrue;
expect(s1.promotionInfo, {
expect(s1.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(objectQVar): _matchVariableModel(
chain: ['num?', 'num'],
ofInterest: ['num?', 'num'],
@ -3739,7 +3750,7 @@ main() {
h, null, objectQVar, Type('int'), new SsaNode<Type>(null));
expect(s2.reachable.overallReachable, true);
expect(s2.promotionInfo, isNot(same(s1.promotionInfo)));
expect(s2.promotionInfo, {
expect(s2.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(objectQVar): _matchVariableModel(
chain: ['num?', 'num'],
ofInterest: ['num?', 'num'],
@ -3753,13 +3764,13 @@ main() {
var x = Var('x')..type = Type('int?');
var s1 = FlowModel<Type>(Reachability.initial)._declare(h, x, true);
expect(s1.promotionInfo, {
expect(s1.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(x):
_matchVariableModel(chain: null),
});
var s2 = s1._write(h, null, x, Type('int'), new SsaNode<Type>(null));
expect(s2.promotionInfo, {
expect(s2.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(x):
_matchVariableModel(chain: ['int']),
});
@ -3769,20 +3780,20 @@ main() {
var x = Var('x')..type = Type('int?');
var s1 = FlowModel<Type>(Reachability.initial)._declare(h, x, true);
expect(s1.promotionInfo, {
expect(s1.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(x):
_matchVariableModel(chain: null),
});
var s2 = s1._conservativeJoin(h, [], [x]);
expect(s2.promotionInfo, {
expect(s2.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(x):
_matchVariableModel(chain: null, writeCaptured: true),
});
// 'x' is write-captured, so not promoted
var s3 = s2._write(h, null, x, Type('int'), new SsaNode<Type>(null));
expect(s3.promotionInfo, {
expect(s3.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(x):
_matchVariableModel(chain: null, writeCaptured: true),
});
@ -3793,7 +3804,7 @@ main() {
._declare(h, objectQVar, true)
._tryPromoteForTypeCheck(h, objectQVar, 'int?')
.ifTrue;
expect(s1.promotionInfo, {
expect(s1.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(objectQVar): _matchVariableModel(
chain: ['int?'],
ofInterest: ['int?'],
@ -3801,7 +3812,7 @@ main() {
});
var s2 = s1._write(
h, null, objectQVar, Type('int'), new SsaNode<Type>(null));
expect(s2.promotionInfo, {
expect(s2.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(objectQVar): _matchVariableModel(
chain: ['int?', 'int'],
ofInterest: ['int?'],
@ -3814,7 +3825,7 @@ main() {
._declare(h, objectQVar, true)
._tryPromoteForTypeCheck(h, objectQVar, 'int?')
.ifFalse;
expect(s1.promotionInfo, {
expect(s1.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(objectQVar): _matchVariableModel(
chain: ['Object'],
ofInterest: ['int?'],
@ -3822,7 +3833,7 @@ main() {
});
var s2 = s1._write(
h, null, objectQVar, Type('int'), new SsaNode<Type>(null));
expect(s2.promotionInfo, {
expect(s2.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(objectQVar): _matchVariableModel(
chain: ['Object', 'int'],
ofInterest: ['int?'],
@ -3836,7 +3847,7 @@ main() {
._declare(h, objectQVar, true)
._tryPromoteForTypeCheck(h, objectQVar, 'num?')
.ifFalse;
expect(s1.promotionInfo, {
expect(s1.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(objectQVar): _matchVariableModel(
chain: ['Object'],
ofInterest: ['num?'],
@ -3844,7 +3855,7 @@ main() {
});
var s2 = s1._write(h, _MockNonPromotionReason(), objectQVar,
Type('num?'), new SsaNode<Type>(null));
expect(s2.promotionInfo, {
expect(s2.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(objectQVar): _matchVariableModel(
chain: ['num?'],
ofInterest: ['num?'],
@ -3859,7 +3870,7 @@ main() {
.ifTrue
._tryPromoteForTypeCheck(h, objectQVar, 'int?')
.ifFalse;
expect(s1.promotionInfo, {
expect(s1.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(objectQVar): _matchVariableModel(
chain: ['num?', 'num'],
ofInterest: ['num?', 'int?'],
@ -3867,7 +3878,7 @@ main() {
});
var s2 = s1._write(h, _MockNonPromotionReason(), objectQVar,
Type('int?'), new SsaNode<Type>(null));
expect(s2.promotionInfo, {
expect(s2.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(objectQVar): _matchVariableModel(
chain: ['num?', 'int?'],
ofInterest: ['num?', 'int?'],
@ -3896,7 +3907,7 @@ main() {
.ifFalse
._tryPromoteForTypeCheck(h, x, 'A?')
.ifFalse;
expect(s1.promotionInfo, {
expect(s1.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(x): _matchVariableModel(
chain: ['Object'],
ofInterest: ['A?', 'B?'],
@ -3904,7 +3915,7 @@ main() {
});
var s2 = s1._write(h, null, x, Type('C'), new SsaNode<Type>(null));
expect(s2.promotionInfo, {
expect(s2.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(x): _matchVariableModel(
chain: ['Object', 'B'],
ofInterest: ['A?', 'B?'],
@ -3921,7 +3932,7 @@ main() {
.ifFalse
._tryPromoteForTypeCheck(h, x, 'B?')
.ifFalse;
expect(s1.promotionInfo, {
expect(s1.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(x): _matchVariableModel(
chain: ['Object'],
ofInterest: ['A?', 'B?'],
@ -3929,7 +3940,7 @@ main() {
});
var s2 = s1._write(h, null, x, Type('C'), new SsaNode<Type>(null));
expect(s2.promotionInfo, {
expect(s2.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(x): _matchVariableModel(
chain: ['Object', 'B'],
ofInterest: ['A?', 'B?'],
@ -3946,7 +3957,7 @@ main() {
.ifFalse
._tryPromoteForTypeCheck(h, x, 'A?')
.ifFalse;
expect(s1.promotionInfo, {
expect(s1.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(x): _matchVariableModel(
chain: ['Object'],
ofInterest: ['A', 'A?'],
@ -3954,7 +3965,7 @@ main() {
});
var s2 = s1._write(h, null, x, Type('B'), new SsaNode<Type>(null));
expect(s2.promotionInfo, {
expect(s2.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(x): _matchVariableModel(
chain: ['Object', 'A'],
ofInterest: ['A', 'A?'],
@ -3971,7 +3982,7 @@ main() {
.ifFalse
._tryPromoteForTypeCheck(h, objectQVar, 'num*')
.ifFalse;
expect(s1.promotionInfo, {
expect(s1.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(objectQVar):
_matchVariableModel(
chain: ['Object'],
@ -3983,7 +3994,7 @@ main() {
// It's ambiguous whether to promote to num? or num*, so we don't
// promote.
expect(s2, isNot(same(s1)));
expect(s2.promotionInfo, {
expect(s2.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(objectQVar):
_matchVariableModel(
chain: ['Object'],
@ -4000,7 +4011,7 @@ main() {
.ifFalse
._tryPromoteForTypeCheck(h, objectQVar, 'num*')
.ifFalse;
expect(s1.promotionInfo, {
expect(s1.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(objectQVar): _matchVariableModel(
chain: ['Object'],
ofInterest: ['num?', 'num*'],
@ -4010,7 +4021,7 @@ main() {
Type('num?'), new SsaNode<Type>(null));
// It's ambiguous whether to promote to num? or num*, but since the
// written type is exactly num?, we use that.
expect(s2.promotionInfo, {
expect(s2.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(objectQVar): _matchVariableModel(
chain: ['num?'],
ofInterest: ['num?', 'num*'],
@ -4030,7 +4041,7 @@ main() {
.ifTrue
._tryPromoteForTypeCheck(h, x, 'int?')
.ifTrue;
expect(s1.promotionInfo, {
expect(s1.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(x): _matchVariableModel(
chain: ['num?', 'int?'],
ofInterest: ['num?', 'int?'],
@ -4039,7 +4050,7 @@ main() {
var s2 = s1._write(h, _MockNonPromotionReason(), x, Type('double'),
new SsaNode<Type>(null));
expect(s2.promotionInfo, {
expect(s2.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(x): _matchVariableModel(
chain: ['num?', 'num'],
ofInterest: ['num?', 'int?'],
@ -4054,7 +4065,7 @@ main() {
test('initialized', () {
var s =
FlowModel<Type>(Reachability.initial)._declare(h, objectQVar, true);
expect(s.promotionInfo, {
expect(s.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(objectQVar):
_matchVariableModel(assigned: true, unassigned: false),
});
@ -4063,7 +4074,7 @@ main() {
test('not initialized', () {
var s = FlowModel<Type>(Reachability.initial)
._declare(h, objectQVar, false);
expect(s.promotionInfo, {
expect(s.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(objectQVar):
_matchVariableModel(assigned: false, unassigned: true),
});
@ -4099,7 +4110,7 @@ main() {
.ifTrue;
var s2 = s1._tryMarkNonNullable(h, objectQVar).ifTrue;
expect(s2.reachable.overallReachable, true);
expect(s2.promotionInfo, {
expect(s2.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(objectQVar):
_matchVariableModel(chain: ['int?', 'int'], ofInterest: ['int?'])
});
@ -4123,7 +4134,7 @@ main() {
var s2 = s1._conservativeJoin(h, [intQVar], []);
expect(s2, isNot(same(s1)));
expect(s2.reachable, same(s1.reachable));
expect(s2.promotionInfo, {
expect(s2.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(objectQVar):
_matchVariableModel(chain: ['int'], ofInterest: ['int']),
h.promotionKeyStore.keyForVariable(intQVar):
@ -4139,7 +4150,7 @@ main() {
.ifTrue;
var s2 = s1._conservativeJoin(h, [intQVar], []);
expect(s2.reachable.overallReachable, true);
expect(s2.promotionInfo, {
expect(s2.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(objectQVar):
_matchVariableModel(chain: ['int'], ofInterest: ['int']),
h.promotionKeyStore.keyForVariable(intQVar):
@ -4155,7 +4166,7 @@ main() {
.ifTrue;
var s2 = s1._conservativeJoin(h, [], [intQVar]);
expect(s2.reachable.overallReachable, true);
expect(s2.promotionInfo, {
expect(s2.promotionInfo.unwrap(h), {
h.promotionKeyStore.keyForVariable(objectQVar):
_matchVariableModel(chain: ['int'], ofInterest: ['int']),
h.promotionKeyStore.keyForVariable(intQVar): _matchVariableModel(
@ -4267,7 +4278,7 @@ main() {
: s0._tryPromoteForTypeCheck(h, x, otherType).ifTrue;
var result = s2.rebaseForward(h, s1);
if (expectedChain == null) {
expect(result.promotionInfo,
expect(result.promotionInfo.unwrap(h),
contains(h.promotionKeyStore.keyForVariable(x)));
expect(result._infoFor(h, x).promotedTypes, isNull);
} else {
@ -4586,15 +4597,16 @@ main() {
group('without input reuse', () {
test('promoted with unpromoted', () {
var p1 = {
var s0 = FlowModel<Type>(Reachability.initial);
var s1 = s0._setInfo(h, {
x: model([intType]),
y: model(null)
};
var p2 = {
});
var s2 = s0._setInfo(h, {
x: model(null),
y: model([intType])
};
expect(FlowModel.joinPromotionInfo(h, p1, p2), {
});
expect(FlowModel.joinPromotionInfo(h, s1, s2).promotionInfo.unwrap(h), {
x: _matchVariableModel(chain: null, ofInterest: ['int']),
y: _matchVariableModel(chain: null, ofInterest: ['int'])
});
@ -4602,112 +4614,138 @@ main() {
});
group('should re-use an input if possible', () {
test('identical inputs', () {
var p = {
var s0 = FlowModel<Type>(Reachability.initial);
var s1 = s0._setInfo(h, {
x: model([intType]),
y: model([stringType])
};
expect(FlowModel.joinPromotionInfo(h, p, p), same(p));
});
expect(FlowModel.joinPromotionInfo(h, s1, s1), same(s1));
});
test('one input empty', () {
var p1 = {
var s0 = FlowModel<Type>(Reachability.initial);
var s1 = s0._setInfo(h, {
x: model([intType]),
y: model([stringType])
};
var p2 = <int, PromotionModel<Type>>{};
const expected = const <int, PromotionModel<Never>>{};
expect(FlowModel.joinPromotionInfo(h, p1, p2), same(expected));
expect(FlowModel.joinPromotionInfo(h, p2, p1), same(expected));
});
var s2 = s0;
const Null expected = null;
expect(FlowModel.joinPromotionInfo(h, s1, s2).promotionInfo,
same(expected));
expect(FlowModel.joinPromotionInfo(h, s2, s1).promotionInfo,
same(expected));
});
test('promoted with unpromoted', () {
var p1 = {
var s0 = FlowModel<Type>(Reachability.initial);
var s1 = s0._setInfo(h, {
x: model([intType])
};
var p2 = {x: model(null)};
});
var s2 = s0._setInfo(h, {x: model(null)});
var expected = {
x: _matchVariableModel(chain: null, ofInterest: ['int'])
};
expect(FlowModel.joinPromotionInfo(h, p1, p2), expected);
expect(FlowModel.joinPromotionInfo(h, p2, p1), expected);
expect(FlowModel.joinPromotionInfo(h, s1, s2).promotionInfo.unwrap(h),
expected);
expect(FlowModel.joinPromotionInfo(h, s2, s1).promotionInfo.unwrap(h),
expected);
});
test('related type chains', () {
var p1 = {
var s0 = FlowModel<Type>(Reachability.initial);
var s1 = s0._setInfo(h, {
x: model([intQType, intType])
};
var p2 = {
});
var s2 = s0._setInfo(h, {
x: model([intQType])
};
});
var expected = {
x: _matchVariableModel(chain: ['int?'], ofInterest: ['int?', 'int'])
};
expect(FlowModel.joinPromotionInfo(h, p1, p2), expected);
expect(FlowModel.joinPromotionInfo(h, p2, p1), expected);
expect(FlowModel.joinPromotionInfo(h, s1, s2).promotionInfo.unwrap(h),
expected);
expect(FlowModel.joinPromotionInfo(h, s2, s1).promotionInfo.unwrap(h),
expected);
});
test('unrelated type chains', () {
var p1 = {
var s0 = FlowModel<Type>(Reachability.initial);
var s1 = s0._setInfo(h, {
x: model([intType])
};
var p2 = {
});
var s2 = s0._setInfo(h, {
x: model([stringType])
};
});
var expected = {
x: _matchVariableModel(chain: null, ofInterest: ['String', 'int'])
};
expect(FlowModel.joinPromotionInfo(h, p1, p2), expected);
expect(FlowModel.joinPromotionInfo(h, p2, p1), expected);
expect(FlowModel.joinPromotionInfo(h, s1, s2).promotionInfo.unwrap(h),
expected);
expect(FlowModel.joinPromotionInfo(h, s2, s1).promotionInfo.unwrap(h),
expected);
});
test('sub-map', () {
var s0 = FlowModel<Type>(Reachability.initial);
var xModel = model([intType]);
var p1 = {
var s1 = s0._setInfo(h, {
x: xModel,
y: model([stringType])
};
var p2 = {x: xModel};
expect(FlowModel.joinPromotionInfo(h, p1, p2), same(p2));
expect(FlowModel.joinPromotionInfo(h, p2, p1), same(p2));
});
var s2 = s0._setInfo(h, {x: xModel});
var expected = {x: xModel};
expect(FlowModel.joinPromotionInfo(h, s1, s2).promotionInfo.unwrap(h),
expected);
expect(FlowModel.joinPromotionInfo(h, s2, s1).promotionInfo.unwrap(h),
expected);
});
test('sub-map with matched subtype', () {
var p1 = {
var s0 = FlowModel<Type>(Reachability.initial);
var s1 = s0._setInfo(h, {
x: model([intQType, intType]),
y: model([stringType])
};
var p2 = {
});
var s2 = s0._setInfo(h, {
x: model([intQType])
};
});
var expected = {
x: _matchVariableModel(chain: ['int?'], ofInterest: ['int?', 'int'])
};
expect(FlowModel.joinPromotionInfo(h, p1, p2), expected);
expect(FlowModel.joinPromotionInfo(h, p2, p1), expected);
expect(FlowModel.joinPromotionInfo(h, s1, s2).promotionInfo.unwrap(h),
expected);
expect(FlowModel.joinPromotionInfo(h, s2, s1).promotionInfo.unwrap(h),
expected);
});
test('sub-map with mismatched subtype', () {
var p1 = {
var s0 = FlowModel<Type>(Reachability.initial);
var s1 = s0._setInfo(h, {
x: model([intQType]),
y: model([stringType])
};
var p2 = {
});
var s2 = s0._setInfo(h, {
x: model([intQType, intType])
};
});
var expected = {
x: _matchVariableModel(chain: ['int?'], ofInterest: ['int?', 'int'])
};
expect(FlowModel.joinPromotionInfo(h, p1, p2), expected);
expect(FlowModel.joinPromotionInfo(h, p2, p1), expected);
expect(FlowModel.joinPromotionInfo(h, s1, s2).promotionInfo.unwrap(h),
expected);
expect(FlowModel.joinPromotionInfo(h, s2, s1).promotionInfo.unwrap(h),
expected);
});
test('assigned', () {
var s0 = FlowModel<Type>(Reachability.initial);
var unassigned = model(null, assigned: false);
var assigned = model(null, assigned: true);
var p1 = {x: assigned, y: assigned, z: unassigned, w: unassigned};
var p2 = {x: assigned, y: unassigned, z: assigned, w: unassigned};
var joined = FlowModel.joinPromotionInfo(h, p1, p2);
expect(joined, {
var s1 = s0._setInfo(
h, {x: assigned, y: assigned, z: unassigned, w: unassigned});
var s2 = s0._setInfo(
h, {x: assigned, y: unassigned, z: assigned, w: unassigned});
var joined = FlowModel.joinPromotionInfo(h, s1, s2);
expect(joined.promotionInfo.unwrap(h), {
x: same(assigned),
y: _matchVariableModel(
chain: null, assigned: false, unassigned: false),
@ -4718,22 +4756,23 @@ main() {
});
test('write captured', () {
var s0 = FlowModel<Type>(Reachability.initial);
var intQModel = model([intQType]);
var writeCapturedModel = intQModel.writeCapture();
var p1 = {
var s1 = s0._setInfo(h, {
x: writeCapturedModel,
y: writeCapturedModel,
z: intQModel,
w: intQModel
};
var p2 = {
});
var s2 = s0._setInfo(h, {
x: writeCapturedModel,
y: intQModel,
z: writeCapturedModel,
w: intQModel
};
var joined = FlowModel.joinPromotionInfo(h, p1, p2);
expect(joined, {
});
var joined = FlowModel.joinPromotionInfo(h, s1, s2);
expect(joined.promotionInfo.unwrap(h), {
x: same(writeCapturedModel),
y: same(writeCapturedModel),
z: same(writeCapturedModel),
@ -4747,7 +4786,6 @@ main() {
late int x;
var intType = Type('int');
var stringType = Type('String');
const emptyMap = const <int, PromotionModel<Type>>{};
setUp(() {
x = h.promotionKeyStore.keyForVariable(Var('x')..type = Type('Object?'));
@ -4762,29 +4800,32 @@ main() {
ssaNode: new SsaNode<Type>(null));
test('inherits types of interest from other', () {
var m1 = FlowModel.withInfo(Reachability.initial, {
var m0 = FlowModel<Type>(Reachability.initial);
var m1 = m0._setInfo(h, {
x: model([intType])
});
var m2 = FlowModel.withInfo(Reachability.initial, {
var m2 = m0._setInfo(h, {
x: model([stringType])
});
expect(m1.inheritTested(h, m2).promotionInfo[x]!.tested,
expect(m1.inheritTested(h, m2).promotionInfo!.get(h, x)!.tested,
_matchOfInterestSet(['int', 'String']));
});
test('handles variable missing from other', () {
var m1 = FlowModel.withInfo(Reachability.initial, {
var m0 = FlowModel<Type>(Reachability.initial);
var m1 = m0._setInfo(h, {
x: model([intType])
});
var m2 = FlowModel.withInfo(Reachability.initial, emptyMap);
var m2 = m0;
expect(m1.inheritTested(h, m2), same(m1));
});
test('returns identical model when no changes', () {
var m1 = FlowModel.withInfo(Reachability.initial, {
var m0 = FlowModel<Type>(Reachability.initial);
var m1 = m0._setInfo(h, {
x: model([intType])
});
var m2 = FlowModel.withInfo(Reachability.initial, {
var m2 = m0._setInfo(h, {
x: model([intType])
});
expect(m1.inheritTested(h, m2), same(m1));
@ -11001,6 +11042,17 @@ extension on FlowModel<Type> {
infoFor(h, h.promotionKeyStore.keyForVariable(variable),
ssaNode: new SsaNode(null));
FlowModel<Type> _setInfo(
FlowAnalysisTestHarness h, Map<int, PromotionModel<Type>> newInfo) {
var result = this;
for (var core.MapEntry(:key, :value) in newInfo.entries) {
if (result.promotionInfo?.get(h, key) != value) {
result = result.updatePromotionInfo(h, key, value);
}
}
return result;
}
ExpressionInfo<Type> _tryMarkNonNullable(
FlowAnalysisTestHarness h, Var variable) =>
tryMarkNonNullable(h, _varRefWithType(h, variable));
@ -11017,7 +11069,8 @@ extension on FlowModel<Type> {
new TrivialVariableReference<Type>(
promotionKey: _varRef(h, variable),
after: this,
type: promotionInfo[h.promotionKeyStore.keyForVariable(variable)]
type: promotionInfo
?.get(h, h.promotionKeyStore.keyForVariable(variable))
?.promotedTypes
?.last ??
variable.type,
@ -11034,3 +11087,11 @@ extension on FlowModel<Type> {
writtenType, newSsaNode, h.typeOperations,
unpromotedType: variable.type);
}
extension on PromotionInfo<Type>? {
Map<int, PromotionModel<Type>> unwrap(FlowAnalysisTestHarness h) => {
for (var FlowLinkDiffEntry(:int key, right: second!)
in h.reader.diff(null, this).entries)
key: second.model
};
}

View file

@ -0,0 +1,221 @@
// Copyright (c) 2023, 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:_fe_analyzer_shared/src/flow_analysis/flow_link.dart';
import 'package:checks/checks.dart';
import 'package:test/scaffolding.dart';
main() {
group('get:', () {
test('handles forward step', () {
var a = _Link('A', key: 0, previous: null, previousForKey: null);
var b = _Link('B', key: 0, previous: a, previousForKey: a);
var c = _Link('C', key: 0, previous: b, previousForKey: b);
var reader = _FlowLinkReader();
check(reader.get(null, 0)).identicalTo(null);
check(reader.get(a, 0)).identicalTo(a);
check(reader.get(b, 0)).identicalTo(b);
check(reader.get(c, 0)).identicalTo(c);
});
test('handles backward step', () {
var a = _Link('A', key: 0, previous: null, previousForKey: null);
var b = _Link('B', key: 0, previous: a, previousForKey: a);
var c = _Link('C', key: 0, previous: b, previousForKey: b);
var reader = _FlowLinkReader();
check(reader.get(c, 0)).identicalTo(c);
check(reader.get(b, 0)).identicalTo(b);
check(reader.get(a, 0)).identicalTo(a);
check(reader.get(null, 0)).identicalTo(null);
});
test('handles side step', () {
// B C
// \ /
// A
var a = _Link('A', key: 0, previous: null, previousForKey: null);
var b = _Link('B', key: 0, previous: a, previousForKey: a);
var c = _Link('C', key: 0, previous: a, previousForKey: a);
var reader = _FlowLinkReader();
check(reader.get(b, 0)).identicalTo(b);
check(reader.get(c, 0)).identicalTo(c);
});
test('handles multiple cache entries', () {
var a = _Link('A', key: 0, previous: null, previousForKey: null);
var b = _Link('B', key: 1, previous: a, previousForKey: null);
var c = _Link('C', key: 2, previous: b, previousForKey: null);
var reader = _FlowLinkReader();
check(reader.get(a, 1)).identicalTo(null);
check(reader.get(a, 2)).identicalTo(null);
check(reader.get(c, 1)).identicalTo(b);
check(reader.get(c, 2)).identicalTo(c);
});
});
group('diff:', () {
test('trivial null', () {
var reader = _FlowLinkReader();
var diff = reader.diff(null, null);
check(diff.ancestor).identicalTo(null);
check(diff.entries).isEmpty();
});
test('trivial non-null', () {
var a = _Link('A', key: 0, previous: null, previousForKey: null);
var reader = _FlowLinkReader();
var diff = reader.diff(a, a);
check(diff.ancestor).identicalTo(a);
check(diff.entries).isEmpty();
});
test('finds common ancestor', () {
// E
// |
// D G
// | |
// C F
// \ /
// B
// |
// A
var a = _Link('A', key: 0, previous: null, previousForKey: null);
var b = _Link('B', key: 0, previous: a, previousForKey: a);
var c = _Link('C', key: 0, previous: b, previousForKey: b);
var d = _Link('D', key: 0, previous: c, previousForKey: c);
var e = _Link('E', key: 0, previous: d, previousForKey: d);
var f = _Link('F', key: 0, previous: b, previousForKey: b);
var g = _Link('G', key: 0, previous: f, previousForKey: f);
var reader = _FlowLinkReader();
check(reader.diff(e, g).ancestor).identicalTo(b);
});
test('stepLeft twice for the same key', () {
var a = _Link('A', key: 0, previous: null, previousForKey: null);
var b = _Link('B', key: 0, previous: a, previousForKey: a);
var c = _Link('C', key: 0, previous: b, previousForKey: b);
var reader = _FlowLinkReader();
var entries = reader.diff(c, a).entries;
check(entries).length.equals(1);
var entry = entries[0];
check(entry.key).equals(0);
check(entry.ancestor).identicalTo(a);
check(entry.left).identicalTo(c);
check(entry.right).identicalTo(a);
});
test('stepLeft handles multiple keys', () {
var a = _Link('A', key: 0, previous: null, previousForKey: null);
var b = _Link('B', key: 1, previous: a, previousForKey: null);
var c = _Link('C', key: 0, previous: b, previousForKey: a);
var d = _Link('D', key: 1, previous: c, previousForKey: b);
var reader = _FlowLinkReader();
var entries = reader.diff(d, b).entries;
check(entries).length.equals(2);
var entryMap = entries.toMap();
check(entryMap[0]!.ancestor).identicalTo(a);
check(entryMap[0]!.left).identicalTo(c);
check(entryMap[0]!.right).identicalTo(a);
check(entryMap[1]!.ancestor).identicalTo(b);
check(entryMap[1]!.left).identicalTo(d);
check(entryMap[1]!.right).identicalTo(b);
});
test('stepRight twice for the same key', () {
var a = _Link('A', key: 0, previous: null, previousForKey: null);
var b = _Link('B', key: 0, previous: a, previousForKey: a);
var c = _Link('C', key: 0, previous: b, previousForKey: b);
var reader = _FlowLinkReader();
var entries = reader.diff(a, c).entries;
check(entries).length.equals(1);
var entry = entries[0];
check(entry.key).equals(0);
check(entry.ancestor).identicalTo(a);
check(entry.left).identicalTo(a);
check(entry.right).identicalTo(c);
});
test('stepRight handles multiple keys', () {
var a = _Link('A', key: 0, previous: null, previousForKey: null);
var b = _Link('B', key: 1, previous: a, previousForKey: null);
var c = _Link('C', key: 0, previous: b, previousForKey: a);
var d = _Link('D', key: 1, previous: c, previousForKey: b);
var reader = _FlowLinkReader();
var entries = reader.diff(b, d).entries;
check(entries).length.equals(2);
var entryMap = entries.toMap();
check(entryMap[0]!.ancestor).identicalTo(a);
check(entryMap[0]!.left).identicalTo(a);
check(entryMap[0]!.right).identicalTo(c);
check(entryMap[1]!.ancestor).identicalTo(b);
check(entryMap[1]!.left).identicalTo(b);
check(entryMap[1]!.right).identicalTo(d);
});
test('multiple keys with stepLeft and stepRight', () {
// B(0) D(1)
// | |
// A(1) C(0)
// \ /
// null
var a = _Link('A', key: 1, previous: null, previousForKey: null);
var b = _Link('B', key: 0, previous: a, previousForKey: null);
var c = _Link('C', key: 0, previous: null, previousForKey: null);
var d = _Link('D', key: 1, previous: c, previousForKey: null);
var reader = _FlowLinkReader();
var entries = reader.diff(b, d).entries;
check(entries).length.equals(2);
var entryMap = entries.toMap();
check(entryMap[0]!.ancestor).identicalTo(null);
check(entryMap[0]!.left).identicalTo(b);
check(entryMap[0]!.right).identicalTo(c);
check(entryMap[1]!.ancestor).identicalTo(null);
check(entryMap[1]!.left).identicalTo(a);
check(entryMap[1]!.right).identicalTo(d);
});
test('diffs only on one side', () {
// A(1) B(0)
// \ /
// null
var a = _Link('A', key: 1, previous: null, previousForKey: null);
var b = _Link('B', key: 0, previous: null, previousForKey: null);
var reader = _FlowLinkReader();
var entries = reader.diff(a, b).entries;
check(entries).length.equals(2);
var entryMap = entries.toMap();
check(entryMap[0]!.ancestor).identicalTo(null);
check(entryMap[0]!.left).identicalTo(null);
check(entryMap[0]!.right).identicalTo(b);
check(entryMap[1]!.ancestor).identicalTo(null);
check(entryMap[1]!.left).identicalTo(a);
check(entryMap[1]!.right).identicalTo(null);
});
});
}
class _FlowLinkReader extends FlowLinkReader<_Link> {}
base class _Link extends FlowLink<_Link> {
final String debugName;
_Link(this.debugName,
{required super.key,
required super.previous,
required super.previousForKey});
@override
String toString() => debugName;
}
extension on List<FlowLinkDiffEntry<_Link>> {
Map<int, FlowLinkDiffEntry<_Link>> toMap() {
Map<int, FlowLinkDiffEntry<_Link>> result = {};
for (var entry in this) {
check(result[entry.key]).isNull();
result[entry.key] = entry;
}
return result;
}
}

View file

@ -3154,7 +3154,7 @@ class List<E> extends Iterable<E> {
E operator [](int index) => null;
}
class _GrowableList<E> {
class _GrowableList<E> implements List<E> {
factory _GrowableList(int length) => null;
factory _GrowableList.empty() => null;
factory _GrowableList.filled() => null;
@ -3167,6 +3167,10 @@ class _GrowableList<E> {
factory _GrowableList._literal6(E e0, E e1, E e2, E e3, E e4, E e5) => null;
factory _GrowableList._literal7(E e0, E e1, E e2, E e3, E e4, E e5, E e6) => null;
factory _GrowableList._literal8(E e0, E e1, E e2, E e3, E e4, E e5, E e6, E e7) => null;
void add(E element) {}
void addAll(Iterable<E> iterable) {}
Iterator<E> get iterator => null;
E operator [](int index) => null;
}
class _List<E> {

View file

@ -67,6 +67,7 @@ apparently
applicable
approximation
architectures
area
arg
args
arise
@ -118,6 +119,7 @@ balance
bang
bar
basically
basis
batch
baz
bazel
@ -209,6 +211,7 @@ ce
ceil
cfe
ch
chaining
channel
char
charcode
@ -268,6 +271,7 @@ complement
complementary
completers
completes
complexity
complicated
complicating
component's
@ -287,6 +291,7 @@ condensed
config
configs
configured
confusing
connecting
connectivity
consecutive
@ -447,6 +452,7 @@ doc
docs
doesn\'t
dom
dominator
dont
doubles
downcasts
@ -608,6 +614,7 @@ forcing
foreign
formed
former
forming
fortunately
fourth
framework
@ -628,6 +635,7 @@ futured
futureor
fvar
g
garbage
gardening
garner
gathers
@ -740,6 +748,7 @@ inc
incomparable
inconsistency
increased
increasing
incremented
incrementing
independently
@ -1237,6 +1246,7 @@ realign
realise
reapplies
reapply
reapplying
reasonably
reassigned
reassigns
@ -1253,6 +1263,7 @@ recalls
received
receivers
recheck
reclaimed
recognizing
recompile
recompiled
@ -1318,6 +1329,7 @@ replaces
replicated
repo
repositories
reproduces
repurposed
repurposing
requesting
@ -1337,8 +1349,10 @@ ret
retrieval
reusage
reversible
rewinding
rewinds
rework
rewound
rewrites
rewrote
rf
@ -1441,6 +1455,7 @@ silenced
simplifier
simplify
singleton
singly
sinker
site
six
@ -1553,6 +1568,7 @@ subtracted
subtracting
subtraction
subtracts
successive
suffixed
suffixing
sugared