[_fe_shared_analyzer] Handle remaining patterns in exhaustiveness

This adds explicit handling of the remaining patterns in exhaustiveness.
All these patterns are handled trivially but with TODOs for where we might
want to improve handling.

Asserts are added to flag if a pattern is unhandled.

Change-Id: I82bfccda396316c718685b4d810f259c25771f00
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/285321
Commit-Queue: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Paul Berry <paulberry@google.com>
This commit is contained in:
Johnni Winther 2023-02-27 14:51:50 +00:00 committed by Commit Queue
parent b38b33681b
commit 3c4ca52b2f
9 changed files with 346 additions and 32 deletions

View file

@ -0,0 +1,23 @@
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
class A {}
and(A o1, A o2) {
var a = /*
fields={hashCode:int,runtimeType:Type},
type=A
*/switch (o1) {
A() && var a /*space=??*/=> 0,
_ /*space=()*/=> 1,
};
var b = /*
error=non-exhaustive:A,
fields={hashCode:int,runtimeType:Type},
type=A
*/switch (o1) {
A() && var a /*space=??*/=> 0,
};
}

View file

@ -0,0 +1,51 @@
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
class A {
int field;
A(this.field);
}
simpleCast(o1, o2) {
var a = /*
fields={},
subtypes={Object,Null},
type=Object?
*/switch (o1) {
_ as A /*space=??*/=> 0,
_ /*space=()*/=> 1
};
var b = /*
error=non-exhaustive:Object,
fields={},
subtypes={Object,Null},
type=Object?
*/switch (o2) {
_ as A /*space=??*/=> 0,
};
}
restrictedCase(o1, o2) {
// Cast shouldn't match everything, because even though it doesn't throw,
// it might not match.
var a = /*
fields={},
subtypes={Object,Null},
type=Object?
*/switch (o1) {
A(field: 42) as A /*space=??*/=> 0,
_ /*space=()*/=> 1
};
var b = /*
error=non-exhaustive:Object,
fields={},
subtypes={Object,Null},
type=Object?
*/switch (o2) {
A(field: 42) as A /*space=??*/=> 0,
};
}

View file

@ -0,0 +1,68 @@
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
untypedList(List list) {
var a = /*cfe.
error=non-exhaustive:List<dynamic>,
fields={first:Object?,hashCode:int,isEmpty:bool,isNotEmpty:bool,iterator:Iterator<dynamic>,last:Object?,length:int,reversed:Iterable<dynamic>,runtimeType:Type,single:Object?},
type=List<dynamic>
*//*analyzer.
error=non-exhaustive:List<dynamic>,
fields={first:Object?,hashCode:int,isEmpty:bool,isNotEmpty:bool,iterator:Iterator<dynamic>,last:Object?,length:int,runtimeType:Type},
type=List<dynamic>
*/switch (list) {
[] /*space=??*/=> 0,
[_] /*space=??*/=> 1,
[_, _] /*space=??*/=> 2,
[_, ..., _] /*space=??*/=> 3,
};
}
sealed class A {}
class B extends A {}
class C extends A {}
typedList(List<A> list) {
var a = /*cfe.
error=non-exhaustive:List<A>,
fields={first:A,hashCode:int,isEmpty:bool,isNotEmpty:bool,iterator:Iterator<A>,last:A,length:int,reversed:Iterable<A>,runtimeType:Type,single:A},
type=List<A>
*//*analyzer.
error=non-exhaustive:List<A>,
fields={first:A,hashCode:int,isEmpty:bool,isNotEmpty:bool,iterator:Iterator<A>,last:A,length:int,runtimeType:Type},
type=List<A>
*/switch (list) {
[] /*space=??*/=> 0,
[B b] /*space=??*/=> 1,
[C c] /*space=??*/=> 2,
[_, _] /*space=??*/=> 3,
[B b, ..., _] /*space=??*/=> 4,
[C c, ..., _] /*space=??*/=> 5,
};
}
restWithSubpattern(List list) {
var a = /*cfe.
error=non-exhaustive:List<dynamic>,
fields={first:Object?,hashCode:int,isEmpty:bool,isNotEmpty:bool,iterator:Iterator<dynamic>,last:Object?,length:int,reversed:Iterable<dynamic>,runtimeType:Type,single:Object?},
type=List<dynamic>
*//*analyzer.
error=non-exhaustive:List<dynamic>,
fields={first:Object?,hashCode:int,isEmpty:bool,isNotEmpty:bool,iterator:Iterator<dynamic>,last:Object?,length:int,runtimeType:Type},
type=List<dynamic>
*/switch (list) {
[...var l] /*space=??*/=> l.length,
};
var b = /*cfe.
error=non-exhaustive:List<dynamic>,
fields={first:Object?,hashCode:int,isEmpty:bool,isNotEmpty:bool,iterator:Iterator<dynamic>,last:Object?,length:int,reversed:Iterable<dynamic>,runtimeType:Type,single:Object?},
type=List<dynamic>
*//*analyzer.
error=non-exhaustive:List<dynamic>,
fields={first:Object?,hashCode:int,isEmpty:bool,isNotEmpty:bool,iterator:Iterator<dynamic>,last:Object?,length:int,runtimeType:Type},
type=List<dynamic>
*/switch (list) {
[...List<String> l] /*space=??*/=> l.length,
};
}

