11// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
22// for details. All rights reserved. Use of this source code is governed by a
33// 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' ;
55import 'package:exhaustiveness_prototype/static_type.dart' ;
66
77import 'space.dart' ;
@@ -61,62 +61,55 @@ List<Space> _subtractExtractAtType(StaticType type, ExtractSpace left,
6161 // and keep all of the current fields.
6262 if (! type.isSubtypeOf (right.type)) return [Space (type, left.fields)];
6363
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+
6479 // If any pair of fields have no overlapping values, then no overall value
6580 // that matches the left space will also match the right space. So the right
6681 // space doesn't subtract anything and we keep the left space as-is.
6782 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]! )) {
7484 return [Space (type, left.fields)];
7585 }
7686 }
7787
7888 // If all the right's fields strictly cover all of the left's, then the
7989 // right completely subtracts this type and nothing remains.
80- if (_isLeftSubspace (type, fieldNames, left.fields, right.fields )) {
90+ if (_isLeftSubspace (type, fieldNames, leftFields, rightFields )) {
8191 return const [];
8292 }
8393
8494 // 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.
8896
8997 // Walk the fields and see which ones are modified by the right-hand fields.
9098 var fixed = < String , Space > {};
9199 var changedLeft = < String , Space > {};
92100 var changedDifference = < String , Space > {};
93101 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.
106110 } 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;
120113 }
121114 }
122115
@@ -150,23 +143,10 @@ List<Space> _subtractExtractAtType(StaticType type, ExtractSpace left,
150143/// corresponding field in [rightFields] .
151144bool _isLeftSubspace (StaticType leftType, List <String > fieldNames,
152145 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.
156146 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+ }
170150 }
171151
172152 // If we get here, every field covered.
@@ -176,28 +156,13 @@ bool _isLeftSubspace(StaticType leftType, List<String> fieldNames,
176156/// Recursively replaces [left] with a union of its sealed subtypes as long as
177157/// doing so enables it to more precisely match against [right] .
178158List <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];
203168}
0 commit comments