[flow] Issue 1721 - Allow better promotions for final variables.
Promotions should happen for final variables because they are assigned and won't be re-assigned by the time they're evaluated. In a conservative join, promotions should not be cancelled for final variables.
Language tests made in https://dart-review.googlesource.com/c/sdk/+/390340.
Bug: https://github.com/dart-lang/language/issues/1721
Change-Id: I7bb577a694ddb5572a28884de70bd8c5b68e3c25
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/390803
Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
Reviewed-by: Paul Berry <paulberry@google.com>
Commit-Queue: Kallen Tu <kallentu@google.com>
diff --git a/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart b/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
index f9f59cf..ff20d36 100644
--- a/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
@@ -2313,6 +2313,11 @@
PromotionModel<Type>? info =
result.promotionInfo?.get(helper, variableKey);
if (info == null) continue;
+
+ // We don't need to discard promotions for final variables. They are
+ // guaranteed to be already assigned and won't be assigned again.
+ if (helper.isFinal(variableKey)) continue;
+
PromotionModel<Type> newInfo =
info.discardPromotionsAndMarkNotUnassigned();
if (!identical(info, newInfo)) {
@@ -2823,6 +2828,10 @@
/// subtyping.
@visibleForTesting
FlowAnalysisTypeOperations<Type> get typeOperations;
+
+ /// Whether the variable of [variableKey] was declared with the `final`
+ /// modifier and the `inference-update-4` feature flag is enabled.
+ bool isFinal(int variableKey);
}
/// Documentation links that might be presented to the user to accompany a "why
@@ -4994,6 +5003,14 @@
}
@override
+ bool isFinal(int variableKey) {
+ if (!inferenceUpdate4Enabled) return false;
+ Variable? variable = promotionKeyStore.variableForKey(variableKey);
+ if (variable != null && operations.isFinal(variable)) return true;
+ return false;
+ }
+
+ @override
bool isUnassigned(Variable variable) {
return _current.promotionInfo
?.get(this, promotionKeyStore.keyForVariable(variable))
diff --git a/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis_operations.dart b/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis_operations.dart
index b51a594..2e08988 100644
--- a/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis_operations.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis_operations.dart
@@ -10,6 +10,9 @@
/// representation of variables and types.
abstract interface class FlowAnalysisOperations<Variable extends Object,
Type extends Object> implements FlowAnalysisTypeOperations<Type> {
+ /// Whether the given [variable] was declared with the `final` modifier.
+ bool isFinal(Variable variable);
+
/// Determines whether the given property can be promoted.
///
/// [property] will correspond to a `propertyMember` value passed to
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_mini_ast.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_mini_ast.dart
index 537738b..4508426 100644
--- a/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_mini_ast.dart
+++ b/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_mini_ast.dart
@@ -39,6 +39,13 @@
@override
FlowAnalysisOperations<Var, SharedTypeView<Type>> get typeOperations =>
typeAnalyzer.operations;
+
+ @override
+ bool isFinal(int variableKey) {
+ Var? variable = promotionKeyStore.variableForKey(variableKey);
+ if (variable != null && operations.isFinal(variable)) return true;
+ return false;
+ }
}
/// Helper class allowing tests to examine the values of variables' SSA nodes.
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart
index 574c72f..092bb18 100644
--- a/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart
+++ b/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart
@@ -1018,6 +1018,99 @@
]);
});
+ test(
+ 'functionExpression_begin() cancels promotions of final vars'
+ ' with inference-update-4 disabled', () {
+ // See test for "functionExpression_begin() preserves promotions of final
+ // variables" for enabled behavior.
+ var x = Var('x', isFinal: true);
+ h.disableInferenceUpdate4();
+ h.run([
+ declare(x, type: 'num'),
+ if_(expr('bool'), [
+ x.write(expr('int')),
+ ], [
+ x.write(expr('double')),
+ ]),
+ if_(x.is_('int'), [
+ localFunction([
+ checkNotPromoted(x),
+ ]),
+ ], [
+ localFunction([
+ checkNotPromoted(x),
+ ]),
+ ]),
+ ]);
+ });
+
+ test('functionExpression_begin() cancels promotions of non-final vars', () {
+ // num x;
+ // if (<bool>) {
+ // x = <int>;
+ // } else {
+ // x = <double>;
+ // }
+ // if (x is int) {
+ // () => x is not promoted
+ // } else {
+ // () => x is not promoted
+ // }
+
+ var x = Var('x');
+ h.run([
+ declare(x, type: 'num'),
+ if_(expr('bool'), [
+ x.write(expr('int')),
+ ], [
+ x.write(expr('double')),
+ ]),
+ if_(x.is_('int'), [
+ localFunction([
+ checkNotPromoted(x),
+ ]),
+ ], [
+ localFunction([
+ checkNotPromoted(x),
+ ]),
+ ]),
+ ]);
+ });
+
+ test('functionExpression_begin() preserves promotions of final variables',
+ () {
+ // final num x;
+ // if (<bool>) {
+ // x = <int>;
+ // } else {
+ // x = <double>;
+ // }
+ // if (x is int) {
+ // () => x is promoted to int
+ // } else {
+ // () => x is not promoted
+ // }
+
+ var x = Var('x', isFinal: true);
+ h.run([
+ declare(x, type: 'num'),
+ if_(expr('bool'), [
+ x.write(expr('int')),
+ ], [
+ x.write(expr('double')),
+ ]),
+ if_(x.is_('int'), [
+ localFunction([
+ checkPromoted(x, 'int'),
+ ]),
+ ], [
+ localFunction([
+ checkNotPromoted(x),
+ ]),
+ ]),
+ ]);
+ });
+
test('functionExpression_begin() preserves promotions of initialized vars',
() {
var x = Var('x');
diff --git a/pkg/_fe_analyzer_shared/test/mini_ast.dart b/pkg/_fe_analyzer_shared/test/mini_ast.dart
index 9bb9d64..1c7dd83 100644
--- a/pkg/_fe_analyzer_shared/test/mini_ast.dart
+++ b/pkg/_fe_analyzer_shared/test/mini_ast.dart
@@ -2964,6 +2964,11 @@
}
@override
+ bool isFinal(Var variable) {
+ return variable.isFinal;
+ }
+
+ @override
bool isInterfaceType(SharedTypeView<Type> type) {
Type unwrappedType = type.unwrapTypeView();
return unwrappedType is PrimaryType && unwrappedType.isInterfaceType;
diff --git a/pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart b/pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart
index 219759e..8313e98 100644
--- a/pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart
@@ -544,6 +544,11 @@
}
@override
+ bool isFinal(PromotableElement variable) {
+ return variable.isFinal;
+ }
+
+ @override
bool isInterfaceType(SharedTypeView<DartType> type) {
DartType unwrappedType = type.unwrapTypeView();
return unwrappedType is InterfaceType &&
diff --git a/pkg/analyzer/test/src/dart/resolution/if_statement_test.dart b/pkg/analyzer/test/src/dart/resolution/if_statement_test.dart
index 4243e1d..fbea420 100644
--- a/pkg/analyzer/test/src/dart/resolution/if_statement_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/if_statement_test.dart
@@ -2,6 +2,7 @@
// 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:analyzer/dart/analysis/features.dart';
import 'package:analyzer/src/dart/error/syntactic_errors.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -11,6 +12,7 @@
main() {
defineReflectiveSuite(() {
defineReflectiveTests(IfStatementResolutionTest);
+ defineReflectiveTests(InferenceUpdate4Test);
});
}
@@ -1405,3 +1407,294 @@
''');
}
}
+
+@reflectiveTest
+class InferenceUpdate4Test extends PubPackageResolutionTest {
+ @override
+ List<String> get experiments {
+ return [
+ ...super.experiments,
+ Feature.inference_update_4.enableString,
+ ];
+ }
+
+ test_finalPromotionKept_isExpression() async {
+ await assertNoErrorsInCode('''
+f(bool b) {
+ final num x;
+ if (b) {
+ x = 1;
+ } else {
+ x = 0.1;
+ }
+ if (x is int) {
+ () => x.isEven;
+ }
+}
+''');
+
+ assertResolvedNodeText(
+ findNode.ifStatement('if (x is int) {'),
+ r'''
+IfStatement
+ ifKeyword: if
+ leftParenthesis: (
+ expression: IsExpression
+ expression: SimpleIdentifier
+ token: x
+ staticElement: x@24
+ element: x@24
+ staticType: num
+ isOperator: is
+ type: NamedType
+ name: int
+ element: dart:core::<fragment>::@class::int
+ element2: dart:core::<fragment>::@class::int#element
+ type: int
+ staticType: bool
+ rightParenthesis: )
+ thenStatement: Block
+ leftBracket: {
+ statements
+ ExpressionStatement
+ expression: FunctionExpression
+ parameters: FormalParameterList
+ leftParenthesis: (
+ rightParenthesis: )
+ body: ExpressionFunctionBody
+ functionDefinition: =>
+ expression: PrefixedIdentifier
+ prefix: SimpleIdentifier
+ token: x
+ staticElement: x@24
+ element: x@24
+ staticType: int
+ period: .
+ identifier: SimpleIdentifier
+ token: isEven
+ staticElement: dart:core::<fragment>::@class::int::@getter::isEven
+ element: dart:core::<fragment>::@class::int::@getter::isEven#element
+ staticType: bool
+ staticElement: dart:core::<fragment>::@class::int::@getter::isEven
+ element: dart:core::<fragment>::@class::int::@getter::isEven#element
+ staticType: bool
+ declaredElement: @99
+ type: bool Function()
+ staticType: bool Function()
+ semicolon: ;
+ rightBracket: }
+''',
+ );
+ }
+
+ test_finalPromotionKept_isExpression_late() async {
+ await assertNoErrorsInCode('''
+f(bool b) {
+ late final num x;
+ if (b) {
+ x = 1;
+ } else {
+ x = 0.1;
+ }
+ if (x is int) {
+ () => x.isEven;
+ }
+}
+''');
+
+ assertResolvedNodeText(
+ findNode.ifStatement('if (x is int) {'),
+ r'''
+IfStatement
+ ifKeyword: if
+ leftParenthesis: (
+ expression: IsExpression
+ expression: SimpleIdentifier
+ token: x
+ staticElement: x@29
+ element: x@29
+ staticType: num
+ isOperator: is
+ type: NamedType
+ name: int
+ element: dart:core::<fragment>::@class::int
+ element2: dart:core::<fragment>::@class::int#element
+ type: int
+ staticType: bool
+ rightParenthesis: )
+ thenStatement: Block
+ leftBracket: {
+ statements
+ ExpressionStatement
+ expression: FunctionExpression
+ parameters: FormalParameterList
+ leftParenthesis: (
+ rightParenthesis: )
+ body: ExpressionFunctionBody
+ functionDefinition: =>
+ expression: PrefixedIdentifier
+ prefix: SimpleIdentifier
+ token: x
+ staticElement: x@29
+ element: x@29
+ staticType: int
+ period: .
+ identifier: SimpleIdentifier
+ token: isEven
+ staticElement: dart:core::<fragment>::@class::int::@getter::isEven
+ element: dart:core::<fragment>::@class::int::@getter::isEven#element
+ staticType: bool
+ staticElement: dart:core::<fragment>::@class::int::@getter::isEven
+ element: dart:core::<fragment>::@class::int::@getter::isEven#element
+ staticType: bool
+ declaredElement: @104
+ type: bool Function()
+ staticType: bool Function()
+ semicolon: ;
+ rightBracket: }
+''',
+ );
+ }
+
+ test_finalPromotionKept_notEqNull() async {
+ await assertNoErrorsInCode('''
+f(bool b) {
+ final int? x;
+ if (b) {
+ x = 1;
+ } else {
+ x = null;
+ }
+ if (x != null) {
+ () => x.isEven;
+ }
+}
+''');
+
+ assertResolvedNodeText(
+ findNode.ifStatement('if (x != null) {'),
+ r'''
+IfStatement
+ ifKeyword: if
+ leftParenthesis: (
+ expression: BinaryExpression
+ leftOperand: SimpleIdentifier
+ token: x
+ staticElement: x@25
+ element: x@25
+ staticType: int?
+ operator: !=
+ rightOperand: NullLiteral
+ literal: null
+ parameter: dart:core::<fragment>::@class::num::@method::==::@parameter::other
+ staticType: Null
+ staticElement: dart:core::<fragment>::@class::num::@method::==
+ element: dart:core::<fragment>::@class::num::@method::==#element
+ staticInvokeType: bool Function(Object)
+ staticType: bool
+ rightParenthesis: )
+ thenStatement: Block
+ leftBracket: {
+ statements
+ ExpressionStatement
+ expression: FunctionExpression
+ parameters: FormalParameterList
+ leftParenthesis: (
+ rightParenthesis: )
+ body: ExpressionFunctionBody
+ functionDefinition: =>
+ expression: PrefixedIdentifier
+ prefix: SimpleIdentifier
+ token: x
+ staticElement: x@25
+ element: x@25
+ staticType: int
+ period: .
+ identifier: SimpleIdentifier
+ token: isEven
+ staticElement: dart:core::<fragment>::@class::int::@getter::isEven
+ element: dart:core::<fragment>::@class::int::@getter::isEven#element
+ staticType: bool
+ staticElement: dart:core::<fragment>::@class::int::@getter::isEven
+ element: dart:core::<fragment>::@class::int::@getter::isEven#element
+ staticType: bool
+ declaredElement: @102
+ type: bool Function()
+ staticType: bool Function()
+ semicolon: ;
+ rightBracket: }
+''',
+ );
+ }
+
+ test_finalPromotionKept_notEqNull_late() async {
+ await assertNoErrorsInCode('''
+f(bool b) {
+ late final int? x;
+ if (b) {
+ x = 1;
+ } else {
+ x = null;
+ }
+ if (x != null) {
+ () => x.isEven;
+ }
+}
+''');
+
+ assertResolvedNodeText(
+ findNode.ifStatement('if (x != null) {'),
+ r'''
+IfStatement
+ ifKeyword: if
+ leftParenthesis: (
+ expression: BinaryExpression
+ leftOperand: SimpleIdentifier
+ token: x
+ staticElement: x@30
+ element: x@30
+ staticType: int?
+ operator: !=
+ rightOperand: NullLiteral
+ literal: null
+ parameter: dart:core::<fragment>::@class::num::@method::==::@parameter::other
+ staticType: Null
+ staticElement: dart:core::<fragment>::@class::num::@method::==
+ element: dart:core::<fragment>::@class::num::@method::==#element
+ staticInvokeType: bool Function(Object)
+ staticType: bool
+ rightParenthesis: )
+ thenStatement: Block
+ leftBracket: {
+ statements
+ ExpressionStatement
+ expression: FunctionExpression
+ parameters: FormalParameterList
+ leftParenthesis: (
+ rightParenthesis: )
+ body: ExpressionFunctionBody
+ functionDefinition: =>
+ expression: PrefixedIdentifier
+ prefix: SimpleIdentifier
+ token: x
+ staticElement: x@30
+ element: x@30
+ staticType: int
+ period: .
+ identifier: SimpleIdentifier
+ token: isEven
+ staticElement: dart:core::<fragment>::@class::int::@getter::isEven
+ element: dart:core::<fragment>::@class::int::@getter::isEven#element
+ staticType: bool
+ staticElement: dart:core::<fragment>::@class::int::@getter::isEven
+ element: dart:core::<fragment>::@class::int::@getter::isEven#element
+ staticType: bool
+ declaredElement: @107
+ type: bool Function()
+ staticType: bool Function()
+ semicolon: ;
+ rightBracket: }
+''',
+ );
+ }
+}
diff --git a/pkg/front_end/lib/src/type_inference/type_inference_engine.dart b/pkg/front_end/lib/src/type_inference/type_inference_engine.dart
index 31dcd22..61cbc99 100644
--- a/pkg/front_end/lib/src/type_inference/type_inference_engine.dart
+++ b/pkg/front_end/lib/src/type_inference/type_inference_engine.dart
@@ -557,6 +557,11 @@
}
@override
+ bool isFinal(VariableDeclaration variable) {
+ return variable.isFinal;
+ }
+
+ @override
// Coverage-ignore(suite): Not run.
bool isInterfaceType(SharedTypeView<DartType> type) {
return type.unwrapTypeView() is InterfaceType;
diff --git a/pkg/front_end/testcases/inference_update_4/final_var_promotion_kept_test.dart b/pkg/front_end/testcases/inference_update_4/final_var_promotion_kept_test.dart
new file mode 100644
index 0000000..ff4fbf4
--- /dev/null
+++ b/pkg/front_end/testcases/inference_update_4/final_var_promotion_kept_test.dart
@@ -0,0 +1,59 @@
+// Copyright (c) 2024, 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.
+
+// Tests promotions with final variables are not cancelled when they are
+// certainly assigned as described by:
+// https://github.com/dart-lang/language/issues/1721
+
+isInt(bool b) {
+ final num x;
+ if (b) {
+ x = 1;
+ } else {
+ x = 0.1;
+ }
+ if (x is int) {
+ // Flow analysis retains the promotion to `int`.
+ () => x.isEven;
+ }
+}
+
+isInt_late(bool b) {
+ late final num x;
+ if (b) {
+ x = 1;
+ } else {
+ x = 0.1;
+ }
+ if (x is int) {
+ // Flow analysis retains the promotion to `int`.
+ () => x.isEven;
+ }
+}
+
+notEqNull(bool b) {
+ final int? x;
+ if (b) {
+ x = 1;
+ } else {
+ x = null;
+ }
+ if (x != null) {
+ // Flow analysis retains the promotion to `int`.
+ () => x.isEven;
+ }
+}
+
+notEqNull_late(bool b) {
+ late final int? x;
+ if (b) {
+ x = 1;
+ } else {
+ x = null;
+ }
+ if (x != null) {
+ // Flow analysis retains the promotion to `int`.
+ () => x.isEven;
+ }
+}
diff --git a/pkg/front_end/testcases/inference_update_4/final_var_promotion_kept_test.dart.strong.expect b/pkg/front_end/testcases/inference_update_4/final_var_promotion_kept_test.dart.strong.expect
new file mode 100644
index 0000000..03e097a
--- /dev/null
+++ b/pkg/front_end/testcases/inference_update_4/final_var_promotion_kept_test.dart.strong.expect
@@ -0,0 +1,52 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+static method isInt(core::bool b) → dynamic {
+ final core::num x;
+ if(b) {
+ x = 1;
+ }
+ else {
+ x = 0.1;
+ }
+ if(x is core::int) {
+ () → core::bool => x{core::int}.{core::int::isEven}{core::bool};
+ }
+}
+static method isInt_late(core::bool b) → dynamic {
+ late final core::num x;
+ if(b) {
+ x = 1;
+ }
+ else {
+ x = 0.1;
+ }
+ if(x is core::int) {
+ () → core::bool => x{core::int}.{core::int::isEven}{core::bool};
+ }
+}
+static method notEqNull(core::bool b) → dynamic {
+ final core::int? x;
+ if(b) {
+ x = 1;
+ }
+ else {
+ x = null;
+ }
+ if(!(x == null)) {
+ () → core::bool => x{core::int}.{core::int::isEven}{core::bool};
+ }
+}
+static method notEqNull_late(core::bool b) → dynamic {
+ late final core::int? x;
+ if(b) {
+ x = 1;
+ }
+ else {
+ x = null;
+ }
+ if(!(x == null)) {
+ () → core::bool => x{core::int}.{core::int::isEven}{core::bool};
+ }
+}
diff --git a/pkg/front_end/testcases/inference_update_4/final_var_promotion_kept_test.dart.strong.modular.expect b/pkg/front_end/testcases/inference_update_4/final_var_promotion_kept_test.dart.strong.modular.expect
new file mode 100644
index 0000000..03e097a
--- /dev/null
+++ b/pkg/front_end/testcases/inference_update_4/final_var_promotion_kept_test.dart.strong.modular.expect
@@ -0,0 +1,52 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+static method isInt(core::bool b) → dynamic {
+ final core::num x;
+ if(b) {
+ x = 1;
+ }
+ else {
+ x = 0.1;
+ }
+ if(x is core::int) {
+ () → core::bool => x{core::int}.{core::int::isEven}{core::bool};
+ }
+}
+static method isInt_late(core::bool b) → dynamic {
+ late final core::num x;
+ if(b) {
+ x = 1;
+ }
+ else {
+ x = 0.1;
+ }
+ if(x is core::int) {
+ () → core::bool => x{core::int}.{core::int::isEven}{core::bool};
+ }
+}
+static method notEqNull(core::bool b) → dynamic {
+ final core::int? x;
+ if(b) {
+ x = 1;
+ }
+ else {
+ x = null;
+ }
+ if(!(x == null)) {
+ () → core::bool => x{core::int}.{core::int::isEven}{core::bool};
+ }
+}
+static method notEqNull_late(core::bool b) → dynamic {
+ late final core::int? x;
+ if(b) {
+ x = 1;
+ }
+ else {
+ x = null;
+ }
+ if(!(x == null)) {
+ () → core::bool => x{core::int}.{core::int::isEven}{core::bool};
+ }
+}
diff --git a/pkg/front_end/testcases/inference_update_4/final_var_promotion_kept_test.dart.strong.outline.expect b/pkg/front_end/testcases/inference_update_4/final_var_promotion_kept_test.dart.strong.outline.expect
new file mode 100644
index 0000000..47e97fb
--- /dev/null
+++ b/pkg/front_end/testcases/inference_update_4/final_var_promotion_kept_test.dart.strong.outline.expect
@@ -0,0 +1,12 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+static method isInt(core::bool b) → dynamic
+ ;
+static method isInt_late(core::bool b) → dynamic
+ ;
+static method notEqNull(core::bool b) → dynamic
+ ;
+static method notEqNull_late(core::bool b) → dynamic
+ ;
diff --git a/pkg/front_end/testcases/inference_update_4/final_var_promotion_kept_test.dart.strong.transformed.expect b/pkg/front_end/testcases/inference_update_4/final_var_promotion_kept_test.dart.strong.transformed.expect
new file mode 100644
index 0000000..03e097a
--- /dev/null
+++ b/pkg/front_end/testcases/inference_update_4/final_var_promotion_kept_test.dart.strong.transformed.expect
@@ -0,0 +1,52 @@
+library;
+import self as self;
+import "dart:core" as core;
+
+static method isInt(core::bool b) → dynamic {
+ final core::num x;
+ if(b) {
+ x = 1;
+ }
+ else {
+ x = 0.1;
+ }
+ if(x is core::int) {
+ () → core::bool => x{core::int}.{core::int::isEven}{core::bool};
+ }
+}
+static method isInt_late(core::bool b) → dynamic {
+ late final core::num x;
+ if(b) {
+ x = 1;
+ }
+ else {
+ x = 0.1;
+ }
+ if(x is core::int) {
+ () → core::bool => x{core::int}.{core::int::isEven}{core::bool};
+ }
+}
+static method notEqNull(core::bool b) → dynamic {
+ final core::int? x;
+ if(b) {
+ x = 1;
+ }
+ else {
+ x = null;
+ }
+ if(!(x == null)) {
+ () → core::bool => x{core::int}.{core::int::isEven}{core::bool};
+ }
+}
+static method notEqNull_late(core::bool b) → dynamic {
+ late final core::int? x;
+ if(b) {
+ x = 1;
+ }
+ else {
+ x = null;
+ }
+ if(!(x == null)) {
+ () → core::bool => x{core::int}.{core::int::isEven}{core::bool};
+ }
+}
diff --git a/pkg/front_end/testcases/inference_update_4/final_var_promotion_kept_test.dart.textual_outline.expect b/pkg/front_end/testcases/inference_update_4/final_var_promotion_kept_test.dart.textual_outline.expect
new file mode 100644
index 0000000..b384725
--- /dev/null
+++ b/pkg/front_end/testcases/inference_update_4/final_var_promotion_kept_test.dart.textual_outline.expect
@@ -0,0 +1,7 @@
+isInt(bool b) {}
+
+isInt_late(bool b) {}
+
+notEqNull(bool b) {}
+
+notEqNull_late(bool b) {}
diff --git a/pkg/front_end/testcases/inference_update_4/final_var_promotion_kept_test.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/inference_update_4/final_var_promotion_kept_test.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..b384725
--- /dev/null
+++ b/pkg/front_end/testcases/inference_update_4/final_var_promotion_kept_test.dart.textual_outline_modelled.expect
@@ -0,0 +1,7 @@
+isInt(bool b) {}
+
+isInt_late(bool b) {}
+
+notEqNull(bool b) {}
+
+notEqNull_late(bool b) {}
diff --git a/pkg/front_end/testcases/inference_update_4/final_var_promotion_kept_test.dart.weak.expect b/pkg/front_end/testcases/inference_update_4/final_var_promotion_kept_test.dart.weak.expect
new file mode 100644
index 0000000..18914e0
--- /dev/null
+++ b/pkg/front_end/testcases/inference_update_4/final_var_promotion_kept_test.dart.weak.expect
@@ -0,0 +1,93 @@
+//
+// Problems in component:
+//
+// sdk/lib/core/core.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+// sdk/lib/async/async.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+// sdk/lib/collection/collection.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+// sdk/lib/_internal/vm_shared/lib/compact_hash.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+// sdk/lib/concurrent/concurrent.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+// sdk/lib/convert/convert.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+// sdk/lib/developer/developer.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+// sdk/lib/ffi/ffi.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+// sdk/lib/internal/internal.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+// sdk/lib/isolate/isolate.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+// sdk/lib/math/math.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+// sdk/lib/mirrors/mirrors.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+// sdk/lib/typed_data/typed_data.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+// sdk/lib/_internal/vm/bin/vmservice_io.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+// sdk/lib/vmservice/vmservice.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+// sdk/lib/_internal/vm/bin/builtin.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+// sdk/lib/html/dartium/nativewrappers.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+// sdk/lib/io/io.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+// sdk/lib/cli/cli.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+library;
+import self as self;
+import "dart:core" as core;
+
+static method isInt(core::bool b) → dynamic {
+ final core::num x;
+ if(b) {
+ x = 1;
+ }
+ else {
+ x = 0.1;
+ }
+ if(x is core::int) {
+ () → core::bool => x{core::int}.{core::int::isEven}{core::bool};
+ }
+}
+static method isInt_late(core::bool b) → dynamic {
+ late final core::num x;
+ if(b) {
+ x = 1;
+ }
+ else {
+ x = 0.1;
+ }
+ if(x is core::int) {
+ () → core::bool => x{core::int}.{core::int::isEven}{core::bool};
+ }
+}
+static method notEqNull(core::bool b) → dynamic {
+ final core::int? x;
+ if(b) {
+ x = 1;
+ }
+ else {
+ x = null;
+ }
+ if(!(x == null)) {
+ () → core::bool => x{core::int}.{core::int::isEven}{core::bool};
+ }
+}
+static method notEqNull_late(core::bool b) → dynamic {
+ late final core::int? x;
+ if(b) {
+ x = 1;
+ }
+ else {
+ x = null;
+ }
+ if(!(x == null)) {
+ () → core::bool => x{core::int}.{core::int::isEven}{core::bool};
+ }
+}
diff --git a/pkg/front_end/testcases/inference_update_4/final_var_promotion_kept_test.dart.weak.transformed.expect b/pkg/front_end/testcases/inference_update_4/final_var_promotion_kept_test.dart.weak.transformed.expect
new file mode 100644
index 0000000..18914e0
--- /dev/null
+++ b/pkg/front_end/testcases/inference_update_4/final_var_promotion_kept_test.dart.weak.transformed.expect
@@ -0,0 +1,93 @@
+//
+// Problems in component:
+//
+// sdk/lib/core/core.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+// sdk/lib/async/async.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+// sdk/lib/collection/collection.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+// sdk/lib/_internal/vm_shared/lib/compact_hash.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+// sdk/lib/concurrent/concurrent.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+// sdk/lib/convert/convert.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+// sdk/lib/developer/developer.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+// sdk/lib/ffi/ffi.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+// sdk/lib/internal/internal.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+// sdk/lib/isolate/isolate.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+// sdk/lib/math/math.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+// sdk/lib/mirrors/mirrors.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+// sdk/lib/typed_data/typed_data.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+// sdk/lib/_internal/vm/bin/vmservice_io.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+// sdk/lib/vmservice/vmservice.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+// sdk/lib/_internal/vm/bin/builtin.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+// sdk/lib/html/dartium/nativewrappers.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+// sdk/lib/io/io.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+// sdk/lib/cli/cli.dart: Error: Loaded library is compiled with sound null safety and cannot be used in compilation for unsound null safety.
+//
+library;
+import self as self;
+import "dart:core" as core;
+
+static method isInt(core::bool b) → dynamic {
+ final core::num x;
+ if(b) {
+ x = 1;
+ }
+ else {
+ x = 0.1;
+ }
+ if(x is core::int) {
+ () → core::bool => x{core::int}.{core::int::isEven}{core::bool};
+ }
+}
+static method isInt_late(core::bool b) → dynamic {
+ late final core::num x;
+ if(b) {
+ x = 1;
+ }
+ else {
+ x = 0.1;
+ }
+ if(x is core::int) {
+ () → core::bool => x{core::int}.{core::int::isEven}{core::bool};
+ }
+}
+static method notEqNull(core::bool b) → dynamic {
+ final core::int? x;
+ if(b) {
+ x = 1;
+ }
+ else {
+ x = null;
+ }
+ if(!(x == null)) {
+ () → core::bool => x{core::int}.{core::int::isEven}{core::bool};
+ }
+}
+static method notEqNull_late(core::bool b) → dynamic {
+ late final core::int? x;
+ if(b) {
+ x = 1;
+ }
+ else {
+ x = null;
+ }
+ if(!(x == null)) {
+ () → core::bool => x{core::int}.{core::int::isEven}{core::bool};
+ }
+}
diff --git a/pkg/front_end/testcases/inference_update_4/folder.options b/pkg/front_end/testcases/inference_update_4/folder.options
new file mode 100644
index 0000000..0c0d959
--- /dev/null
+++ b/pkg/front_end/testcases/inference_update_4/folder.options
@@ -0,0 +1 @@
+--enable-experiment=inference-update-4