[_fe_analyzer_shared] Handle empty types in exhaustiveness

This handles Never, and as a consequence, empty sealed types in
exhaustiveness. Since these contain no value, we should not try to
exhaust them. This avoid invalid non-exhaustive errors in these cases.

Included is handling of a scrutinee with invalid type as Never in the
CFE, thus avoid cascading non-exhaustive or unreachable errors.

Change-Id: Ife097731bb5399c42bf83f1d77aff773346adfb6
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/293682
Reviewed-by: Paul Berry <paulberry@google.com>
Commit-Queue: Johnni Winther <johnniwinther@google.com>
This commit is contained in:
Johnni Winther 2023-04-05 18:26:06 +00:00 committed by Commit Queue
parent 17dd6d27cc
commit 65335cb4b3
6 changed files with 146 additions and 48 deletions

View file

@ -109,6 +109,10 @@ class _Checker {
List<StaticType> stack = [firstValuePattern.type];
while (stack.isNotEmpty) {
StaticType type = stack.removeAt(0);
if (type.isSubtypeOf(StaticType.neverType)) {
// Don't try to exhaust the Never type.
continue;
}
if (type.isSealed) {
Witness? result = _filterByType(
contextType,

View file

@ -49,3 +49,38 @@ emptyEnum(E e) {
};
}
sealed class Empty {}
emptySealed(Empty empty) => /*
checkingOrder={Empty},
type=Empty
*/
switch (empty) {
};
emptyNever(Never never) => /*type=Never*/ switch (never) { };
emptyUnresolved(
Unresolved
unresolved) => /*cfe.type=Never*/ /*analyzer.
checkingOrder={Object?,Object,Null},
error=non-exhaustive:Object(),
subtypes={Object,Null},
type=Object?
*/
switch (unresolved) {
};
nonEmptyUnresolved(
Unresolved
unresolved) => /*cfe.type=Never*/ /*analyzer.
checkingOrder={Object?,Object,Null},
subtypes={Object,Null},
type=Object?
*/
switch (unresolved) {
_ /*cfe.space=∅*/ /*analyzer.space=()*/ => 0,
};

View file

@ -2,7 +2,17 @@
// 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.
class A<T> {}
class I<T> {}
class J<T> extends I<T> {}
class A<T> extends J<T> {}
extension<T> on I<T> {
num get member {
return T == int ? 0.5 : 1;
}
}
extension<T> on A<T> {
void member(T t) {}
@ -10,36 +20,27 @@ extension<T> on A<T> {
exhaustiveInferred(
A<num>
a) => /*cfe.
fields={A<int>.member:void Function(int),A<num>.member:void Function(num)},
type=<invalid>
*/ /*analyzer.
checkingOrder={Object?,Object,Null},
error=non-exhaustive:Object(),
fields={A<int>.member:void Function(int),A<num>.member:void Function(num)},
subtypes={Object,Null},
type=Object?
*/
switch (o) {
a) => /*
fields={A<int>.member:void Function(int),A<num>.member:void Function(num)},
type=A<num>
*/
switch (a) {
A<int>(
:var member
) /*cfe.space=<invalid>(A<int>.member: void Function(int) (void Function(int)))*/ /*analyzer.space=A<int>(A<int>.member: void Function(int) (void Function(int)))*/ =>
) /*space=A<int>(A<int>.member: void Function(int) (void Function(int)))*/ =>
0,
A<num>(
:var member
) /*cfe.
error=unreachable,
space=<invalid>(A<num>.member: void Function(num) (void Function(num)))
*/ /*analyzer.space=A<num>(A<num>.member: void Function(num) (void Function(num)))*/ =>
) /*space=A<num>(A<num>.member: void Function(num) (void Function(num)))*/ =>
1,
};
exhaustiveTyped(
A<num>
a) => /*cfe.
fields={A<int>.member:void Function(int),A<num>.member:void Function(num)},
type=<invalid>
*/ /*analyzer.
fields={A<int>.member:void Function(int),A<num>.member:void Function(num)},
type=Never
*/ /*analyzer.
checkingOrder={Object?,Object,Null},
error=non-exhaustive:Object(),
fields={A<int>.member:void Function(int),A<num>.member:void Function(num)},
@ -49,23 +50,23 @@ exhaustiveTyped(
switch (o) {
A<int>(
:void Function(int) member
) /*cfe.space=<invalid>(A<int>.member: void Function(int) (void Function(int)))*/ /*analyzer.space=A<int>(A<int>.member: void Function(int) (void Function(int)))*/ =>
) /*cfe.space=Never(A<int>.member: void Function(int) (void Function(int)))*/ /*analyzer.space=A<int>(A<int>.member: void Function(int) (void Function(int)))*/ =>
0,
A<num>(
:void Function(num) member
) /*cfe.
error=unreachable,
space=<invalid>(A<num>.member: void Function(num) (void Function(num)))
*/ /*analyzer.space=A<num>(A<num>.member: void Function(num) (void Function(num)))*/ =>
error=unreachable,
space=Never(A<num>.member: void Function(num) (void Function(num)))
*/ /*analyzer.space=A<num>(A<num>.member: void Function(num) (void Function(num)))*/ =>
1,
};
unreachable(
A<num>
a) => /*cfe.
fields={A<int>.member:void Function(int),A<num>.member:void Function(num)},
type=<invalid>
*/ /*analyzer.
fields={A<int>.member:void Function(int),A<num>.member:void Function(num)},
type=Never
*/ /*analyzer.
checkingOrder={Object?,Object,Null},
error=non-exhaustive:Object(),
fields={A<int>.member:void Function(int),A<num>.member:void Function(num)},
@ -75,14 +76,14 @@ unreachable(
switch (o) {
A<num>(
:var member
) /*cfe.space=<invalid>(A<num>.member: void Function(num) (void Function(num)))*/ /*analyzer.space=A<num>(A<num>.member: void Function(num) (void Function(num)))*/ =>
) /*cfe.space=Never(A<num>.member: void Function(num) (void Function(num)))*/ /*analyzer.space=A<num>(A<num>.member: void Function(num) (void Function(num)))*/ =>
1,
A<int>(
:var member
) /*cfe.
error=unreachable,
space=<invalid>(A<int>.member: void Function(int) (void Function(int)))
*/ /*analyzer.
error=unreachable,
space=Never(A<int>.member: void Function(int) (void Function(int)))
*/ /*analyzer.
error=unreachable,
space=A<int>(A<int>.member: void Function(int) (void Function(int)))
*/
@ -93,9 +94,9 @@ unreachable(
nonExhaustiveRestricted(
A<num>
a) => /*cfe.
fields={A<int>.member:void Function(int),A<num>.member:void Function(num)},
type=<invalid>
*/ /*analyzer.
fields={A<int>.member:void Function(int),A<num>.member:void Function(num)},
type=Never
*/ /*analyzer.
checkingOrder={Object?,Object,Null},
error=non-exhaustive:Object(),
fields={A<int>.member:void Function(int),A<num>.member:void Function(num)},
@ -105,14 +106,14 @@ nonExhaustiveRestricted(
switch (o) {
A<num>(
:void Function(num) member
) /*cfe.space=<invalid>(A<num>.member: void Function(num) (void Function(num)))*/ /*analyzer.space=A<num>(A<num>.member: void Function(num) (void Function(num)))*/ =>
) /*cfe.space=Never(A<num>.member: void Function(num) (void Function(num)))*/ /*analyzer.space=A<num>(A<num>.member: void Function(num) (void Function(num)))*/ =>
1,
A<int>(
:var member
) /*cfe.
error=unreachable,
space=<invalid>(A<int>.member: void Function(int) (void Function(int)))
*/ /*analyzer.
error=unreachable,
space=Never(A<int>.member: void Function(int) (void Function(int)))
*/ /*analyzer.
error=unreachable,
space=A<int>(A<int>.member: void Function(int) (void Function(int)))
*/
@ -135,3 +136,17 @@ intersection(o) {
A<num>(member: var member2):
}
}
// TODO(johnniwinther): This should be exhaustive.
num exhaustiveMixed(
I<num>
i) => /*
error=non-exhaustive:I<num>(member: double()),
fields={I<num>.member:num,J<num>.member:num},
type=I<num>
*/
switch (i) {
I<num>(:int member) /*space=I<num>(I<num>.member: int (num))*/ => member,
J<num>(:double member) /*space=J<num>(J<num>.member: double (num))*/ =>
member,
};

