1
1
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
2
2
// for details. All rights reserved. Use of this source code is governed by a
3
3
// BSD-style license that can be found in the LICENSE file.
4
- import 'package:exhaustiveness_prototype/intersect .dart' ;
4
+ import 'package:exhaustiveness_prototype/intersect_empty .dart' ;
5
5
import 'package:exhaustiveness_prototype/static_type.dart' ;
6
6
7
7
import 'space.dart' ;
@@ -61,62 +61,55 @@ List<Space> _subtractExtractAtType(StaticType type, ExtractSpace left,
61
61
// and keep all of the current fields.
62
62
if (! type.isSubtypeOf (right.type)) return [Space (type, left.fields)];
63
63
64
+ // Infer any fields that appear in one space and not the other.
65
+ var leftFields = < String , Space > {};
66
+ var rightFields = < String , Space > {};
67
+ for (var name in fieldNames) {
68
+ // If the right space matches on a field that the left doesn't have, infer
69
+ // it from the static type of the field. That contains the same set of
70
+ // values as having no field at all.
71
+ leftFields[name] = left.fields[name] ?? Space (type.fields[name]! );
72
+
73
+ // If the left matches on a field that the right doesn't have, infer top
74
+ // for the right field since the right will accept any of left's values for
75
+ // that field.
76
+ rightFields[name] = right.fields[name] ?? Space .top;
77
+ }
78
+
64
79
// If any pair of fields have no overlapping values, then no overall value
65
80
// that matches the left space will also match the right space. So the right
66
81
// space doesn't subtract anything and we keep the left space as-is.
67
82
for (var name in fieldNames) {
68
- var leftField = left.fields[name];
69
- var rightField = right.fields[name];
70
-
71
- if (leftField != null &&
72
- rightField != null &&
73
- intersect (leftField, rightField) == Space .empty) {
83
+ if (intersectEmpty (leftFields[name]! , rightFields[name]! )) {
74
84
return [Space (type, left.fields)];
75
85
}
76
86
}
77
87
78
88
// If all the right's fields strictly cover all of the left's, then the
79
89
// right completely subtracts this type and nothing remains.
80
- if (_isLeftSubspace (type, fieldNames, left.fields, right.fields )) {
90
+ if (_isLeftSubspace (type, fieldNames, leftFields, rightFields )) {
81
91
return const [];
82
92
}
83
93
84
94
// The right side is a supertype but its fields don't totally cover, so
85
- // handle each of them individually. This is equation 8.3 but more complex
86
- // to handle the fact that records may choose to match arbitrary subsets of
87
- // the available fields.
95
+ // handle each of them individually.
88
96
89
97
// Walk the fields and see which ones are modified by the right-hand fields.
90
98
var fixed = < String , Space > {};
91
99
var changedLeft = < String , Space > {};
92
100
var changedDifference = < String , Space > {};
93
101
for (var name in fieldNames) {
94
- var leftField = left.fields[name];
95
- var rightField = right.fields[name];
96
-
97
- // To subtract a right field, we need a baseline left field to compare
98
- // it to. If there isn't one, infer a left field that matches all values
99
- // of its type.
100
- leftField ?? = Space (type.fields[name]! );
101
-
102
- if (rightField == null ) {
103
- // If we have a left field that constrains but aren't subtracting
104
- // anything from it, just keep the constraint.
105
- fixed[name] = leftField;
102
+ var difference = subtract (leftFields[name]! , rightFields[name]! );
103
+ if (difference == Space .empty) {
104
+ // The right field accepts all the values that the left field accepts, so
105
+ // keep the left field as it is.
106
+ fixed[name] = leftFields[name]! ;
107
+ } else if (difference.isTop) {
108
+ // If the resulting field matches everything, simply discard it since
109
+ // it's equivalent to omitting the field.
106
110
} else {
107
- var difference = subtract (leftField, rightField);
108
- if (difference == Space .empty) {
109
- // TODO: This comment isn't right.
110
- // The left and right fields don't overlap, so there's no need to
111
- // consider this empty case since it will match nothing.
112
- fixed[name] = leftField;
113
- } else if (difference.isTop) {
114
- // Don't bother keeping a field that matches everything.
115
- // TODO: When does this get reached?
116
- } else {
117
- changedLeft[name] = leftField;
118
- changedDifference[name] = difference;
119
- }
111
+ changedLeft[name] = leftFields[name]! ;
112
+ changedDifference[name] = difference;
120
113
}
121
114
}
122
115
@@ -150,23 +143,10 @@ List<Space> _subtractExtractAtType(StaticType type, ExtractSpace left,
150
143
/// corresponding field in [rightFields] .
151
144
bool _isLeftSubspace (StaticType leftType, List <String > fieldNames,
152
145
Map <String , Space > leftFields, Map <String , Space > rightFields) {
153
- // 8.1: If this type is a subtype of the right type, and all of the fields
154
- // on the left are covered by the fields on the right, then the right hand
155
- // space covers the left space entirely.
156
146
for (var name in fieldNames) {
157
- var leftField = leftFields[name];
158
- var rightField = rightFields[name];
159
-
160
- // If there is no right field, it definitely covers all values matched
161
- // by the left field.
162
- if (rightField == null ) continue ;
163
-
164
- // If there is no left field, infer it from the type on the left. This is
165
- // safe to do because we know the left type is a subtype of the right and
166
- // thus the field will exist.
167
- leftField ?? = Space (leftType.fields[name]! );
168
-
169
- if (subtract (leftField, rightField) != Space .empty) return false ;
147
+ if (subtract (leftFields[name]! , rightFields[name]! ) != Space .empty) {
148
+ return false ;
149
+ }
170
150
}
171
151
172
152
// If we get here, every field covered.
@@ -176,28 +156,13 @@ bool _isLeftSubspace(StaticType leftType, List<String> fieldNames,
176
156
/// Recursively replaces [left] with a union of its sealed subtypes as long as
177
157
/// doing so enables it to more precisely match against [right] .
178
158
List <StaticType > expandType (StaticType left, StaticType right) {
179
- // If we've reached the type, stop.
180
- if (left.isSubtypeOf (right)) return [left];
181
-
182
- if (! left.isSealed) return [left];
183
-
184
- // If expanding left into its subtypes won't get closer to right, then don't.
185
- if (! right.isSubtypeOf (left)) return [left];
186
-
187
- // If we remove the restriction that a type can only be a direct subtype of
188
- // one sealed supertype, then the above check needs to be smarter. Consider:
189
- // (A)
190
- // / \
191
- // (B) (C)
192
- // / \ / \
193
- // D E F
194
- //
195
- // Here, `expandType(B, F)` should return [D, E, F] because even though B and
196
- // F aren't supertypes of each other, they have a shared supertype in a sealed
197
- // family.
198
-
199
- // Expand [left] recursively to reach down to right.
200
- return {
201
- for (var subtype in left.subtypes) ...expandType (subtype, right),
202
- }.toList ();
159
+ // If [left] is a sealed supertype and [right] is in its subtype hierarchy,
160
+ // then expand out the subtypes (recursively) to more precisely match [right].
161
+ if (left.isSealed && left != right && right.isSubtypeOf (left)) {
162
+ return {
163
+ for (var subtype in left.subtypes) ...expandType (subtype, right),
164
+ }.toList ();
165
+ }
166
+
167
+ return [left];
203
168
}
0 commit comments