View file

@ -0,0 +1,44 @@
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
untypedMap(Map map) {
var a = /*cfe.
error=non-exhaustive:Map<dynamic, dynamic>,
fields={entries:Iterable<MapEntry<dynamic, dynamic>>,hashCode:int,isEmpty:bool,isNotEmpty:bool,keys:Iterable<dynamic>,length:int,runtimeType:Type,values:Iterable<dynamic>},
type=Map<dynamic, dynamic>
*//*analyzer.
error=non-exhaustive:Map<dynamic, dynamic>,
fields={hashCode:int,isEmpty:bool,isNotEmpty:bool,keys:Iterable<dynamic>,length:int,runtimeType:Type,values:Iterable<dynamic>},
type=Map<dynamic, dynamic>
*/switch (map) {
{} /*space=??*/=> 0,
{1: _} /*space=??*/=> 1,
[1: _, 2: _] /*space=??*/=> 2,
[1: _, ..., _] /*space=??*/=> 3,
{...} /*space=??*/=> 4:
};
}
sealed class A {}
class B extends A {}
class C extends A {}
typedList(List<A> list) {
var a = /*cfe.
error=non-exhaustive:List<A>,
fields={first:A,hashCode:int,isEmpty:bool,isNotEmpty:bool,iterator:Iterator<A>,last:A,length:int,reversed:Iterable<A>,runtimeType:Type,single:A},
type=List<A>
*//*analyzer.
error=non-exhaustive:List<A>,
fields={first:A,hashCode:int,isEmpty:bool,isNotEmpty:bool,iterator:Iterator<A>,last:A,length:int,runtimeType:Type},
type=List<A>
*/switch (list) {
[] /*space=??*/=> 0,
[B b] /*space=??*/=> 1,
[C c] /*space=??*/=> 2,
[_, _] /*space=??*/=> 3,
[B b, ... _] /*space=??*/=> 4,
[C c, ... _] /*space=??*/=> 5,
};
}

View file

@ -0,0 +1,51 @@
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
class A {
int field;
A(this.field);
}
simpleAssert(o1, o2) {
var a = /*
fields={},
subtypes={Object,Null},
type=Object?
*/switch (o1) {
_! /*space=??*/=> 0,
_ /*space=()*/=> 1
};
var b = /*
error=non-exhaustive:Object,
fields={},
subtypes={Object,Null},
type=Object?
*/switch (o2) {
_! /*space=??*/=> 0,
};
}
restrictedCase(o1, o2) {
// Null assert shouldn't match everything, because even though it doesn't
// throw, it might not match.
var a = /*
fields={},
subtypes={Object,Null},
type=Object?
*/switch (o1) {
A(field: 42)! /*space=??*/=> 0,
_ /*space=()*/=> 1
};
var b = /*
error=non-exhaustive:Object,
fields={},
subtypes={Object,Null},
type=Object?
*/switch (o2) {
A(field: 42)! /*space=??*/=> 0,
};
}

View file

@ -0,0 +1,43 @@
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
equals(o1, o2) {
var a = /*
fields={},
subtypes={Object,Null},
type=Object?
*/switch (o1) {
== 0 /*space=??*/=> 0,
_ /*space=()*/=> 1
};
var b = /*
error=non-exhaustive:Object,
fields={},
subtypes={Object,Null},
type=Object?
*/switch (o2) {
== 0 /*space=??*/=> 0,
};
}
greaterThan(o1, o2) {
var a = /*
fields={},
subtypes={Object,Null},
type=Object?
*/switch (o1) {
>= 0 /*space=??*/=> 0,
_ /*space=()*/=> 1
};
var b = /*
error=non-exhaustive:Object,
fields={},
subtypes={Object,Null},
type=Object?
*/switch (o2) {
>= 0 /*space=??*/=> 0,
};
}

View file

