mirror of
https://github.com/dart-lang/sdk
synced 2024-09-18 20:51:19 +00:00
[_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:
parent
b38b33681b
commit
3c4ca52b2f
|
@ -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,
|
||||
};
|
||||
}
|
51
pkg/_fe_analyzer_shared/test/exhaustiveness/data/cast.dart
Normal file
51
pkg/_fe_analyzer_shared/test/exhaustiveness/data/cast.dart
Normal 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,
|
||||
};
|
||||
}
|
68
pkg/_fe_analyzer_shared/test/exhaustiveness/data/list.dart
Normal file
68
pkg/_fe_analyzer_shared/test/exhaustiveness/data/list.dart
Normal 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,
|
||||
};
|
||||
}
|
44
pkg/_fe_analyzer_shared/test/exhaustiveness/data/map.dart
Normal file
44
pkg/_fe_analyzer_shared/test/exhaustiveness/data/map.dart
Normal 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,
|
||||
};
|
||||
}
|
|
@ -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,
|
||||
};
|
||||
}
|
|
@ -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,
|
||||
};
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue