blob: a6a8c27492bde431bdf21bde31bb26e3818e3bba [file] [log] [blame]
// Copyright (c) 2022, 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.
import 'package:_fe_analyzer_shared/src/exhaustiveness/exhaustive.dart';
import 'package:_fe_analyzer_shared/src/exhaustiveness/key.dart';
import 'package:_fe_analyzer_shared/src/exhaustiveness/path.dart';
import 'package:_fe_analyzer_shared/src/exhaustiveness/space.dart';
import 'package:_fe_analyzer_shared/src/exhaustiveness/static_type.dart';
import 'package:test/test.dart';
/// Test that [spaces] is exhaustive over [value].
void expectExhaustive(
ObjectFieldLookup fieldLookup, Space value, List<Space> spaces) {
_expectExhaustive(fieldLookup, value, spaces, true);
}
/// Test that [cases] are exhaustive over [type] if and only if all cases are
/// included and that all subsets of the cases are not exhaustive.
void expectExhaustiveOnlyAll(
ObjectFieldLookup fieldLookup, StaticType type, List<Object> cases) {
_testCases(fieldLookup, type, cases, true);
}
/// Test that [cases] are not exhaustive over [type]. Also test that omitting
/// each case is still not exhaustive.
void expectNeverExhaustive(
ObjectFieldLookup fieldLookup, StaticType type, List<Object> cases) {
_testCases(fieldLookup, type, cases, false);
}
/// Test that [spaces] is not exhaustive over [value].
void expectNotExhaustive(
ObjectFieldLookup fieldLookup, Space value, List<Space> spaces) {
_expectExhaustive(fieldLookup, value, spaces, false);
}
Map<Key, Space> fieldsToSpace(Map<String, Object> fields, Path path,
{required bool asRecordNames}) =>
fields.map((String name, Object value) {
Key key = asRecordNames ? new RecordNameKey(name) : new NameKey(name);
return MapEntry(key, parseSpace(value, path.add(key)));
});
Space parseSpace(Object object, [Path path = const Path.root()]) {
if (object is Space) return object;
if (object == '∅') return Space(path, StaticType.neverType);
if (object is StaticType) return Space(path, object);
if (object is List<Object>) {
Space? spaces;
for (Object element in object) {
if (spaces == null) {
spaces = parseSpace(element, path);
} else {
spaces = spaces.union(parseSpace(element, path));
}
}
return spaces ?? new Space.empty(path);
}
throw ArgumentError('Invalid space $object');
}
/// Parse a list of spaces using [parseSpace].
List<Space> parseSpaces(List<Object> objects) =>
objects.map(parseSpace).toList();
/// Make a [Space] with [type] and [fields].
Space ty(StaticType type, Map<String, Object> fields,
[Path path = const Path.root()]) =>
Space(path, type,
fields: fieldsToSpace(fields, path, asRecordNames: type.isRecord));
void _checkExhaustive(ObjectFieldLookup fieldLookup, Space value,
List<Space> spaces, bool expectation) {
var actual = isExhaustive(fieldLookup, value, spaces);
if (expectation != actual) {
if (expectation) {
fail('Expected $spaces to cover $value but did not.');
} else {
fail('Expected $spaces to not cover $value but did.');
}
}
}
void _expectExhaustive(ObjectFieldLookup fieldLookup, Space value,
List<Space> spaces, bool expectation) {
test(
'$value - ${spaces.join(' - ')} ${expectation ? 'is' : 'is not'} '
'exhaustive', () {
_checkExhaustive(fieldLookup, value, spaces, expectation);
});
}
/// Test that [cases] are not exhaustive over [type].
void _testCases(ObjectFieldLookup fieldLookup, StaticType type,
List<Object> cases, bool expectation) {
var valueSpace = Space(const Path.root(), type);
var spaces = parseSpaces(cases);
test('$type with all cases', () {
_checkExhaustive(fieldLookup, valueSpace, spaces, expectation);
});
// With any single case removed, should also not be exhaustive.
for (var i = 0; i < spaces.length; i++) {
var filtered = spaces.toList();
filtered.removeAt(i);
test('$type without case ${spaces[i]}', () {
_checkExhaustive(fieldLookup, valueSpace, filtered, false);
});
}
}