From 2a746cb51f25d8398475fd6fd1c6d9ba2da20757 Mon Sep 17 00:00:00 2001 From: Johnni Winther Date: Fri, 3 Mar 2023 12:31:26 +0000 Subject: [PATCH] [_fe_analyzer_shared] Improve exhaustiveness for null-assert This improves the exhaustiveness handling for null assert patterns by extending the space of the subpattern with the null space. This reflects the fact that null assert will throw on `null` and can therefore be considered to cover that case. Change-Id: If344eaaa55dcc70474f45519b53586e7d36e6e80 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/286400 Commit-Queue: Johnni Winther Reviewed-by: Konstantin Shcheglov --- .../test/exhaustiveness/data/null_assert.dart | 117 ++++++++++++++++-- .../lib/src/generated/exhaustiveness.dart | 6 +- .../lib/src/fasta/kernel/exhaustiveness.dart | 8 +- 3 files changed, 120 insertions(+), 11 deletions(-) diff --git a/pkg/_fe_analyzer_shared/test/exhaustiveness/data/null_assert.dart b/pkg/_fe_analyzer_shared/test/exhaustiveness/data/null_assert.dart index bc803a8503b..b9ce4d2f0a1 100644 --- a/pkg/_fe_analyzer_shared/test/exhaustiveness/data/null_assert.dart +++ b/pkg/_fe_analyzer_shared/test/exhaustiveness/data/null_assert.dart @@ -8,23 +8,29 @@ class A { A(this.field); } +sealed class B {} +class C extends B {} +class D extends B {} + simpleAssert(o1, o2) { var a = /* fields={}, subtypes={Object,Null}, type=Object? */switch (o1) { - _! /*space=??*/=> 0, - _ /*space=()*/=> 1 + _! /*space=()*/=> 0, + _ /* + error=unreachable, + space=() + */=> 1 }; var b = /* - error=non-exhaustive:Object, fields={}, subtypes={Object,Null}, type=Object? */switch (o2) { - _! /*space=??*/=> 0, + _! /*space=()*/=> 0, }; } @@ -36,7 +42,7 @@ restrictedCase(o1, o2) { subtypes={Object,Null}, type=Object? */switch (o1) { - A(field: 42)! /*space=??*/=> 0, + A(field: 42)! /*cfe.space=A(field: IntConstant(42))|Null*//*analyzer.space=A(field: int (42))|Null*/=> 0, _ /*space=()*/=> 1 }; @@ -46,6 +52,103 @@ restrictedCase(o1, o2) { subtypes={Object,Null}, type=Object? */switch (o2) { - A(field: 42)! /*space=??*/=> 0, + A(field: 42)! /*cfe.space=A(field: IntConstant(42))|Null*//*analyzer.space=A(field: int (42))|Null*/=> 0, }; -} \ No newline at end of file +} + +nullableBool(bool? b1, bool? b2) { + /* + expandedSubtypes={true,false,Null}, + fields={}, + subtypes={bool,Null}, + type=bool? + */ + switch (b1) { + /*space=true?*/ + case true!: + break; + /*space=false*/ + case false: + break; + } + /* + error=non-exhaustive:false, + expandedSubtypes={true,false,Null}, + fields={}, + subtypes={bool,Null}, + type=bool? + */ + switch (b2) { + /*space=true?*/ + case true!: + break; + } +} + +nullableA(A? a1, A? a2, A? a3) { + var a = /* + fields={}, + subtypes={A,Null}, + type=A? + */switch (a1) { + A()! /*space=A?*/=> 0, + }; + var b = /* + fields={}, + subtypes={A,Null}, + type=A? + */switch (a2) { + A(:var field)! /*space=A(field: int)|Null*/=> 0, + }; + var c = /* + error=non-exhaustive:A(field: int), + fields={}, + subtypes={A,Null}, + type=A? + */switch (a3) { + A(field: 42)! + /*cfe.space=A(field: IntConstant(42))|Null*/ + /*analyzer.space=A(field: int (42))|Null*/ + => 0, + }; +} + +nullableB(B? b1, B? b2, B? b3) { + /* + expandedSubtypes={C,D,Null}, + fields={}, + subtypes={B,Null}, + type=B? + */ + switch (b1) { + /*space=B?*/ + case B()!: + break; + } + /* + expandedSubtypes={C,D,Null}, + fields={}, + subtypes={B,Null}, + type=B? + */ + switch (b2) { + /*space=C?*/ + case C()!: + break; + /*space=D*/ + case D(): + break; + } + /* + error=non-exhaustive:D, + expandedSubtypes={C,D,Null}, + fields={}, + subtypes={B,Null}, + type=B? + */ + switch (b3) { + /*space=C?*/ + case C()!: + break; + } +} diff --git a/pkg/analyzer/lib/src/generated/exhaustiveness.dart b/pkg/analyzer/lib/src/generated/exhaustiveness.dart index 37f7e43fb0d..72d51364f95 100644 --- a/pkg/analyzer/lib/src/generated/exhaustiveness.dart +++ b/pkg/analyzer/lib/src/generated/exhaustiveness.dart @@ -380,8 +380,10 @@ class PatternConverter { return convertPattern(pattern.pattern, nonNull: true); } else if (pattern is ParenthesizedPattern) { return convertPattern(pattern.pattern, nonNull: nonNull); - } else if (pattern is NullAssertPattern || - pattern is CastPattern || + } else if (pattern is NullAssertPattern) { + Space space = convertPattern(pattern.pattern, nonNull: true); + return Space.union([space, Space.nullSpace]); + } else if (pattern is CastPattern || pattern is RelationalPattern || pattern is LogicalAndPattern) { // These pattern do not add to the exhaustiveness coverage. diff --git a/pkg/front_end/lib/src/fasta/kernel/exhaustiveness.dart b/pkg/front_end/lib/src/fasta/kernel/exhaustiveness.dart index f7d7505366e..985fbbc1b92 100644 --- a/pkg/front_end/lib/src/fasta/kernel/exhaustiveness.dart +++ b/pkg/front_end/lib/src/fasta/kernel/exhaustiveness.dart @@ -473,8 +473,12 @@ Space convertPatternToSpace(CfeExhaustivenessCache cache, Pattern pattern, } else if (pattern is NullCheckPattern) { return convertPatternToSpace(cache, pattern.pattern, constants, context, nonNull: true); - } else if (pattern is NullAssertPattern || - pattern is CastPattern || + } else if (pattern is NullAssertPattern) { + Space space = convertPatternToSpace( + cache, pattern.pattern, constants, context, + nonNull: true); + return new Space.union([space, Space.nullSpace]); + } else if (pattern is CastPattern || pattern is InvalidPattern || pattern is RelationalPattern || pattern is AndPattern) {