shape/case_enum/one.rs
1use super::deduplicate_shape::extend_or_insert_shape;
2use crate::location::Location;
3use crate::{Shape, ShapeCase};
4use indexmap::IndexSet;
5
6/// The contents of a [`ShapeCase::One`]. Constructing this requires simplifying the shapes it
7/// contains so there aren't unnecessary duplicates left over and so hashes are consistent.
8///
9/// This intentionally can't be constructed directly because it prevents accidental unsimplified
10/// [`ShapeCase::One`]s. Construct this via [`Shape::one`] instead.
11#[derive(Clone, Debug, Eq)]
12pub struct One {
13 shapes: Vec<Shape>,
14}
15
16impl One {
17 pub fn is_empty(&self) -> bool {
18 self.shapes.is_empty()
19 }
20
21 pub fn iter(&self) -> impl Iterator<Item = &Shape> {
22 self.shapes.iter()
23 }
24}
25
26impl<'a> IntoIterator for &'a One {
27 type Item = &'a Shape;
28 type IntoIter = std::slice::Iter<'a, Shape>;
29
30 fn into_iter(self) -> Self::IntoIter {
31 self.shapes.iter()
32 }
33}
34
35impl PartialEq<One> for One {
36 fn eq(&self, other: &One) -> bool {
37 self.shapes.iter().collect::<IndexSet<_>>() == other.shapes.iter().collect::<IndexSet<_>>()
38 }
39}
40
41impl Shape {
42 pub fn never(locations: impl IntoIterator<Item = Location>) -> Self {
43 Shape::new(ShapeCase::One(One { shapes: vec![] }), locations)
44 }
45
46 #[must_use]
47 pub fn is_never(&self) -> bool {
48 matches!(self.case(), ShapeCase::One(One { shapes }) if shapes.is_empty())
49 }
50}
51
52/// Builds a [`Shape`] with [`ShapeCase::One`] _unless_ the shapes can simplify to a single
53/// [`ShapeCase`].
54pub(crate) fn one(shapes: impl Iterator<Item = Shape>, mut locations: Vec<Location>) -> Shape {
55 let mut shapes = shapes.peekable();
56 if shapes.peek().is_none() {
57 // The empty One<> union represents an unsatisfiable shape
58 // analogous to TypeScript's `never` type.
59 return Shape::never(locations);
60 }
61
62 let mut new_shapes = Vec::new();
63
64 // Flatten any nested `One` shapes
65 for shape in shapes {
66 if let ShapeCase::One(One { shapes }) = shape.case.as_ref() {
67 locations.extend(shape.locations);
68 for inner_shape in shapes {
69 extend_or_insert_shape(&mut new_shapes, inner_shape.clone());
70 }
71 } else {
72 extend_or_insert_shape(&mut new_shapes, shape);
73 }
74 }
75
76 // The presence of the general Unknown shape subsumes all
77 // other union member shapes.
78 if let Some(unknown) = new_shapes
79 .iter()
80 .find(|shape| shape.case() == ShapeCase::Unknown)
81 {
82 return unknown.clone();
83 }
84
85 // The presence of the general Bool shape subsumes all
86 // specific literal boolean shapes.
87 let boolean_shape = ShapeCase::Bool(None);
88 if new_shapes.iter().any(|shape| shape.case() == boolean_shape) {
89 new_shapes.retain(|shape| !matches!(shape.case.as_ref(), ShapeCase::Bool(Some(_))));
90 } else {
91 // Also, if new_shapes contains both true and false, then we
92 // can remove them and simplify the union by adding
93 // boolean_shape.
94 //
95 // Theoretically this same logic could apply to Int or
96 // String, but that would require somehow forming a union of
97 // every possible integer or string value.
98 let true_shape = ShapeCase::Bool(Some(true));
99 let false_shape = ShapeCase::Bool(Some(false));
100
101 let mut true_shapes = new_shapes
102 .iter()
103 .filter(|shape| shape.case() == true_shape)
104 .peekable();
105 let mut false_shapes = new_shapes
106 .iter()
107 .filter(|shape| shape.case() == false_shape)
108 .peekable();
109
110 if true_shapes.peek().is_some() && false_shapes.peek().is_some() {
111 new_shapes.push(Shape::bool(
112 true_shapes
113 .chain(false_shapes)
114 .flat_map(|shape| &shape.locations)
115 .cloned(),
116 ));
117 new_shapes.retain(|shape| !matches!(shape.case(), ShapeCase::Bool(Some(_))));
118 }
119 }
120
121 // The presence of the general String shape subsumes all
122 // specific literal string shapes.
123 if new_shapes
124 .iter()
125 .any(|shape| shape.case() == ShapeCase::String(None))
126 {
127 new_shapes.retain(|shape| !matches!(shape.case.as_ref(), ShapeCase::String(Some(_))));
128 }
129
130 if new_shapes
131 .iter()
132 .any(|shape| shape.case() == ShapeCase::Float)
133 {
134 // The presence of the general Float shape subsumes all
135 // general and literal Int shapes.
136 new_shapes.retain(|shape| !matches!(shape.case.as_ref(), ShapeCase::Int(_)));
137 } else if new_shapes
138 .iter()
139 .any(|shape| shape.case() == ShapeCase::Int(None))
140 {
141 // The presence of the general Int shape subsumes all
142 // specific literal int shapes.
143 new_shapes.retain(|shape| !matches!(shape.case.as_ref(), ShapeCase::Int(Some(_))));
144 }
145
146 // It's tempting to try to simplify new_shapes further by
147 // removing any shape that satisfies another shape, since that's
148 // one interpretation of the cases above: specific primitive
149 // value shapes like 123 satisfy general primitive shapes like
150 // Int, and therefore add nothing to the union if the general
151 // shape is present.
152 //
153 // However, in the cases above, the specific value shapes happen
154 // to be fully subsumed by the general shape, adding no further
155 // information to the general union. When we're dealing with
156 // arrays or objects, a shape can satisfy another shape and
157 // still add information to the union, so information would be
158 // lost if we removed such shapes here.
159 //
160 // For example, since { a: Int } is a more general shape than
161 // (is satisfied by) { a: Int, b: String }, including { a: Int,
162 // b: String } in a union with { a: Int } technically does not
163 // change the set of values represented by the union, but you
164 // might still like to know that the b field's shape across the
165 // union is String | None, rather than just None, as it would be
166 // if we removed the more specific shape from new_shapes.
167 //
168 // As a side note, detecting satisfaction among all pairs of
169 // members of the union is a quadratic operation in the size of
170 // the union, so it's nice we don't need to do all that work.
171
172 // A union of one shape is equivalent to that shape by itself.
173 if let (Some(only_shape), 1) = (new_shapes.first(), new_shapes.len()) {
174 return only_shape.clone();
175 }
176
177 Shape::new(ShapeCase::One(One { shapes: new_shapes }), locations)
178}