@ -20,36 +20,30 @@ import 'package:analyzer/src/dart/resolver/variance.dart';
import 'package:analyzer/src/generated/constant.dart';
Space convertConstantValueToSpace(
AnalyzerExhaustivenessCache cache, DartObjectImpl? constantValue) {
if (constantValue != null) {
InstanceState state = constantValue.state;
if (constantValue.isNull) {
return Space.nullSpace;
} else if (state is BoolState && state.value != null) {
return Space(cache.getBoolValueStaticType(state.value!));
} else if (state is RecordState) {
Map<String, Space> fields = {};
for (int index = 0; index < state.positionalFields.length; index++) {
fields['\$${index + 1}'] =
convertConstantValueToSpace(cache, state.positionalFields[index]);
}
for (MapEntry<String, DartObjectImpl> entry
in state.namedFields.entries) {
fields[entry.key] = convertConstantValueToSpace(cache, entry.value);
}
return Space(cache.getStaticType(constantValue.type), fields);
AnalyzerExhaustivenessCache cache, DartObjectImpl constantValue) {
InstanceState state = constantValue.state;
if (constantValue.isNull) {
return Space.nullSpace;
} else if (state is BoolState && state.value != null) {
return Space(cache.getBoolValueStaticType(state.value!));
} else if (state is RecordState) {
Map<String, Space> fields = {};
for (int index = 0; index < state.positionalFields.length; index++) {
fields['\$${index + 1}'] =
convertConstantValueToSpace(cache, state.positionalFields[index]);
}
DartType type = constantValue.type;
if (type is InterfaceType && type.element.kind == ElementKind.ENUM) {
return Space(cache.getEnumElementStaticType(
type.element as EnumElement, constantValue));
for (MapEntry<String, DartObjectImpl> entry in state.namedFields.entries) {
fields[entry.key] = convertConstantValueToSpace(cache, entry.value);
}
return Space(cache.getUniqueStaticType(
type, constantValue, constantValue.toString()));
return Space(cache.getStaticType(constantValue.type), fields);
}
// TODO(johnniwinther): Assert that constant value is available when the
// exhaustiveness checking is complete.
return Space(cache.getUnknownStaticType());
DartType type = constantValue.type;
if (type is InterfaceType && type.element.kind == ElementKind.ENUM) {
return Space(cache.getEnumElementStaticType(
type.element as EnumElement, constantValue));
}
return Space(
cache.getUniqueStaticType(type, constantValue, constantValue.toString()));
}
Space convertPatternToSpace(
@ -150,10 +144,32 @@ Space convertPatternToSpace(
} else if (pattern is ParenthesizedPattern) {
return convertPatternToSpace(cache, pattern.pattern, constantPatternValues,
nonNull: nonNull);
} else if (pattern is NullAssertPattern ||
pattern is CastPattern ||
pattern is RelationalPattern ||
pattern is LogicalAndPattern) {
// These pattern do not add to the exhaustiveness coverage.
// TODO(johnniwinther): Handle `Null` aspect implicitly covered by
// [NullAssertPattern] and `as Null`.
// TODO(johnniwinther): Handle top in [AndPattern] branches.
return Space(cache.getUnknownStaticType());
} else if (pattern is ListPattern || pattern is MapPattern) {
// TODO(johnniwinther): Support list and map patterns. This not only
// requires a new interpretation of [Space] fields that handles the
// relation between concrete lengths, rest patterns with/without
// subpattern, and list/map of arbitrary size and content, but also for the
// runtime to check for lengths < 0.
return Space(cache.getUnknownStaticType());
} else if (pattern is ConstantPattern) {
DartObjectImpl? value = constantPatternValues[pattern];
if (value != null) {
return convertConstantValueToSpace(cache, value);
}
assert(false, "No constant value for $pattern");
return Space(cache.getUnknownStaticType());
}
// TODO(johnniwinther): Handle remaining patterns.
DartObjectImpl? value = constantPatternValues[pattern];
return convertConstantValueToSpace(cache, value);
assert(false, "Unexpected pattern $pattern (${pattern.runtimeType})");
return Space(cache.getUnknownStaticType());
}
class AnalyzerEnumOperations

View file

@ -456,9 +456,25 @@ 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 ||
pattern is InvalidPattern ||
pattern is RelationalPattern ||
pattern is AndPattern) {
// These pattern do not add to the exhaustiveness coverage.
// TODO(johnniwinther): Handle `Null` aspect implicitly covered by
// [NullAssertPattern] and `as Null`.
// TODO(johnniwinther): Handle top in [AndPattern] branches.
return new Space(cache.getUnknownStaticType());
} else if (pattern is ListPattern || pattern is MapPattern) {
// TODO(johnniwinther): Support list and map patterns. This not only
// requires a new interpretation of [Space] fields that handles the
// relation between concrete lengths, rest patterns with/without
// subpattern, and list/map of arbitrary size and content, but also for the
// runtime to check for lengths < 0.
return new Space(cache.getUnknownStaticType());
}
// TODO(johnniwinther): Handle remaining constants.
assert(false, "Unexpected pattern $pattern (${pattern.runtimeType}).");
return new Space(cache.getUnknownStaticType());
}

View file

@ -748,6 +748,7 @@ intermediate
internet
interop
interpolations
interpretation
interrupted
intersections
intersects
@ -822,6 +823,7 @@ leeway
left's
legitimately
len
lengths
lets
letting
levels