Skip to content

Commit bf6ddd2

Browse files
stereotype441Commit Queue
authored andcommitted
[flow analysis] Do not promote to mutual subtypes.
Previously, flow analysis had the rule that type promotion only occurred when the type being tested was a subtype of the previously promoted type (or the declared type, if there was no previous promotion). This led to counterintuitive behaviors when the type being tested and the previously promoted type were mutual subtypes (see dart-lang/language#4368). With this change, the rule is updated so that type promotion only occurs when the type being tested is a subtype of the previously promoted type _and_ the previously promoted type is _not_ a subtype of the type being tested. The user-visible difference is that promotion to a mutual subtype no longer occurs. This change makes flow analysis easier to reason about, and improves its behavior in corner cases, but I believe it will have minimal impact on real-world code. But to reduce the risk to existing code, the change only takes effect when the `sound-flow-analysis` language feature is enabled. Fixes dart-lang/language#4368. Bug: dart-lang/language#4368 Change-Id: I30dab017e043e75603d618df721c8a2683667cd5 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/429200 Reviewed-by: Johnni Winther <[email protected]> Reviewed-by: Konstantin Shcheglov <[email protected]> Commit-Queue: Paul Berry <[email protected]>
1 parent 73a85ec commit bf6ddd2

18 files changed

+982
-113
lines changed

pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart

Lines changed: 78 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2669,7 +2669,7 @@ class FlowModel<Type extends Object> {
26692669
// In all of these cases, the correct thing to do is to keep all
26702670
// promotions that were done in both the `try` and `finally` blocks.
26712671
newPromotedTypes = PromotionModel.rebasePromotedTypes(
2672-
helper.typeOperations,
2672+
helper,
26732673
thisModel.promotedTypes,
26742674
afterFinallyModel.promotedTypes,
26752675
);
@@ -2950,7 +2950,7 @@ class FlowModel<Type extends Object> {
29502950
// usual "promotion chain" invariant (each promoted type is a subtype of
29512951
// the previous).
29522952
newPromotedTypes = PromotionModel.rebasePromotedTypes(
2953-
helper.typeOperations,
2953+
helper,
29542954
thisModel.promotedTypes,
29552955
baseModel.promotedTypes,
29562956
);
@@ -3028,13 +3028,15 @@ class FlowModel<Type extends Object> {
30283028

30293029
Type previousType = reference._type;
30303030
Type newType = helper.typeOperations.promoteToNonNull(previousType);
3031-
if (newType == previousType) {
3031+
if (!helper.isValidPromotionStep(
3032+
previousType: previousType,
3033+
newType: newType,
3034+
)) {
30323035
return new ExpressionInfo<Type>.trivial(
30333036
model: this,
30343037
type: helper.boolType,
30353038
);
30363039
}
3037-
assert(helper.typeOperations.isSubtypeOf(newType, previousType));
30383040

30393041
FlowModel<Type> ifTrue = _finishTypeTest(
30403042
helper,
@@ -3075,14 +3077,14 @@ class FlowModel<Type extends Object> {
30753077

30763078
Type previousType = reference._type;
30773079
Type? newType = helper.typeOperations.tryPromoteToType(type, previousType);
3078-
if (newType == null || newType == previousType) {
3080+
if (newType == null ||
3081+
!helper.isValidPromotionStep(
3082+
previousType: previousType,
3083+
newType: newType,
3084+
)) {
30793085
return this;
30803086
}
30813087

3082-
assert(
3083-
helper.typeOperations.isSubtypeOf(newType, previousType),
3084-
"Expected $newType to be a subtype of $previousType.",
3085-
);
30863088
return _finishTypeTest(helper, reference, info, type, newType);
30873089
}
30883090

@@ -3118,11 +3120,11 @@ class FlowModel<Type extends Object> {
31183120
type,
31193121
previousType,
31203122
);
3121-
if (typeIfSuccess != null && typeIfSuccess != previousType) {
3122-
assert(
3123-
helper.typeOperations.isSubtypeOf(typeIfSuccess, previousType),
3124-
"Expected $typeIfSuccess to be a subtype of $previousType.",
3125-
);
3123+
if (typeIfSuccess != null &&
3124+
helper.isValidPromotionStep(
3125+
previousType: previousType,
3126+
newType: typeIfSuccess,
3127+
)) {
31263128
ifTrue = _finishTypeTest(helper, reference, info, type, typeIfSuccess);
31273129
}
31283130

@@ -3132,8 +3134,11 @@ class FlowModel<Type extends Object> {
31323134
// Promoting to `Never` would mark the code as unreachable. But it might
31333135
// be reachable due to mixed mode unsoundness. So don't promote.
31343136
typeIfFalse = null;
3135-
} else if (factoredType == previousType) {
3136-
// No change to the type, so don't promote.
3137+
} else if (!helper.isValidPromotionStep(
3138+
previousType: previousType,
3139+
newType: factoredType,
3140+
)) {
3141+
// Don't promote.
31373142
typeIfFalse = null;
31383143
} else {
31393144
typeIfFalse = factoredType;
@@ -3198,19 +3203,18 @@ class FlowModel<Type extends Object> {
31983203
NonPromotionReason? nonPromotionReason,
31993204
int variableKey,
32003205
Type writtenType,
3201-
SsaNode<Type> newSsaNode,
3202-
FlowAnalysisOperations<Variable, Type> operations, {
3206+
SsaNode<Type> newSsaNode, {
32033207
bool promoteToTypeOfInterest = true,
32043208
required Type unpromotedType,
32053209
}) {
32063210
FlowModel<Type>? newModel;
32073211
PromotionModel<Type>? infoForVar = promotionInfo?.get(helper, variableKey);
32083212
if (infoForVar != null) {
32093213
PromotionModel<Type> newInfoForVar = infoForVar.write(
3214+
helper,
32103215
nonPromotionReason,
32113216
variableKey,
32123217
writtenType,
3213-
operations,
32143218
newSsaNode,
32153219
promoteToTypeOfInterest: promoteToTypeOfInterest,
32163220
unpromotedType: unpromotedType,
@@ -3386,6 +3390,15 @@ mixin FlowModelHelper<Type extends Object> {
33863390
/// Whether the variable of [variableKey] was declared with the `final`
33873391
/// modifier and the `inference-update-4` feature flag is enabled.
33883392
bool isFinal(int variableKey);
3393+
3394+
/// Determines whether a promotion from type [previousType] to [newType] is
3395+
/// allowed to occur, given the current configuration of flow analysis.
3396+
///
3397+
/// Caller is required to ensure that `newType <: previousType`.
3398+
bool isValidPromotionStep({
3399+
required Type previousType,
3400+
required Type newType,
3401+
});
33893402
}
33903403

33913404
/// Documentation links that might be presented to the user to accompany a "why
@@ -3717,10 +3730,10 @@ class PromotionModel<Type extends Object> {
37173730
/// must pass in a non-null value for [nonPromotionReason] describing the
37183731
/// reason for any potential demotion.
37193732
PromotionModel<Type> write<Variable extends Object>(
3733+
FlowModelHelper<Type> helper,
37203734
NonPromotionReason? nonPromotionReason,
37213735
int variableKey,
37223736
Type writtenType,
3723-
FlowAnalysisOperations<Variable, Type> operations,
37243737
SsaNode<Type> newSsaNode, {
37253738
required bool promoteToTypeOfInterest,
37263739
required Type unpromotedType,
@@ -3737,14 +3750,14 @@ class PromotionModel<Type extends Object> {
37373750

37383751
_DemotionResult<Type> demotionResult = _demoteViaAssignment(
37393752
writtenType,
3740-
operations,
3753+
helper.typeOperations,
37413754
nonPromotionReason,
37423755
);
37433756
List<Type>? newPromotedTypes = demotionResult.promotedTypes;
37443757

37453758
if (promoteToTypeOfInterest) {
37463759
newPromotedTypes = _tryPromoteToTypeOfInterest(
3747-
operations,
3760+
helper,
37483761
unpromotedType,
37493762
newPromotedTypes,
37503763
writtenType,
@@ -3858,7 +3871,7 @@ class PromotionModel<Type extends Object> {
38583871
/// Note that since promotion chains are considered immutable, if promotion
38593872
/// is required, a new promotion chain will be created and returned.
38603873
List<Type>? _tryPromoteToTypeOfInterest(
3861-
FlowAnalysisTypeOperations<Type> typeOperations,
3874+
FlowModelHelper<Type> helper,
38623875
Type declaredType,
38633876
List<Type>? promotedTypes,
38643877
Type writtenType,
@@ -3875,15 +3888,18 @@ class PromotionModel<Type extends Object> {
38753888

38763889
void handleTypeOfInterest(Type type) {
38773890
// The written type must be a subtype of the type.
3878-
if (!typeOperations.isSubtypeOf(writtenType, type)) {
3891+
if (!helper.typeOperations.isSubtypeOf(writtenType, type)) {
38793892
return;
38803893
}
38813894

38823895
// Must be more specific that the currently promoted type.
3883-
if (type == currentlyPromotedType) {
3896+
if (!helper.typeOperations.isSubtypeOf(type, currentlyPromotedType)) {
38843897
return;
38853898
}
3886-
if (!typeOperations.isSubtypeOf(type, currentlyPromotedType)) {
3899+
if (!helper.isValidPromotionStep(
3900+
previousType: currentlyPromotedType,
3901+
newType: type,
3902+
)) {
38873903
return;
38883904
}
38893905

@@ -3906,7 +3922,9 @@ class PromotionModel<Type extends Object> {
39063922

39073923
// The declared type is always a type of interest, but we never promote
39083924
// to the declared type. So, try NonNull of it.
3909-
Type declaredTypeNonNull = typeOperations.promoteToNonNull(declaredType);
3925+
Type declaredTypeNonNull = helper.typeOperations.promoteToNonNull(
3926+
declaredType,
3927+
);
39103928
if (declaredTypeNonNull != declaredType) {
39113929
handleTypeOfInterest(declaredTypeNonNull);
39123930
if (result != null) {
@@ -3922,7 +3940,7 @@ class PromotionModel<Type extends Object> {
39223940
return result!;
39233941
}
39243942

3925-
Type typeNonNull = typeOperations.promoteToNonNull(type);
3943+
Type typeNonNull = helper.typeOperations.promoteToNonNull(type);
39263944
if (typeNonNull != type) {
39273945
handleTypeOfInterest(typeNonNull);
39283946
if (result != null) {
@@ -3940,7 +3958,10 @@ class PromotionModel<Type extends Object> {
39403958
for (int i = 0; i < candidates2.length; i++) {
39413959
for (int j = 0; j < candidates2.length; j++) {
39423960
if (j == i) continue;
3943-
if (!typeOperations.isSubtypeOf(candidates2[i], candidates2[j])) {
3961+
if (!helper.typeOperations.isSubtypeOf(
3962+
candidates2[i],
3963+
candidates2[j],
3964+
)) {
39443965
// Not a subtype of all the others.
39453966
continue outer;
39463967
}
@@ -4127,7 +4148,7 @@ class PromotionModel<Type extends Object> {
41274148
/// [thisPromotedTypes] or [basePromotedTypes] (to make it easier for the
41284149
/// caller to detect when data structures may be re-used).
41294150
static List<Type>? rebasePromotedTypes<Type extends Object>(
4130-
FlowAnalysisTypeOperations<Type> typeOperations,
4151+
FlowModelHelper<Type> helper,
41314152
List<Type>? thisPromotedTypes,
41324153
List<Type>? basePromotedTypes,
41334154
) {
@@ -4147,8 +4168,11 @@ class PromotionModel<Type extends Object> {
41474168
Type otherPromotedType = basePromotedTypes.last;
41484169
for (int i = 0; i < thisPromotedTypes.length; i++) {
41494170
Type nextType = thisPromotedTypes[i];
4150-
if (typeOperations.isSubtypeOf(nextType, otherPromotedType) &&
4151-
nextType != otherPromotedType) {
4171+
if (helper.typeOperations.isSubtypeOf(nextType, otherPromotedType) &&
4172+
helper.isValidPromotionStep(
4173+
previousType: otherPromotedType,
4174+
newType: nextType,
4175+
)) {
41524176
newPromotedTypes =
41534177
basePromotedTypes.toList()..addAll(thisPromotedTypes.skip(i));
41544178
break;
@@ -4582,7 +4606,7 @@ class SsaNode<Type extends Object> {
45824606
}
45834607
List<Type>? newPromotedTypes = newModel.promotedTypes;
45844608
List<Type>? rebasedPromotedTypes = PromotionModel.rebasePromotedTypes(
4585-
helper.typeOperations,
4609+
helper,
45864610
afterFinallyPromotedTypes,
45874611
newPromotedTypes,
45884612
);
@@ -5772,6 +5796,27 @@ class _FlowAnalysisImpl<
57725796
true;
57735797
}
57745798

5799+
@override
5800+
bool isValidPromotionStep({
5801+
required Type previousType,
5802+
required Type newType,
5803+
}) {
5804+
// Caller must ensure that `newType <: previousType`.
5805+
assert(
5806+
typeOperations.isSubtypeOf(newType, previousType),
5807+
"Expected $newType to be a subtype of $previousType.",
5808+
);
5809+
if (this.typeAnalyzerOptions.soundFlowAnalysisEnabled) {
5810+
// Promotion to a mutual subtype is not allowed. Since the caller has
5811+
// already ensured that `newType <: previousType`, it's only necessary to
5812+
// check whether `previousType <: newType`.
5813+
return !typeOperations.isSubtypeOf(previousType, newType);
5814+
} else {
5815+
// Repeated promotion to the same type is not allowed.
5816+
return newType != previousType;
5817+
}
5818+
}
5819+
57755820
@override
57765821
void labeledStatement_begin(Statement node) {
57775822
_current = _current.split();
@@ -7126,7 +7171,6 @@ class _FlowAnalysisImpl<
71267171
promotionKey,
71277172
matchedType,
71287173
newSsaNode,
7129-
operations,
71307174
promoteToTypeOfInterest: !isImplicitlyTyped && !isFinal,
71317175
unpromotedType: unpromotedType,
71327176
);
@@ -7394,7 +7438,6 @@ class _FlowAnalysisImpl<
73947438
variableKey,
73957439
writtenType,
73967440
newSsaNode,
7397-
operations,
73987441
unpromotedType: unpromotedType,
73997442
);
74007443

pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_mini_ast.dart

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,22 @@ class FlowAnalysisTestHarness extends Harness
4848
if (variable != null && operations.isFinal(variable)) return true;
4949
return false;
5050
}
51+
52+
@override
53+
bool isValidPromotionStep({
54+
required SharedTypeView previousType,
55+
required SharedTypeView newType,
56+
}) {
57+
// Caller must ensure that `newType <: previousType`.
58+
assert(
59+
typeOperations.isSubtypeOf(newType, previousType),
60+
"Expected $newType to be a subtype of $previousType.",
61+
);
62+
// Promotion to a mutual subtype is not allowed. Since the caller has
63+
// already ensured that `newType <: previousType`, it's only necessary to
64+
// check whether `previousType <: newType`.
65+
return !typeOperations.isSubtypeOf(previousType, newType);
66+
}
5167
}
5268

5369
/// Helper class allowing tests to examine the values of variables' SSA nodes.

0 commit comments

Comments
 (0)