[vm/aot,dart2wasm,tfa] Infer null after comparison with null only if value is potentially nullable
Previously, TFA was building a summary for the comparison
'v == null' assuming type of 'v' is Null on the true branch.
As a result, after joining data flow Null contaminates value of 'v'
further down:
v0 = ...
t1 = IsNull(v0) // Condition
t2 = Null // On true branch
t3 = NarrowNotNull(v0) // On false branch
v1 = Join(t2, t3) // This value is nullable even if v0 was not.
...
use(v1)
This change adds a condition to the Null value so it is evaluated to
Empty type if original value turns out to be non-nullable:
t1 = IsNull(v0)
t2 = Move(Null) {t1} // Empty if t1 is false.
t3 = NarrowNotNull(v0)
v1 = Join(t2, t3)
TEST=pkg/vm/testcases/transformations/type_flow/transformer/regress_59845.dart
Fixes https://github.com/dart-lang/sdk/issues/59845
Change-Id: I0f78e31f9eaf6507cc78e393de2fc12a9cf29f4a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/403200
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
diff --git a/pkg/vm/lib/transformations/type_flow/summary_collector.dart b/pkg/vm/lib/transformations/type_flow/summary_collector.dart
index 0b20ed4..19bdfcc 100644
--- a/pkg/vm/lib/transformations/type_flow/summary_collector.dart
+++ b/pkg/vm/lib/transformations/type_flow/summary_collector.dart
@@ -1604,11 +1604,12 @@
Args<TypeExpr>([expr, _nullType]));
final narrowedNotNull = _makeNarrowNotNull(node, expr);
final int varIndex = _variablesInfo.varIndex[lhs.variable]!;
+ final result = _makeUnaryOperation(UnaryOp.IsNull, expr);
if (!_aggregateVariable[varIndex]) {
- trueState[varIndex] = _nullType;
+ trueState[varIndex] = _makeUnaryOperation(UnaryOp.Move, _nullType)
+ ..condition = result;
falseState[varIndex] = narrowedNotNull;
}
- final result = _makeUnaryOperation(UnaryOp.IsNull, expr);
_variableValues = const <TypeExpr?>[]; // Should not be used.
return result;
} else if (node is IsExpression && node.operand is VariableGet) {
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/regress_59845.dart b/pkg/vm/testcases/transformations/type_flow/transformer/regress_59845.dart
new file mode 100644
index 0000000..cd9b95d
--- /dev/null
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/regress_59845.dart
@@ -0,0 +1,26 @@
+// Copyright (c) 2025, 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.
+
+// Regression test for https://github.com/dart-lang/sdk/issues/59845.
+// Verifies that TFA can propagate non-nullability of a variable down after
+// comparison with null which implies that variable is null.
+
+void main() {
+ repro(something: 1.0, other: 1.0);
+}
+
+void repro({double? something, double? other}) {
+ print(
+ // Joined data flow after '||' should not be contaminated with
+ // inferred null values after 'something == null && other == null'.
+ (something == null && other == null) ||
+ (something != null && other != null),
+ );
+
+ if (something != null) {
+ // 'if' should be eliminated.
+ print(something);
+ print(other!); // '!' should be eliminated.
+ }
+}
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/regress_59845.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/regress_59845.dart.expect
new file mode 100644
index 0000000..2b38e96
--- /dev/null
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/regress_59845.dart.expect
@@ -0,0 +1,22 @@
+library #lib;
+import self as self;
+import "dart:core" as core;
+import "dart:_internal" as _in;
+
+
+[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)]
+static method main() → void {
+ self::repro();
+}
+
+[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)]
+static method repro() → void {
+ core::print(true);
+ {
+ core::print(#C1);
+ core::print(_in::unsafeCast<core::double>(#C1));
+ }
+}
+constants {
+ #C1 = 1.0
+}