mirror of
https://github.com/dart-lang/sdk
synced 2024-09-18 20:01:19 +00:00
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:
parent
de302d7f3b
commit
2887fe0b2c
|
@ -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,
|
||||
|
|
351
pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_link.dart
Normal file
351
pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_link.dart
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
221
pkg/_fe_analyzer_shared/test/flow_analysis/flow_link_test.dart
Normal file
221
pkg/_fe_analyzer_shared/test/flow_analysis/flow_link_test.dart
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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> {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue