@@ -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
0 commit comments