diff --git a/CHANGELOG.md b/CHANGELOG.md index 443724ac352..8ce02c18969 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/pkg/_fe_analyzer_shared/lib/src/experiments/flags.dart b/pkg/_fe_analyzer_shared/lib/src/experiments/flags.dart index c4c3db64351..43563f2149b 100644 --- a/pkg/_fe_analyzer_shared/lib/src/experiments/flags.dart +++ b/pkg/_fe_analyzer_shared/lib/src/experiments/flags.dart @@ -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', diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/reachability/data/equality_operator.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/reachability/data/equality_operator.dart index 30a2a57ad61..9c6c5910319 100644 --- a/pkg/_fe_analyzer_shared/test/flow_analysis/reachability/data/equality_operator.dart +++ b/pkg/_fe_analyzer_shared/test/flow_analysis/reachability/data/equality_operator.dart @@ -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 x) { } void nonNullableTypeVar(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; } diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/reachability/data/if_null.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/reachability/data/if_null.dart index fa2053df93d..6634d1b1915 100644 --- a/pkg/_fe_analyzer_shared/test/flow_analysis/reachability/data/if_null.dart +++ b/pkg/_fe_analyzer_shared/test/flow_analysis/reachability/data/if_null.dart @@ -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 { @@ -70,12 +70,12 @@ void property_if_null_assign_reachable(HasProperty x) { void property_if_null_unreachable(HasProperty 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 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? x) { @@ -94,7 +94,7 @@ void null_aware_property_if_null_not_shortened(HasProperty? x) { void null_aware_property_if_null_assign_unreachable(HasProperty? x) { // Reachable since the value returned by prop might come from legacy code. - x?.prop ??= 0; + x?.prop ??= /*unreachable*/0; } class SuperIntQuestionProperty extends HasProperty { @@ -110,12 +110,12 @@ class SuperIntQuestionProperty extends HasProperty { class SuperIntProperty extends HasProperty { 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 x) { void extended_property_if_null_unreachable(HasProperty 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 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? x) { @@ -166,7 +166,7 @@ void null_aware_extended_property_if_null_assign_unreachable( HasProperty? 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 x) { @@ -180,13 +180,13 @@ void explicit_extended_property_if_null_assign_reachable(HasProperty x) { void explicit_extended_property_if_null_unreachable(HasProperty 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 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? x) { // Reachable since the value returned by extendedProp might come from legacy // code. - ExtensionProperty(x)?.extendedProp ??= 0; + ExtensionProperty(x)?.extendedProp ??= /*unreachable*/0; } class Indexable { @@ -226,7 +226,7 @@ void index_if_null_reachable(Indexable x) { void index_if_null_unreachable(Indexable 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 x) { @@ -236,7 +236,7 @@ void index_if_null_assign_reachable(Indexable x) { void index_if_null_assign_unreachable(Indexable 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? x) { @@ -256,7 +256,7 @@ void null_aware_index_if_null_assign_reachable(Indexable? x) { void null_aware_index_if_null_assign_unreachable(Indexable? x) { // Reachable since the value returned by operator[] might come from legacy // code. - x?[0] ??= 0; + x?[0] ??= /*unreachable*/0; } class SuperIntQuestionIndex extends Indexable { @@ -273,13 +273,13 @@ class SuperIntIndex extends Indexable { 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 x) { void extended_index_if_null_unreachable(HasProperty 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 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? x) { @@ -328,7 +328,7 @@ void null_aware_extended_index_if_null_not_shortened(HasProperty? x) { void null_aware_extended_index_if_null_assign_unreachable(HasProperty? 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 x) { @@ -342,13 +342,13 @@ void explicit_extended_index_if_null_assign_reachable(HasProperty x) { void explicit_extended_index_if_null_unreachable(HasProperty 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 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? x) { // Reachable since the value returned by operator[] might come from legacy // code. - ExtensionIndex(x)?[0] ??= 0; + ExtensionIndex(x)?[0] ??= /*unreachable*/0; } diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/reachability/data/null_aware_access.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/reachability/data/null_aware_access.dart index 43c8e2e1ee2..09c8c90d864 100644 --- a/pkg/_fe_analyzer_shared/test/flow_analysis/reachability/data/null_aware_access.dart +++ b/pkg/_fe_analyzer_shared/test/flow_analysis/reachability/data/null_aware_access.dart @@ -7,10 +7,11 @@ void index_reachable(List? f()) { 0; } +/*member: index_unreachable:doesNotComplete*/ void index_unreachable(List f()) { // Reachable since the value returned by f() might come from legacy code f()?[throw '']; - 0; + /*stmt: unreachable*/0; } void cascaded_index_reachable(List? f()) { @@ -18,10 +19,11 @@ void cascaded_index_reachable(List? f()) { 0; } +/*member: cascaded_index_unreachable:doesNotComplete*/ void cascaded_index_unreachable(List 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? f()) { @@ -76,13 +82,14 @@ void property_get_invocation_reachable(List? f()) { 0; } +/*member: property_get_invocation_unreachable:doesNotComplete*/ void property_get_invocation_unreachable(List 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 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; } diff --git a/pkg/analysis_server/test/src/services/correction/fix/remove_comparison_test.dart b/pkg/analysis_server/test/src/services/correction/fix/remove_comparison_test.dart index 05c1d325c47..78d5b3282af 100644 --- a/pkg/analysis_server/test/src/services/correction/fix/remove_comparison_test.dart +++ b/pkg/analysis_server/test/src/services/correction/fix/remove_comparison_test.dart @@ -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 test_ifElement_alwaysFalse_hasElse_withComments() async { @@ -197,7 +202,7 @@ void f(int x) { 2, ]; } -'''); +''', errorFilter: _ignoreDeadCode); } Future test_ifElement_alwaysFalse_noElse_insideList() async { @@ -217,7 +222,7 @@ void f(int x) { 2, ]; } -'''); +''', errorFilter: _ignoreDeadCode); } Future @@ -241,7 +246,7 @@ void f(int x) { 2, ]; } -'''); +''', errorFilter: _ignoreDeadCode); } Future test_ifElement_alwaysFalse_noElse_insideSet() async { @@ -261,7 +266,7 @@ Object f(int x) { 2, }; } -'''); +''', errorFilter: _ignoreDeadCode); } Future test_ifElement_alwaysTrue() async { @@ -304,7 +309,7 @@ void f(int x) { 2, ]; } -'''); +''', errorFilter: _ignoreDeadCode); } Future test_ifElement_alwaysTrue_withComments() async { @@ -351,7 +356,7 @@ void f(int x) { 2; 3; } -'''); +''', errorFilter: _ignoreDeadCode); } Future test_ifStatement_alwaysFalse_hasElse_block_empty() async { @@ -369,7 +374,7 @@ void f(int x) { 0; 2; } -'''); +''', errorFilter: _ignoreDeadCode); } Future test_ifStatement_alwaysFalse_hasElse_statement() async { @@ -389,7 +394,7 @@ void f(int x) { 2; 3; } -'''); +''', errorFilter: _ignoreDeadCode); } Future test_ifStatement_alwaysFalse_noElse() async { @@ -407,7 +412,7 @@ void f(int x) { 0; 2; } -'''); +''', errorFilter: _ignoreDeadCode); } Future test_ifStatement_alwaysTrue_hasElse_block() async { @@ -428,7 +433,7 @@ void f(int x) { 1; 3; } -'''); +''', errorFilter: _ignoreDeadCode); } Future test_ifStatement_alwaysTrue_noElse() async { @@ -745,7 +750,7 @@ class Person { } } '''); - await assertNoFix(); + await assertNoFix(errorFilter: _ignoreDeadCode); } } diff --git a/pkg/analysis_server/test/src/services/correction/fix/remove_if_null_operator_test.dart b/pkg/analysis_server/test/src/services/correction/fix/remove_if_null_operator_test.dart index 2eab8e41940..703a37c8ed5 100644 --- a/pkg/analysis_server/test/src/services/correction/fix/remove_if_null_operator_test.dart +++ b/pkg/analysis_server/test/src/services/correction/fix/remove_if_null_operator_test.dart @@ -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 test_assignmentExpression_simpleIdentifier_field() async { @@ -64,7 +69,7 @@ class C { void f(int b) { } } -'''); +''', errorFilter: _ignoreDeadCode); } Future 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 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 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 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 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); } } diff --git a/pkg/analyzer/lib/src/dart/analysis/experiments.g.dart b/pkg/analyzer/lib/src/dart/analysis/experiments.g.dart index 29226f9a31e..0dc17c3bbdf 100644 --- a/pkg/analyzer/lib/src/dart/analysis/experiments.g.dart +++ b/pkg/analyzer/lib/src/dart/analysis/experiments.g.dart @@ -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; diff --git a/pkg/analyzer/test/src/dart/analysis/analysis_context_collection_test.dart b/pkg/analyzer/test/src/dart/analysis/analysis_context_collection_test.dart index 4c5312a1419..659469609d9 100644 --- a/pkg/analyzer/test/src/dart/analysis/analysis_context_collection_test.dart +++ b/pkg/analyzer/test/src/dart/analysis/analysis_context_collection_test.dart @@ -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 diff --git a/pkg/analyzer/test/src/dart/constant/evaluation_test.dart b/pkg/analyzer/test/src/dart/constant/evaluation_test.dart index 84bdc4884aa..56b1d907e5f 100644 --- a/pkg/analyzer/test/src/dart/constant/evaluation_test.dart +++ b/pkg/analyzer/test/src/dart/constant/evaluation_test.dart @@ -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, diff --git a/pkg/analyzer/test/src/dart/resolution/ast_rewrite_test.dart b/pkg/analyzer/test/src/dart/resolution/ast_rewrite_test.dart index b5ae43e473e..4530c672188 100644 --- a/pkg/analyzer/test/src/dart/resolution/ast_rewrite_test.dart +++ b/pkg/analyzer/test/src/dart/resolution/ast_rewrite_test.dart @@ -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. diff --git a/pkg/analyzer/test/src/diagnostics/constant_pattern_never_matches_value_type_test.dart b/pkg/analyzer/test/src/diagnostics/constant_pattern_never_matches_value_type_test.dart index 872ee19094f..4f2720cf0ab 100644 --- a/pkg/analyzer/test/src/diagnostics/constant_pattern_never_matches_value_type_test.dart +++ b/pkg/analyzer/test/src/diagnostics/constant_pattern_never_matches_value_type_test.dart @@ -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 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), + ], ); } diff --git a/pkg/analyzer/test/src/diagnostics/dead_null_aware_expression_test.dart b/pkg/analyzer/test/src/diagnostics/dead_null_aware_expression_test.dart index 0bd257f1fe4..43d71b88df4 100644 --- a/pkg/analyzer/test/src/diagnostics/dead_null_aware_expression_test.dart +++ b/pkg/analyzer/test/src/diagnostics/dead_null_aware_expression_test.dart @@ -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), + ], ); } diff --git a/pkg/analyzer/test/src/diagnostics/not_assigned_potentially_non_nullable_local_variable_test.dart b/pkg/analyzer/test/src/diagnostics/not_assigned_potentially_non_nullable_local_variable_test.dart index f74f9f6dff2..c210db2aeb4 100644 --- a/pkg/analyzer/test/src/diagnostics/not_assigned_potentially_non_nullable_local_variable_test.dart +++ b/pkg/analyzer/test/src/diagnostics/not_assigned_potentially_non_nullable_local_variable_test.dart @@ -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), ], diff --git a/pkg/analyzer/test/src/diagnostics/pattern_never_matches_value_type_test.dart b/pkg/analyzer/test/src/diagnostics/pattern_never_matches_value_type_test.dart index 02c1a7d33f3..5dcb79b5a9a 100644 --- a/pkg/analyzer/test/src/diagnostics/pattern_never_matches_value_type_test.dart +++ b/pkg/analyzer/test/src/diagnostics/pattern_never_matches_value_type_test.dart @@ -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 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), + ], ); } diff --git a/pkg/analyzer/test/src/diagnostics/relational_pattern_operand_type_not_assignable_test.dart b/pkg/analyzer/test/src/diagnostics/relational_pattern_operand_type_not_assignable_test.dart index 9667f6a4980..ccdfe4d4477 100644 --- a/pkg/analyzer/test/src/diagnostics/relational_pattern_operand_type_not_assignable_test.dart +++ b/pkg/analyzer/test/src/diagnostics/relational_pattern_operand_type_not_assignable_test.dart @@ -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 { diff --git a/pkg/analyzer/test/src/task/strong/inferred_type_test.dart b/pkg/analyzer/test/src/task/strong/inferred_type_test.dart index 2d958e0a20c..a5437ab7f4a 100644 --- a/pkg/analyzer/test/src/task/strong/inferred_type_test.dart +++ b/pkg/analyzer/test/src/task/strong/inferred_type_test.dart @@ -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), ], ); diff --git a/pkg/analyzer/test/verify_diagnostics_test.dart b/pkg/analyzer/test/verify_diagnostics_test.dart index bfc218d609c..58ab49a49a6 100644 --- a/pkg/analyzer/test/verify_diagnostics_test.dart +++ b/pkg/analyzer/test/verify_diagnostics_test.dart @@ -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. diff --git a/pkg/front_end/lib/src/api_prototype/experimental_flags_generated.dart b/pkg/front_end/lib/src/api_prototype/experimental_flags_generated.dart index 19d097f17ab..4b6ab154b56 100644 --- a/pkg/front_end/lib/src/api_prototype/experimental_flags_generated.dart +++ b/pkg/front_end/lib/src/api_prototype/experimental_flags_generated.dart @@ -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', diff --git a/pkg/linter/test/rules/avoid_null_checks_in_equality_operators_test.dart b/pkg/linter/test/rules/avoid_null_checks_in_equality_operators_test.dart index 1a2f4cf363a..51e2c40036a 100644 --- a/pkg/linter/test/rules/avoid_null_checks_in_equality_operators_test.dart +++ b/pkg/linter/test/rules/avoid_null_checks_in_equality_operators_test.dart @@ -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), ], ); diff --git a/pkg/linter/test/rules/invalid_runtime_check_with_js_interop_types_test.dart b/pkg/linter/test/rules/invalid_runtime_check_with_js_interop_types_test.dart index 513096069fa..f1350117a5b 100644 --- a/pkg/linter/test/rules/invalid_runtime_check_with_js_interop_types_test.dart +++ b/pkg/linter/test/rules/invalid_runtime_check_with_js_interop_types_test.dart @@ -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), + ], ); } diff --git a/pkg/linter/test/rules/parameter_assignments_test.dart b/pkg/linter/test/rules/parameter_assignments_test.dart index 919e813c60e..4d41894e7b3 100644 --- a/pkg/linter/test/rules/parameter_assignments_test.dart +++ b/pkg/linter/test/rules/parameter_assignments_test.dart @@ -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)], ); } diff --git a/pkg/linter/test/rules/unnecessary_null_in_if_null_operators_test.dart b/pkg/linter/test/rules/unnecessary_null_in_if_null_operators_test.dart index 846a4ecf943..aec4ab70e08 100644 --- a/pkg/linter/test/rules/unnecessary_null_in_if_null_operators_test.dart +++ b/pkg/linter/test/rules/unnecessary_null_in_if_null_operators_test.dart @@ -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), + ], ); } } diff --git a/pkg/linter/test/rules/unrelated_type_equality_checks_test.dart b/pkg/linter/test/rules/unrelated_type_equality_checks_test.dart index e50ed136b09..9ba287c5186 100644 --- a/pkg/linter/test/rules/unrelated_type_equality_checks_test.dart +++ b/pkg/linter/test/rules/unrelated_type_equality_checks_test.dart @@ -251,6 +251,7 @@ void f() { [ // No lint. error(WarningCode.UNNECESSARY_NULL_COMPARISON_NEVER_NULL_FALSE, 23, 7), + error(WarningCode.DEAD_CODE, 32, 2), ], ); } diff --git a/runtime/vm/experimental_features.cc b/runtime/vm/experimental_features.cc index 09a78fc518d..6486e6c1933 100644 --- a/runtime/vm/experimental_features.cc +++ b/runtime/vm/experimental_features.cc @@ -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(feature) < ARRAY_SIZE(kFeatureValues)); return kFeatureValues[static_cast(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", diff --git a/runtime/vm/experimental_features.h b/runtime/vm/experimental_features.h index 791314dec56..fa8b6ba2f2f 100644 --- a/runtime/vm/experimental_features.h +++ b/runtime/vm/experimental_features.h @@ -12,6 +12,7 @@ namespace dart { enum class ExperimentalFeature { + sound_flow_analysis, null_aware_elements, inference_using_bounds, wildcard_variables, diff --git a/tests/language/nnbd/flow_analysis/unreachable_via_this_test.dart b/tests/language/nnbd/flow_analysis/unreachable_via_this_test.dart index 0a3ee26c7b4..7e7face0b63 100644 --- a/tests/language/nnbd/flow_analysis/unreachable_via_this_test.dart +++ b/tests/language/nnbd/flow_analysis/unreachable_via_this_test.dart @@ -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>(); + // Since the assignment to y was reachable, it should have static type + // `int?` now. + x.expectStaticType>(); y.expectStaticType>(); } @@ -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>(); - y.expectStaticType>(); + // Since the assignments to x and y were both unreachable, they should still + // have static type `int`. + x.expectStaticType>(); + y.expectStaticType>(); } 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>(); - y.expectStaticType>(); + y.expectStaticType>(); } } @@ -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>(); + // Since the assignment to y was reachable, it should have static type + // `int?` now. + x.expectStaticType>(); y.expectStaticType>(); } @@ -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>(); - y.expectStaticType>(); + // Since the assignments to x and y were both unreachable, they should still + // have static type `int`. + x.expectStaticType>(); + y.expectStaticType>(); } 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>(); - y.expectStaticType>(); + y.expectStaticType>(); } } diff --git a/tests/language/patterns/flow_analysis/constant_pattern_error_test.dart b/tests/language/patterns/flow_analysis/constant_pattern_error_test.dart index 7f33841d9c7..32869c87997 100644 --- a/tests/language/patterns/flow_analysis/constant_pattern_error_test.dart +++ b/tests/language/patterns/flow_analysis/constant_pattern_error_test.dart @@ -72,12 +72,10 @@ test() { reachability1.expectStaticType>(); } { - // `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() case null) { @@ -85,7 +83,7 @@ test() { } else { reachability1 = null; } - reachability0.expectStaticType>(); + reachability0.expectStaticType>(); reachability1.expectStaticType>(); } { diff --git a/tests/language/patterns/flow_analysis/relational_pattern_error_test.dart b/tests/language/patterns/flow_analysis/relational_pattern_error_test.dart index 3bc9cb5b1c1..87625082e91 100644 --- a/tests/language/patterns/flow_analysis/relational_pattern_error_test.dart +++ b/tests/language/patterns/flow_analysis/relational_pattern_error_test.dart @@ -86,12 +86,10 @@ test() { reachability1.expectStaticType>(); } { - // 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() case == null) { @@ -99,7 +97,7 @@ test() { } else { reachability1 = null; } - reachability0.expectStaticType>(); + reachability0.expectStaticType>(); reachability1.expectStaticType>(); } { @@ -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() case != null) { @@ -193,7 +189,7 @@ test() { reachability1 = null; } reachability0.expectStaticType>(); - reachability1.expectStaticType>(); + reachability1.expectStaticType>(); } { // If a relational pattern using `!=` appears inside a record pattern, the diff --git a/tests/language/patterns/switch_trivial_exhaustiveness_error_test.dart b/tests/language/patterns/switch_trivial_exhaustiveness_error_test.dart index 624f63b4134..0a362fa771d 100644 --- a/tests/language/patterns/switch_trivial_exhaustiveness_error_test.dart +++ b/tests/language/patterns/switch_trivial_exhaustiveness_error_test.dart @@ -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>(); + y.expectStaticType>(); } 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>(); + y.expectStaticType>(); } void testRelationalNotEqualsNullWithNullableScrutinee(Object? x) { diff --git a/tools/experimental_features.yaml b/tools/experimental_features.yaml index 07d1fe9285e..7cb05c9d6db 100644 --- a/tools/experimental_features.yaml +++ b/tools/experimental_features.yaml @@ -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: |