Enable sound-flow-analysis for Dart 3.9.

Bug: https://github.com/dart-lang/sdk/issues/60438
Change-Id: I908d4e4a9143142281d8198870f40eba6cf6f67f
Tested: trybots
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/427500
Reviewed-by: Ivan Inozemtsev <iinozemtsev@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Morgan :) <davidmorgan@google.com>
Reviewed-by: Daco Harkes <dacoharkes@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
This commit is contained in:
Paul Berry
2025-05-09 14:50:26 -07:00
committed by Commit Queue
parent a42401ab3e
commit a757d2af41
31 changed files with 259 additions and 147 deletions
+10
View File
@@ -1,5 +1,15 @@
## 3.9.0
### Language
Dart 3.9 assumes null safety when computing type promotion, reachability, and
definite assignment. This makes these features produce more accurate results for
modern Dart programs. As a result of this change, more dead_code warnings may be
produced. To take advantage of these improvements, set your package's [SDK
constraint][language version] lower bound to 3.9 or greater (`sdk: '^3.9.0'`).
[language version]: https://dart.dev/guides/language/evolution
### Tools
#### Dart Development Compiler (dartdevc)
@@ -222,10 +222,10 @@ enum ExperimentalFlag {
soundFlowAnalysis(
name: 'sound-flow-analysis',
isEnabledByDefault: false,
isEnabledByDefault: true,
isExpired: false,
experimentEnabledVersion: defaultLanguageVersion,
experimentReleasedVersion: defaultLanguageVersion),
experimentEnabledVersion: const Version(3, 9),
experimentReleasedVersion: const Version(3, 9)),
spreadCollections(
name: 'spread-collections',
@@ -35,9 +35,9 @@ void nullableValue(int? x) {
}
void nonNullableValue(int x) {
if (x == null) {
if (x == null) /*unreachable*/{
// Reachable since the value of x might come from legacy code
1;
/*stmt: unreachable*/1;
} else {
2;
}
@@ -68,9 +68,9 @@ void potentiallyNullableTypeVar_nullableBound<T extends Object?>(T x) {
}
void nonNullableTypeVar<T extends Object>(T x) {
if (x == null) {
if (x == null) /*unreachable*/{
// Reachable since the value of x might come from legacy code
1;
/*stmt: unreachable*/1;
} else {
2;
}
@@ -12,18 +12,18 @@ void variable_if_null_assign_reachable(int? i) {
void variable_if_null_unreachable(int i) {
// Reachable since the value of i might come from legacy code
i ?? 0;
i ?? /*unreachable*/0;
}
void variable_if_null_assign_unreachable(int i) {
// Reachable since the value of i might come from legacy code
i ??= 0;
/*cfe.update: unreachable*/i ??= /*unreachable*/0;
}
void variable_if_null_assign_unreachable_due_to_promotion(int? i) {
if (i == null) return;
// Reachable since the value of i might come from legacy code
i ??= 0;
/*cfe.update: unreachable*/i ??= /*unreachable*/0;
}
/*member: topLevelNullable:doesNotComplete*/
@@ -45,13 +45,13 @@ void top_level_if_null_assign_reachable() {
void top_level_if_null_unreachable() {
// Reachable since the value returned by topLevelNonNullGet might come from
// legacy code
topLevelNonNullGet ?? 0;
topLevelNonNullGet ?? /*unreachable*/0;
}
void top_level_if_null_assign_unreachable() {
// Reachable since the value returned by topLevelNonNullGet might come from
// legacy code
topLevelNonNullGet ??= 0;
topLevelNonNullGet /*cfe.update: unreachable*/??= /*unreachable*/0;
}
class HasProperty<T> {
@@ -70,12 +70,12 @@ void property_if_null_assign_reachable(HasProperty<int?> x) {
void property_if_null_unreachable(HasProperty<int> x) {
// Reachable since the value returned by prop might come from legacy code
x.prop ?? 0;
x.prop ?? /*unreachable*/0;
}
void property_if_null_assign_unreachable(HasProperty<int> x) {
// Reachable since the value returned by prop might come from legacy code
x.prop ??= 0;
x.prop ??= /*unreachable*/0;
}
void null_aware_property_if_null_reachable(HasProperty<int?>? x) {
@@ -94,7 +94,7 @@ void null_aware_property_if_null_not_shortened(HasProperty<int>? x) {
void null_aware_property_if_null_assign_unreachable(HasProperty<int>? x) {
// Reachable since the value returned by prop might come from legacy code.
x?.prop ??= 0;
x?.prop ??= /*unreachable*/0;
}
class SuperIntQuestionProperty extends HasProperty<int?> {
@@ -110,12 +110,12 @@ class SuperIntQuestionProperty extends HasProperty<int?> {
class SuperIntProperty extends HasProperty<int> {
void if_null_unreachable() {
// Reachable since the value returned by prop might come from legacy code.
super.prop ?? 0;
super.prop ?? /*unreachable*/0;
}
void if_null_assign_unreachable() {
// Reachable since the value returned by prop might come from legacy code.
super.prop ??= 0;
super.prop ??= /*unreachable*/0;
}
}
@@ -138,13 +138,13 @@ void extended_property_if_null_assign_reachable(HasProperty<int?> x) {
void extended_property_if_null_unreachable(HasProperty<int> x) {
// Reachable since the value returned by extendedProp might come from legacy
// code.
x.extendedProp ?? 0;
x.extendedProp ?? /*unreachable*/0;
}
void extended_property_if_null_assign_unreachable(HasProperty<int> x) {
// Reachable since the value returned by extendedProp might come from legacy
// code.
x.extendedProp ??= 0;
x.extendedProp ??= /*unreachable*/0;
}
void null_aware_extended_property_if_null_reachable(HasProperty<int?>? x) {
@@ -166,7 +166,7 @@ void null_aware_extended_property_if_null_assign_unreachable(
HasProperty<int>? x) {
// Reachable since the value returned by extendedProp might come from legacy
// code.
x?.extendedProp ??= 0;
x?.extendedProp ??= /*unreachable*/0;
}
void explicit_extended_property_if_null_reachable(HasProperty<int?> x) {
@@ -180,13 +180,13 @@ void explicit_extended_property_if_null_assign_reachable(HasProperty<int?> x) {
void explicit_extended_property_if_null_unreachable(HasProperty<int> x) {
// Reachable since the value returned by extendedProp might come from legacy
// code.
ExtensionProperty(x).extendedProp ?? 0;
ExtensionProperty(x).extendedProp ?? /*unreachable*/0;
}
void explicit_extended_property_if_null_assign_unreachable(HasProperty<int> x) {
// Reachable since the value returned by extendedProp might come from legacy
// code.
ExtensionProperty(x).extendedProp ??= 0;
ExtensionProperty(x).extendedProp ??= /*unreachable*/0;
}
void null_aware_explicit_extended_property_if_null_reachable(
@@ -210,7 +210,7 @@ void null_aware_explicit_extended_property_if_null_assign_unreachable(
HasProperty<int>? x) {
// Reachable since the value returned by extendedProp might come from legacy
// code.
ExtensionProperty(x)?.extendedProp ??= 0;
ExtensionProperty(x)?.extendedProp ??= /*unreachable*/0;
}
class Indexable<T> {
@@ -226,7 +226,7 @@ void index_if_null_reachable(Indexable<int?> x) {
void index_if_null_unreachable(Indexable<int> x) {
// Reachable since the value returned by operator[] might come from legacy
// code.
x[0] ?? 0;
x[0] ?? /*unreachable*/0;
}
void index_if_null_assign_reachable(Indexable<int?> x) {
@@ -236,7 +236,7 @@ void index_if_null_assign_reachable(Indexable<int?> x) {
void index_if_null_assign_unreachable(Indexable<int> x) {
// Reachable since the value returned by operator[] might come from legacy
// code.
x[0] ??= 0;
x[0] ??= /*unreachable*/0;
}
void null_aware_index_if_null_reachable(Indexable<int?>? x) {
@@ -256,7 +256,7 @@ void null_aware_index_if_null_assign_reachable(Indexable<int?>? x) {
void null_aware_index_if_null_assign_unreachable(Indexable<int>? x) {
// Reachable since the value returned by operator[] might come from legacy
// code.
x?[0] ??= 0;
x?[0] ??= /*unreachable*/0;
}
class SuperIntQuestionIndex extends Indexable<int?> {
@@ -273,13 +273,13 @@ class SuperIntIndex extends Indexable<int> {
void if_null_unreachable() {
// Reachable since the value returned by operator[] might come from legacy
// code.
super[0] ?? 0;
super[0] ?? /*unreachable*/0;
}
void if_null_assign_unreachable() {
// Reachable since the value returned by operator[] might come from legacy
// code.
super[0] ??= 0;
super[0] ??= /*unreachable*/0;
}
}
@@ -302,13 +302,13 @@ void extended_index_if_null_assign_reachable(HasProperty<int?> x) {
void extended_index_if_null_unreachable(HasProperty<int> x) {
// Reachable since the value returned by operator[] might come from legacy
// code.
x[0] ?? 0;
x[0] ?? /*unreachable*/0;
}
void extended_index_if_null_assign_unreachable(HasProperty<int> x) {
// Reachable since the value returned by operator[] might come from legacy
// code.
x[0] ??= 0;
x[0] ??= /*unreachable*/0;
}
void null_aware_extended_index_if_null_reachable(HasProperty<int?>? x) {
@@ -328,7 +328,7 @@ void null_aware_extended_index_if_null_not_shortened(HasProperty<int>? x) {
void null_aware_extended_index_if_null_assign_unreachable(HasProperty<int>? x) {
// Reachable since the value returned by operator[] might come from legacy
// code.
x?[0] ??= 0;
x?[0] ??= /*unreachable*/0;
}
void explicit_extended_index_if_null_reachable(HasProperty<int?> x) {
@@ -342,13 +342,13 @@ void explicit_extended_index_if_null_assign_reachable(HasProperty<int?> x) {
void explicit_extended_index_if_null_unreachable(HasProperty<int> x) {
// Reachable since the value returned by operator[] might come from legacy
// code.
ExtensionIndex(x)[0] ?? 0;
ExtensionIndex(x)[0] ?? /*unreachable*/0;
}
void explicit_extended_index_if_null_assign_unreachable(HasProperty<int> x) {
// Reachable since the value returned by operator[] might come from legacy
// code.
ExtensionIndex(x)[0] ??= 0;
ExtensionIndex(x)[0] ??= /*unreachable*/0;
}
void null_aware_explicit_extended_index_if_null_reachable(
@@ -372,5 +372,5 @@ void null_aware_explicit_extended_index_if_null_assign_unreachable(
HasProperty<int>? x) {
// Reachable since the value returned by operator[] might come from legacy
// code.
ExtensionIndex(x)?[0] ??= 0;
ExtensionIndex(x)?[0] ??= /*unreachable*/0;
}
@@ -7,10 +7,11 @@ void index_reachable(List<int>? f()) {
0;
}
/*member: index_unreachable:doesNotComplete*/
void index_unreachable(List<int> f()) {
// Reachable since the value returned by f() might come from legacy code
f()?[throw ''];
0;
/*stmt: unreachable*/0;
}
void cascaded_index_reachable(List<int>? f()) {
@@ -18,10 +19,11 @@ void cascaded_index_reachable(List<int>? f()) {
0;
}
/*member: cascaded_index_unreachable:doesNotComplete*/
void cascaded_index_unreachable(List<int> f()) {
// Reachable since the value returned by f() might come from legacy code
f()?..[throw ''];
0;
/*stmt: unreachable*/0;
}
void method_invocation_reachable(int? f()) {
@@ -29,10 +31,11 @@ void method_invocation_reachable(int? f()) {
0;
}
/*member: method_invocation_unreachable:doesNotComplete*/
void method_invocation_unreachable(int f()) {
// Reachable since the value returned by f() might come from legacy code
f()?.remainder(throw '');
0;
/*stmt: unreachable*/0;
}
void cascaded_method_invocation_reachable(int? f()) {
@@ -40,10 +43,11 @@ void cascaded_method_invocation_reachable(int? f()) {
0;
}
/*member: cascaded_method_invocation_unreachable:doesNotComplete*/
void cascaded_method_invocation_unreachable(int f()) {
// Reachable since the value returned by f() might come from legacy code
f()?..remainder(throw '');
0;
/*stmt: unreachable*/0;
}
void property_get_reachable(int? f()) {
@@ -51,10 +55,11 @@ void property_get_reachable(int? f()) {
0;
}
/*member: property_get_unreachable:doesNotComplete*/
void property_get_unreachable(int f()) {
// Reachable since the value returned by f() might come from legacy code
f()?.hashCode.remainder(throw '');
0;
/*stmt: unreachable*/0;
}
void cascaded_property_get_reachable(int? f()) {
@@ -62,10 +67,11 @@ void cascaded_property_get_reachable(int? f()) {
0;
}
/*member: cascaded_property_get_unreachable:doesNotComplete*/
void cascaded_property_get_unreachable(int f()) {
// Reachable since the value returned by f() might come from legacy code
f()?..hashCode.remainder(throw '');
0;
/*stmt: unreachable*/0;
}
void property_get_invocation_reachable(List<void Function(dynamic)>? f()) {
@@ -76,13 +82,14 @@ void property_get_invocation_reachable(List<void Function(dynamic)>? f()) {
0;
}
/*member: property_get_invocation_unreachable:doesNotComplete*/
void property_get_invocation_unreachable(List<void Function(dynamic)> f()) {
// Reachable since the value returned by f() might come from legacy code
// We need a special test case for this because it parses like a method
// invocation but the analyzer rewrites it as a property access followed by a
// function expression invocation.
f()?.first(throw '');
0;
/*stmt: unreachable*/0;
}
void cascaded_property_get_invocation_reachable(
@@ -94,6 +101,7 @@ void cascaded_property_get_invocation_reachable(
0;
}
/*member: cascaded_property_get_invocation_unreachable:doesNotComplete*/
void cascaded_property_get_invocation_unreachable(
List<void Function(dynamic)> f()) {
// Reachable since the value returned by f() might come from legacy code
@@ -101,7 +109,7 @@ void cascaded_property_get_invocation_unreachable(
// invocation but the analyzer rewrites it as a property access followed by a
// function expression invocation.
f()?..first(throw '');
0;
/*stmt: unreachable*/0;
}
class C {
@@ -113,10 +121,11 @@ void property_set_reachable(C? f()) {
0;
}
/*member: property_set_unreachable:doesNotComplete*/
void property_set_unreachable(C f()) {
// Reachable since the value returned by f() might come from legacy code
f()?.field = throw '';
0;
/*stmt: unreachable*/0;
}
void cascaded_property_set_reachable(C? f()) {
@@ -124,8 +133,9 @@ void cascaded_property_set_reachable(C? f()) {
0;
}
/*member: cascaded_property_set_unreachable:doesNotComplete*/
void cascaded_property_set_unreachable(C f()) {
// Reachable since the value returned by f() might come from legacy code
f()?..field = throw '';
0;
/*stmt: unreachable*/0;
}
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:analysis_server/src/services/correction/fix.dart';
import 'package:analyzer/diagnostic/diagnostic.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
import 'package:linter/src/lint_names.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -19,6 +21,9 @@ void main() {
});
}
bool _ignoreDeadCode(Diagnostic diagnostic) =>
diagnostic.errorCode != WarningCode.DEAD_CODE;
@reflectiveTest
class RemoveComparisonTest extends FixProcessorTest {
@override
@@ -168,7 +173,7 @@ void f(int x) {
2,
];
}
''');
''', errorFilter: _ignoreDeadCode);
}
Future<void> test_ifElement_alwaysFalse_hasElse_withComments() async {
@@ -197,7 +202,7 @@ void f(int x) {
2,
];
}
''');
''', errorFilter: _ignoreDeadCode);
}
Future<void> test_ifElement_alwaysFalse_noElse_insideList() async {
@@ -217,7 +222,7 @@ void f(int x) {
2,
];
}
''');
''', errorFilter: _ignoreDeadCode);
}
Future<void>
@@ -241,7 +246,7 @@ void f(int x) {
2,
];
}
''');
''', errorFilter: _ignoreDeadCode);
}
Future<void> test_ifElement_alwaysFalse_noElse_insideSet() async {
@@ -261,7 +266,7 @@ Object f(int x) {
2,
};
}
''');
''', errorFilter: _ignoreDeadCode);
}
Future<void> test_ifElement_alwaysTrue() async {
@@ -304,7 +309,7 @@ void f(int x) {
2,
];
}
''');
''', errorFilter: _ignoreDeadCode);
}
Future<void> test_ifElement_alwaysTrue_withComments() async {
@@ -351,7 +356,7 @@ void f(int x) {
2;
3;
}
''');
''', errorFilter: _ignoreDeadCode);
}
Future<void> test_ifStatement_alwaysFalse_hasElse_block_empty() async {
@@ -369,7 +374,7 @@ void f(int x) {
0;
2;
}
''');
''', errorFilter: _ignoreDeadCode);
}
Future<void> test_ifStatement_alwaysFalse_hasElse_statement() async {
@@ -389,7 +394,7 @@ void f(int x) {
2;
3;
}
''');
''', errorFilter: _ignoreDeadCode);
}
Future<void> test_ifStatement_alwaysFalse_noElse() async {
@@ -407,7 +412,7 @@ void f(int x) {
0;
2;
}
''');
''', errorFilter: _ignoreDeadCode);
}
Future<void> test_ifStatement_alwaysTrue_hasElse_block() async {
@@ -428,7 +433,7 @@ void f(int x) {
1;
3;
}
''');
''', errorFilter: _ignoreDeadCode);
}
Future<void> test_ifStatement_alwaysTrue_noElse() async {
@@ -745,7 +750,7 @@ class Person {
}
}
''');
await assertNoFix();
await assertNoFix(errorFilter: _ignoreDeadCode);
}
}
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:analysis_server/src/services/correction/fix.dart';
import 'package:analyzer/diagnostic/diagnostic.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
import 'package:linter/src/lint_names.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -18,6 +20,9 @@ void main() {
});
}
bool _ignoreDeadCode(Diagnostic diagnostic) =>
diagnostic.errorCode != WarningCode.DEAD_CODE;
@reflectiveTest
class DeadNullAwareAssignmentExpressionTest extends FixProcessorTest {
@override
@@ -46,7 +51,7 @@ void f() {
}
C g() => C();
''');
''', errorFilter: _ignoreDeadCode);
}
Future<void> test_assignmentExpression_simpleIdentifier_field() async {
@@ -64,7 +69,7 @@ class C {
void f(int b) {
}
}
''');
''', errorFilter: _ignoreDeadCode);
}
Future<void> test_assignmentExpression_simpleIdentifier_parameter() async {
@@ -76,7 +81,7 @@ void f(int a, int b) {
await assertHasFix('''
void f(int a, int b) {
}
''');
''', errorFilter: _ignoreDeadCode);
}
Future<void> test_immediateChild() async {
@@ -85,7 +90,7 @@ void f(int a, int b) => a ??= b;
''');
await assertHasFix('''
void f(int a, int b) => a;
''');
''', errorFilter: _ignoreDeadCode);
}
Future<void> test_nestedChild() async {
@@ -94,7 +99,7 @@ void f(int a, int b) => a ??= b * 2 + 1;
''');
await assertHasFix('''
void f(int a, int b) => a;
''');
''', errorFilter: _ignoreDeadCode);
}
Future<void> test_nestedChild_onRight() async {
@@ -103,7 +108,7 @@ void f(int a, int b, int c) => a = b ??= c;
''');
await assertHasFix('''
void f(int a, int b, int c) => a = b;
''');
''', errorFilter: _ignoreDeadCode);
}
}
@@ -118,7 +123,7 @@ int f(int a, int b) => a ?? b;
''');
await assertHasFix('''
int f(int a, int b) => a;
''');
''', errorFilter: _ignoreDeadCode);
}
Future<void> test_nestedChild() async {
@@ -127,7 +132,7 @@ int f(int a, int b) => a ?? b * 2 + 1;
''');
await assertHasFix('''
int f(int a, int b) => a;
''');
''', errorFilter: _ignoreDeadCode);
}
}
@@ -194,6 +199,6 @@ var b = a ?? '';
await assertHasFix('''
var a = '';
var b = a;
''');
''', errorFilter: _ignoreDeadCode);
}
}
@@ -528,7 +528,7 @@ class ExperimentalFeatures {
documentation:
'Assume sound null safety when computing type promotion, reachability, and definite assignment.',
experimentalReleaseVersion: null,
releaseVersion: null,
releaseVersion: Version.parse('3.9.0'),
channels: ["stable", "beta", "dev", "main"],
);
@@ -717,7 +717,7 @@ class IsEnabledByDefault {
static const bool set_literals = true;
/// Default state of the experiment "sound-flow-analysis"
static const bool sound_flow_analysis = false;
static const bool sound_flow_analysis = true;
/// Default state of the experiment "spread-collections"
static const bool spread_collections = true;
@@ -419,6 +419,7 @@ analysisOptions
records
sealed-class
set-literals
sound-flow-analysis
spread-collections
super-parameters
triple-shift
@@ -497,6 +498,7 @@ analysisOptions
records
sealed-class
set-literals
sound-flow-analysis
spread-collections
super-parameters
triple-shift
@@ -1345,6 +1345,7 @@ const c = x ?? 1;
23,
1,
),
error(WarningCode.DEAD_CODE, 25, 4),
error(StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, 28, 1),
],
);
@@ -1357,6 +1358,7 @@ final x = 1;
const c = 0 ?? x;
''',
[
error(WarningCode.DEAD_CODE, 25, 4),
error(
CompileTimeErrorCode.CONST_INITIALIZED_WITH_NON_CONSTANT_VALUE,
28,
@@ -185,7 +185,10 @@ abstract class C extends A {
void Function() f(A a, bool b, C c, dynamic d) => b ? d : c ?? a;
''',
[error(StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, 130, 1)],
[
error(WarningCode.DEAD_CODE, 127, 4),
error(StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, 130, 1),
],
);
// `c` is on the LHS of an if-null expression, so implicit call tearoff
// logic should not apply to it.
@@ -398,7 +398,10 @@ void f(void Function() x) {
if (x case (null)) {}
}
''',
[error(WarningCode.CONSTANT_PATTERN_NEVER_MATCHES_VALUE_TYPE, 42, 4)],
[
error(WarningCode.CONSTANT_PATTERN_NEVER_MATCHES_VALUE_TYPE, 42, 4),
error(WarningCode.DEAD_CODE, 49, 2),
],
);
}
@@ -417,7 +420,10 @@ void f(int x) {
if (x case (null)) {}
}
''',
[error(WarningCode.CONSTANT_PATTERN_NEVER_MATCHES_VALUE_TYPE, 30, 4)],
[
error(WarningCode.CONSTANT_PATTERN_NEVER_MATCHES_VALUE_TYPE, 30, 4),
error(WarningCode.DEAD_CODE, 37, 2),
],
);
}
@@ -436,7 +442,10 @@ void f<T extends Object>(T x) {
if (x case null) {}
}
''',
[error(WarningCode.CONSTANT_PATTERN_NEVER_MATCHES_VALUE_TYPE, 45, 4)],
[
error(WarningCode.CONSTANT_PATTERN_NEVER_MATCHES_VALUE_TYPE, 45, 4),
error(WarningCode.DEAD_CODE, 51, 2),
],
);
}
@@ -35,7 +35,10 @@ f(int x) {
x ??= 0;
}
''',
[error(StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, 19, 1)],
[
error(WarningCode.DEAD_CODE, 19, 2),
error(StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, 19, 1),
],
);
}
@@ -54,7 +57,10 @@ f(int x) {
x ?? 0;
}
''',
[error(StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, 18, 1)],
[
error(WarningCode.DEAD_CODE, 15, 4),
error(StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, 18, 1),
],
);
}
@@ -89,6 +89,7 @@ void f() {
''',
[
_notAssignedError(22, 1),
error(WarningCode.DEAD_CODE, 28, 2),
error(StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, 28, 1),
],
);
@@ -104,6 +105,7 @@ void f() {
''',
[
_notAssignedError(22, 1),
error(WarningCode.DEAD_CODE, 28, 2),
error(StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, 28, 1),
_notAssignedError(28, 1),
],
@@ -129,7 +131,10 @@ void f() {
v;
}
''',
[error(StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, 33, 1)],
[
error(WarningCode.DEAD_CODE, 30, 4),
error(StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, 33, 1),
],
);
}
@@ -143,6 +148,7 @@ void f(int a) {
}
''',
[
error(WarningCode.DEAD_CODE, 29, 10),
error(StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, 32, 7),
_notAssignedError(43, 1),
],
@@ -958,7 +958,10 @@ void f(Null x) {
class A {}
''',
[error(WarningCode.PATTERN_NEVER_MATCHES_VALUE_TYPE, 30, 1)],
[
error(WarningCode.PATTERN_NEVER_MATCHES_VALUE_TYPE, 30, 1),
error(WarningCode.DEAD_CODE, 35, 2),
],
);
}
@@ -977,7 +980,10 @@ void f(Null x) {
if (x case Object _) {}
}
''',
[error(WarningCode.PATTERN_NEVER_MATCHES_VALUE_TYPE, 30, 6)],
[
error(WarningCode.PATTERN_NEVER_MATCHES_VALUE_TYPE, 30, 6),
error(WarningCode.DEAD_CODE, 40, 2),
],
);
}
@@ -1277,7 +1283,10 @@ void f(A x) {
class A {}
''',
[error(WarningCode.PATTERN_NEVER_MATCHES_VALUE_TYPE, 27, 4)],
[
error(WarningCode.PATTERN_NEVER_MATCHES_VALUE_TYPE, 27, 4),
error(WarningCode.DEAD_CODE, 35, 2),
],
);
}
@@ -1288,7 +1297,10 @@ void f(void Function() x) {
if (x case Null _) {}
}
''',
[error(WarningCode.PATTERN_NEVER_MATCHES_VALUE_TYPE, 41, 4)],
[
error(WarningCode.PATTERN_NEVER_MATCHES_VALUE_TYPE, 41, 4),
error(WarningCode.DEAD_CODE, 49, 2),
],
);
}
@@ -1299,7 +1311,10 @@ void f(Object x) {
if (x case Null _) {}
}
''',
[error(WarningCode.PATTERN_NEVER_MATCHES_VALUE_TYPE, 32, 4)],
[
error(WarningCode.PATTERN_NEVER_MATCHES_VALUE_TYPE, 32, 4),
error(WarningCode.DEAD_CODE, 40, 2),
],
);
}
@@ -1310,7 +1325,10 @@ void f<T extends num>(T x) {
if (x case Null _) {}
}
''',
[error(WarningCode.PATTERN_NEVER_MATCHES_VALUE_TYPE, 42, 4)],
[
error(WarningCode.PATTERN_NEVER_MATCHES_VALUE_TYPE, 42, 4),
error(WarningCode.DEAD_CODE, 50, 2),
],
);
}
@@ -122,7 +122,8 @@ void f(A? x) {
}
test_eqEq_operandNull() async {
await assertNoErrorsInCode(r'''
await assertErrorsInCode(
r'''
class A {}
void f(A x) {
@@ -131,7 +132,9 @@ void f(A x) {
break;
}
}
''');
''',
[error(WarningCode.DEAD_CODE, 65, 6)],
);
}
test_eqEq_operandNullable() async {
@@ -5381,6 +5381,7 @@ main() {
34,
1,
),
error(WarningCode.DEAD_CODE, 36, 5),
error(StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, 39, 2),
error(WarningCode.UNUSED_LOCAL_VARIABLE, 55, 1),
],
@@ -5404,6 +5405,7 @@ main() {
40,
1,
),
error(WarningCode.DEAD_CODE, 42, 5),
error(StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, 45, 2),
],
);
@@ -176,6 +176,9 @@ class DocumentationValidator {
'PubspecWarningCode.WORKSPACE_VALUE_NOT_STRING',
'PubspecWarningCode.WORKSPACE_VALUE_NOT_SUBDIRECTORY',
// Produces two diagnostics out of necessity.
'StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION',
// Reports CompileTimeErrorCode.FINAL_CLASS_EXTENDED_OUTSIDE_OF_LIBRARY
'WarningCode.DEPRECATED_EXTENDS_FUNCTION',
// Produces more than one error range by design.
@@ -183,6 +186,8 @@ class DocumentationValidator {
'WarningCode.TEXT_DIRECTION_CODE_POINT_IN_COMMENT',
// Produces more than one error range by design.
'WarningCode.TEXT_DIRECTION_CODE_POINT_IN_LITERAL',
// Produces two diagnostics out of necessity.
'WarningCode.UNNECESSARY_NULL_COMPARISON_NEVER_NULL_FALSE',
];
/// The buffer to which validation errors are written.
@@ -301,11 +301,11 @@ class ExperimentalFlag {
static const ExperimentalFlag soundFlowAnalysis = const ExperimentalFlag(
name: 'sound-flow-analysis',
isEnabledByDefault: false,
isEnabledByDefault: true,
isExpired: false,
enabledVersion: defaultLanguageVersion,
experimentEnabledVersion: defaultLanguageVersion,
experimentReleasedVersion: defaultLanguageVersion);
enabledVersion: const Version(3, 9),
experimentEnabledVersion: const Version(3, 9),
experimentReleasedVersion: const Version(3, 9));
static const ExperimentalFlag spreadCollections = const ExperimentalFlag(
name: 'spread-collections',
@@ -110,6 +110,7 @@ class C {
[
error(WarningCode.NON_NULLABLE_EQUALS_PARAMETER, 62, 2),
lint(126, 14),
error(WarningCode.DEAD_CODE, 132, 8),
error(StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, 135, 5),
],
);
@@ -442,7 +442,10 @@ class InvalidRuntimeCheckWithJSInteropTypesTest extends LintRuleTest {
null as JSArray?;
}
''',
[error(WarningCode.CAST_FROM_NULL_ALWAYS_FAILS, 54, 13)],
[
error(WarningCode.CAST_FROM_NULL_ALWAYS_FAILS, 54, 13),
error(WarningCode.DEAD_CODE, 75, 17),
],
);
}
@@ -151,6 +151,7 @@ void f([int? optional]) {
[
error(StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, 59, 2),
lint(46, 15),
error(WarningCode.DEAD_CODE, 59, 3),
],
);
}
@@ -305,7 +306,7 @@ void f([int p = 42]) {
p ??= 8;
}
''',
[lint(65, 7)],
[lint(65, 7), error(WarningCode.DEAD_CODE, 71, 2)],
);
}
@@ -317,7 +318,7 @@ void f({int p = 42}) {
p ??= 8;
}
''',
[lint(65, 7)],
[lint(65, 7), error(WarningCode.DEAD_CODE, 71, 2)],
);
}
@@ -346,7 +347,7 @@ void f([int? p]) {
p ??= 16;
}
''',
[lint(72, 8)],
[lint(72, 8), error(WarningCode.DEAD_CODE, 78, 3)],
);
}
@@ -24,7 +24,10 @@ void f() {
var x = 1 ?? 1;
}
''',
[error(StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, 26, 1)],
[
error(WarningCode.DEAD_CODE, 23, 4),
error(StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, 26, 1),
],
);
}
@@ -46,7 +49,11 @@ void f() {
var x = 1 ?? null;
}
''',
[lint(26, 4), error(StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, 26, 4)],
[
error(WarningCode.DEAD_CODE, 23, 7),
lint(26, 4),
error(StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, 26, 4),
],
);
}
@@ -61,6 +68,7 @@ class C {
}
''',
[
error(WarningCode.DEAD_CODE, 32, 7),
lint(35, 4),
error(StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, 35, 4),
lint(53, 4),
@@ -79,6 +87,7 @@ class C {
''',
[
// No lint.
error(WarningCode.DEAD_CODE, 32, 4),
error(StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, 35, 1),
],
);
@@ -91,6 +100,7 @@ var x = 1 ?? null;
var y = null ?? 1;
''',
[
error(WarningCode.DEAD_CODE, 10, 7),
lint(13, 4),
error(StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, 13, 4),
lint(27, 4),
@@ -105,6 +115,7 @@ var x = 1 ?? 1;
''',
[
// No lint.
error(WarningCode.DEAD_CODE, 10, 4),
error(StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, 13, 1),
],
);
@@ -115,7 +126,10 @@ var x = 1 ?? 1;
r'''
var x = 1 ?? 1;
''',
[error(StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, 13, 1)],
[
error(WarningCode.DEAD_CODE, 10, 4),
error(StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, 13, 1),
],
);
}
@@ -133,7 +147,11 @@ var x = null ?? 1;
r'''
var x = 1 ?? null;
''',
[lint(13, 4), error(StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, 13, 4)],
[
error(WarningCode.DEAD_CODE, 10, 7),
lint(13, 4),
error(StaticWarningCode.DEAD_NULL_AWARE_EXPRESSION, 13, 4),
],
);
}
}
@@ -251,6 +251,7 @@ void f() {
[
// No lint.
error(WarningCode.UNNECESSARY_NULL_COMPARISON_NEVER_NULL_FALSE, 23, 7),
error(WarningCode.DEAD_CODE, 32, 2),
],
);
}
+2 -1
View File
@@ -18,7 +18,7 @@ bool GetExperimentalFeatureDefault(ExperimentalFeature feature) {
constexpr bool kFeatureValues[] = {
true, true, true, true, true, true, true, true, true,
true, true, true, true, true, true, true, true, true,
true, true, true, true, true, true, true, true,
true, true, true, true, true, true, true, true, true,
};
ASSERT(static_cast<size_t>(feature) < ARRAY_SIZE(kFeatureValues));
return kFeatureValues[static_cast<int>(feature)];
@@ -26,6 +26,7 @@ bool GetExperimentalFeatureDefault(ExperimentalFeature feature) {
const char* GetExperimentalFeatureName(ExperimentalFeature feature) {
constexpr const char* kFeatureNames[] = {
"sound-flow-analysis",
"null-aware-elements",
"inference-using-bounds",
"wildcard-variables",
+1
View File
@@ -12,6 +12,7 @@
namespace dart {
enum class ExperimentalFeature {
sound_flow_analysis,
null_aware_elements,
inference_using_bounds,
wildcard_variables,
@@ -2,10 +2,8 @@
// 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.
/// This test verifies that `is` and `==` tests performed on `this` do not lead
/// to code being considered unreachable. (In principle, we could soundly mark
/// some such code as unreachable, but we have decided not to do so at this
/// time).
/// This test verifies that `is` and `==` tests performed on `this` lead to code
/// being considered unreachable. (Prior to Dart 3.9, they didn't do so.)
import 'package:expect/static_type_helper.dart';
@@ -19,9 +17,9 @@ class C {
} else {
y = null;
}
// Since the assignments to x and y were both reachable, they should have
// static type `int?` now.
x.expectStaticType<Exactly<int?>>();
// Since the assignment to y was reachable, it should have static type
// `int?` now.
x.expectStaticType<Exactly<int>>();
y.expectStaticType<Exactly<int?>>();
}
@@ -36,10 +34,10 @@ class C {
y = null;
}
}
// Since the assignments to x and y were both reachable, they should have
// static type `int?` now.
x.expectStaticType<Exactly<int?>>();
y.expectStaticType<Exactly<int?>>();
// Since the assignments to x and y were both unreachable, they should still
// have static type `int`.
x.expectStaticType<Exactly<int>>();
y.expectStaticType<Exactly<int>>();
}
void isSimple(int? x, int? y) {
@@ -68,10 +66,10 @@ class C {
y = null;
}
}
// Since the assignment to y was reachable, it should have static type
// `int?` now. But x should still have static type `int`.
// Since the assignments to x an y were both unreachable, they should still
// have static type `int`.
x.expectStaticType<Exactly<int>>();
y.expectStaticType<Exactly<int?>>();
y.expectStaticType<Exactly<int>>();
}
}
@@ -87,9 +85,9 @@ extension on D {
} else {
y = null;
}
// Since the assignments to x and y were both reachable, they should have
// static type `int?` now.
x.expectStaticType<Exactly<int?>>();
// Since the assignment to y was reachable, it should have static type
// `int?` now.
x.expectStaticType<Exactly<int>>();
y.expectStaticType<Exactly<int?>>();
}
@@ -104,10 +102,10 @@ extension on D {
y = null;
}
}
// Since the assignments to x and y were both reachable, they should have
// static type `int?` now.
x.expectStaticType<Exactly<int?>>();
y.expectStaticType<Exactly<int?>>();
// Since the assignments to x and y were both unreachable, they should still
// have static type `int`.
x.expectStaticType<Exactly<int>>();
y.expectStaticType<Exactly<int>>();
}
void isSimple(int? x, int? y) {
@@ -136,10 +134,10 @@ extension on D {
y = null;
}
}
// Since the assignment to y was reachable, it should have static type
// `int?` now. But x should still have static type `int`.
// Since the assignments to x and y were both unreachable, they should still
// have static type `int`.
x.expectStaticType<Exactly<int>>();
y.expectStaticType<Exactly<int?>>();
y.expectStaticType<Exactly<int>>();
}
}
@@ -72,12 +72,10 @@ test() {
reachability1.expectStaticType<Exactly<int?>>();
}
{
// `case null` can even match non-nullable types.
// `case null` cannot match non-nullable types.
// Note that in most cases, flow analysis assumes soundness when analyzing
// patterns. This is an exception: `case null` is assumed to be a possible
// match even for a non-nullable scrutinee; this makes `case null` behave
// similarly to an `if (x == null)` test.
// As of Dart 3.9, flow analysis assumes soundness when analyzing all
// patterns, so `case null` cannot match a non-nullable type.
int? reachability0 = 0;
int? reachability1 = 0;
if (expr<int>() case null) {
@@ -85,7 +83,7 @@ test() {
} else {
reachability1 = null;
}
reachability0.expectStaticType<Exactly<int?>>();
reachability0.expectStaticType<Exactly<int>>();
reachability1.expectStaticType<Exactly<int?>>();
}
{
@@ -86,12 +86,10 @@ test() {
reachability1.expectStaticType<Exactly<int?>>();
}
{
// An `== null` pattern can even match non-nullable types.
// An `== null` pattern cannot match non-nullable types.
// Note that in most cases, flow analysis assumes soundness when analyzing
// patterns. This is an exception: `case == null` is assumed to be a
// possible match even for a non-nullable scrutinee; this makes `case ==
// null` behave similarly to an `if (x == null)` test.
// As of Dart 3.9, flow analysis assumes soundness when analyzing all
// patterns, so `== null` cannot match a non-nullable type.
int? reachability0 = 0;
int? reachability1 = 0;
if (expr<int>() case == null) {
@@ -99,7 +97,7 @@ test() {
} else {
reachability1 = null;
}
reachability0.expectStaticType<Exactly<int?>>();
reachability0.expectStaticType<Exactly<int>>();
reachability1.expectStaticType<Exactly<int?>>();
}
{
@@ -178,13 +176,11 @@ test() {
}
}
{
// A `!= null` pattern may or may not match, even if the matched value type
// if non-nullable.
// A `!= null` pattern always matches, if the matched value type is
// non-nullable.
// Note that in most cases, flow analysis assumes soundness when analyzing
// patterns. This is an exception: `case != null` is assumed to be a
// possible mismatch even for a non-nullable scrutinee; this makes `case !=
// null` behave similarly to an `if (x != null)` test.
// As of Dart 3.9, flow analysis assumes soundness when analyzing all
// patterns, so `!= null` always matches a non-nullable type.
int? reachability0 = 0;
int? reachability1 = 0;
if (expr<int>() case != null) {
@@ -193,7 +189,7 @@ test() {
reachability1 = null;
}
reachability0.expectStaticType<Exactly<int?>>();
reachability1.expectStaticType<Exactly<int?>>();
reachability1.expectStaticType<Exactly<int>>();
}
{
// If a relational pattern using `!=` appears inside a record pattern, the
@@ -308,8 +308,8 @@ void testLogicalOrNeitherMatches(Object x) {
}
void testNullCheckAlwaysMatches(Object x) {
// TODO(paulberry): should be trivially exhaustive because the matched value
// type is non-nullable and the subpattern always matches
// Trivially exhaustive because the matched value type is non-nullable and the
// subpattern always matches
bool? y;
switch (x) {
case _?:
@@ -317,7 +317,7 @@ void testNullCheckAlwaysMatches(Object x) {
// [analyzer] STATIC_WARNING.UNNECESSARY_NULL_CHECK_PATTERN
y = true;
}
y.expectStaticType<Exactly<bool?>>();
y.expectStaticType<Exactly<bool>>();
}
void testNullCheckNullableMatchedValueType(Object? x) {
@@ -635,13 +635,13 @@ void testWildcardUntyped(Object x) {
}
void testRelationalNotEqualsNullWithNonNullableScrutinee(Object x) {
// TODO(paulberry): this should be trivially exhaustive
// Trivially exhaustive because the matched value type is non-nullable
bool? y;
switch (x) {
case != null:
y = true;
}
y.expectStaticType<Exactly<bool?>>();
y.expectStaticType<Exactly<bool>>();
}
void testRelationalNotEqualsNullWithNullableScrutinee(Object? x) {
+12 -4
View File
@@ -151,10 +151,6 @@ features:
getter-setter-error:
help: "Stop reporting errors about mismatching types in a getter/setter pair."
sound-flow-analysis:
# See https://github.com/dart-lang/language/issues/3100
help: "Assume sound null safety when computing type promotion, reachability, and definite assignment."
# Experiment flag only used for testing.
test-experiment:
help: >-
@@ -168,6 +164,18 @@ features:
# Shipped flags should be marked retired the following stable release.
#
sound-flow-analysis:
enabledIn: '3.9.0'
validation: |
main() {
var x = 0;
String y;
if (x != null) y = 'feature enabled';
print(y);
}
# See https://github.com/dart-lang/language/issues/3100
help: "Assume sound null safety when computing type promotion, reachability, and definite assignment."
null-aware-elements:
enabledIn: '3.8.0'
validation: |