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}