[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