Skip to content

Commit 742850f

Browse files
alexmarkovCommit Queue
authored andcommitted
[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 #59845 Change-Id: I0f78e31f9eaf6507cc78e393de2fc12a9cf29f4a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/403200 Reviewed-by: Slava Egorov <[email protected]> Commit-Queue: Alexander Markov <[email protected]>
1 parent 735a739 commit 742850f

File tree

3 files changed

+51
-2
lines changed

3 files changed

+51
-2
lines changed

pkg/vm/lib/transformations/type_flow/summary_collector.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1604,11 +1604,12 @@ class SummaryCollector extends RecursiveResultVisitor<TypeExpr?> {
16041604
Args<TypeExpr>([expr, _nullType]));
16051605
final narrowedNotNull = _makeNarrowNotNull(node, expr);
16061606
final int varIndex = _variablesInfo.varIndex[lhs.variable]!;
1607+
final result = _makeUnaryOperation(UnaryOp.IsNull, expr);
16071608
if (!_aggregateVariable[varIndex]) {
1608-
trueState[varIndex] = _nullType;
1609+
trueState[varIndex] = _makeUnaryOperation(UnaryOp.Move, _nullType)
1610+
..condition = result;
16091611
falseState[varIndex] = narrowedNotNull;
16101612
}
1611-
final result = _makeUnaryOperation(UnaryOp.IsNull, expr);
16121613
_variableValues = const <TypeExpr?>[]; // Should not be used.
16131614
return result;
16141615
} else if (node is IsExpression && node.operand is VariableGet) {
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// Regression test for https://github.com/dart-lang/sdk/issues/59845.
6+
// Verifies that TFA can propagate non-nullability of a variable down after
7+
// comparison with null which implies that variable is null.
8+
9+
void main() {
10+
repro(something: 1.0, other: 1.0);
11+
}
12+
13+
void repro({double? something, double? other}) {
14+
print(
15+
// Joined data flow after '||' should not be contaminated with
16+
// inferred null values after 'something == null && other == null'.
17+
(something == null && other == null) ||
18+
(something != null && other != null),
19+
);
20+
21+
if (something != null) {
22+
// 'if' should be eliminated.
23+
print(something);
24+
print(other!); // '!' should be eliminated.
25+
}
26+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
library #lib;
2+
import self as self;
3+
import "dart:core" as core;
4+
import "dart:_internal" as _in;
5+
6+
7+
[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)]
8+
static method main() → void {
9+
self::repro();
10+
}
11+
12+
[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)]
13+
static method repro() → void {
14+
core::print(true);
15+
{
16+
core::print(#C1);
17+
core::print(_in::unsafeCast<core::double>(#C1));
18+
}
19+
}
20+
constants {
21+
#C1 = 1.0
22+
}

0 commit comments

Comments
 (0)