[tfa] Fix handling of late local variables with static type Never

Initializers of late local variables are wrapped into closures.
The result type of a closure is a static type of the variable.
So initializer of a late variable with static type Never have a call
to a closure with static result type Never.

TFA summary collector handles calls with static result type Never
specially, treating all subsequent code as unreachable (by setting
control-dependent condition to empty). As a result, all subsequent
code after late variable with static type Never (and initializer) is
tree-shaken.

The fix is to avoid propagating control-dependent condition out of
the late variable initializers.

TEST=pkg/vm/testcases/transformations/type_flow/transformer/regress_63478.dart
Fixes https://github.com/dart-lang/sdk/issues/63478

Change-Id: If41221777c9d249dd21ce5827c586b0f531cbce8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/508363
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
Alexander Markov
2026-06-02 05:42:19 -07:00
committed by dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent 4b0a4e0a75
commit 7ff5e3654d
3 changed files with 60 additions and 0 deletions
@@ -2817,6 +2817,7 @@ class SummaryCollector extends RecursiveResultVisitor<TypeExpr?> {
final variable = node.variable;
variable.annotations.forEach(_visitAnnotation);
final initializer = variable.initializer;
final savedCondition = _currentCondition;
final TypeExpr initialValue = initializer == null
? ((variable.type.nullability == Nullability.nonNullable ||
variable.isLate)
@@ -2824,6 +2825,11 @@ class SummaryCollector extends RecursiveResultVisitor<TypeExpr?> {
: _nullType)
: _visit(initializer);
_declareVariable(variable, initialValue);
if (variable.isLate) {
// Restore condition as initializer of a late variable
// is not evaluated immediately.
_currentCondition = savedCondition;
}
return null;
}
@@ -0,0 +1,24 @@
// Copyright (c) 2026, 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/63478.
// Verifies that initializer of a late variable doesn't affect
// subsequent statements.
void test1() {
late var x = throw "error";
print("reachable");
}
Never sayNever() => throw 'Never';
void test2() {
late Never y = sayNever();
print("reachable");
}
void main() {
test1();
test2();
}
@@ -0,0 +1,30 @@
library #lib;
import self as self;
import "dart:core" as core;
[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)]
static method test1() → void {
function #x#initializer() → Never
return throw "error";
late Never x = [@vm.inferred-type.metadata=? (receiver not int)] #x#initializer(){() → Never};
core::print("reachable");
}
[@vm.inferred-return-type.metadata=!]
static method sayNever() → Never
return throw "Never";
[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)]
static method test2() → void {
function #y#initializer() → Never
return self::sayNever();
late Never y = [@vm.inferred-type.metadata=? (receiver not int)] #y#initializer(){() → Never};
core::print("reachable");
}
[@vm.inferred-return-type.metadata=dart.core::Null? (value: null)]
static method main() → void {
self::test1();
self::test2();
}