View file

@ -13,9 +13,30 @@ membersMethod(o) {
*/
switch (o) {
Typedef(:var hashCode) /*space=Never(hashCode: ∅)*/ => hashCode,
Typedef(:var runtimeType) /*space=Never(runtimeType: ∅)*/ => runtimeType,
Typedef(:var toString) /*space=Never(toString: ∅)*/ => toString(),
Typedef(:var noSuchMethod) /*space=Never(noSuchMethod: ∅)*/ => noSuchMethod,
Typedef(
:var runtimeType
) /*
error=unreachable,
space=Never(runtimeType: )
*/
=>
runtimeType,
Typedef(
:var toString
) /*
error=unreachable,
space=Never(toString: )
*/
=>
toString(),
Typedef(
:var noSuchMethod
) /*
error=unreachable,
space=Never(noSuchMethod: )
*/
=>
noSuchMethod,
_ /*space=()*/ => null,
};
}
@ -69,7 +90,6 @@ exhaustiveNoSuchMethod(Typedef o) {
nonExhaustiveRestrictedValue(Typedef o) {
return /*
error=non-exhaustive:Never(hashCode: int())/Never(),
fields={hashCode:int},
type=Never
*/
@ -80,7 +100,6 @@ nonExhaustiveRestrictedValue(Typedef o) {
nonExhaustiveRestrictedType(Typedef o) {
return /*
error=non-exhaustive:Never(noSuchMethod: dynamic Function(Invocation) _)/Never(),
fields={noSuchMethod:dynamic Function(Invocation)},
type=Never
*/
@ -94,14 +113,34 @@ nonExhaustiveRestrictedType(Typedef o) {
unreachableMethod(Typedef o) {
return /*
error=non-exhaustive:Never(hashCode: int(), noSuchMethod: dynamic Function(Invocation) _, runtimeType: Type(), toString: String Function() _)/Never(),
fields={hashCode:int,noSuchMethod:dynamic Function(Invocation),runtimeType:Type,toString:String Function()},
type=Never
*/
switch (o) {
Typedef(:var hashCode) /*space=Never(hashCode: ∅)*/ => hashCode,
Typedef(:var runtimeType) /*space=Never(runtimeType: ∅)*/ => runtimeType,
Typedef(:var toString) /*space=Never(toString: ∅)*/ => toString(),
Typedef(:var noSuchMethod) /*space=Never(noSuchMethod: ∅)*/ => noSuchMethod,
Typedef(
:var runtimeType
) /*
error=unreachable,
space=Never(runtimeType: )
*/
=>
runtimeType,
Typedef(
:var toString
) /*
error=unreachable,
space=Never(toString: )
*/
=>
toString(),
Typedef(
:var noSuchMethod
) /*
error=unreachable,
space=Never(noSuchMethod: )
*/
=>
noSuchMethod,
};
}

View file

@ -1537,7 +1537,11 @@ class ConstantsTransformer extends RemovingTransformer {
required bool hasDefault,
required bool mustBeExhaustive,
required bool isSwitchExpression}) {
StaticType type = exhaustivenessCache.getStaticType(expressionType);
StaticType type = exhaustivenessCache.getStaticType(
// Treat invalid types as empty.
expressionType is InvalidType
? const NeverType.nonNullable()
: expressionType);
List<Space> cases = [];
PatternConverter patternConverter = new PatternConverter(
exhaustivenessCache, staticTypeContext,

View file

@ -504,6 +504,7 @@ exchanging
execute
executor
executors
exhaust
exhausted
exhaustively
exhaustiveness