Skip to main content

simplicity/types/
incomplete.rs

1// SPDX-License-Identifier: CC0-1.0
2
3//! "Finalized" Incomplete Type Data
4//!
5//! This structure is essentially the same as [types::Final](super::Final) except
6//! that it has free variables (represented by strings) and supports self-reference.
7//! The purpose of this structure is to provide a useful representation of a type
8//! in error messages.
9//!
10
11use crate::dag::{Dag, DagLike, NoSharing};
12use crate::types::union_bound::PointerLike;
13
14use super::context::BoundRefSharing;
15use super::{Bound, BoundRef, Context};
16use super::{MAX_DISPLAY_DEPTH, MAX_DISPLAY_LENGTH};
17
18use std::fmt;
19use std::sync::Arc;
20
21/// An incomplete type bound for use in error messages.
22#[derive(Clone)]
23pub enum Incomplete {
24    /// A free variable.
25    Free(String),
26    /// A type containing this type.
27    Cycle,
28    /// A sum of two other types
29    Sum(Arc<Incomplete>, Arc<Incomplete>),
30    /// A product of two other types
31    Product(Arc<Incomplete>, Arc<Incomplete>),
32    /// A complete type (including unit)
33    Final(Arc<super::Final>),
34}
35
36impl Incomplete {
37    /// Private helper function for `Incomplete::drop`. See `node::Inner::into_dag` which is
38    /// similar but doesn't require unsafe code.
39    fn into_dag(mut self) -> Dag<Arc<Self>> {
40        use core::{mem, ptr};
41        let ret = match &mut self {
42            Incomplete::Sum(ref mut left, ref mut right)
43            | Incomplete::Product(ref mut left, ref mut right) => {
44                // Because Rust is stupid, we cannot just move 'left' and 'right' out of 'self'.
45                // We get the error "cannot move out of type that implements Drop". This message
46                // dates to before Rust 1.0, before mem::forget was marked safe, and has no
47                // justification that stands up to any scrutiny. There have been mulitple RFCs to
48                // remove it but for some reason they have never gone anywhere. See for example
49                //
50                // https://internals.rust-lang.org/t/destructuring-droppable-structs/20993/41
51                //
52                // Anyway, instead we have to use unsafe code here to do this obviously-safe
53                // operation in an overcomplicated and hard-to-review way.
54                unsafe {
55                    // SAFETY we are calling `ptr::read` on valid pointers (they come directly
56                    // from references, which are always valid), and we will mem::forget their old
57                    // locations before any early returns or panics.
58
59                    let left = ptr::read(left);
60                    let right = ptr::read(right);
61                    Dag::Binary(left, right)
62                }
63            }
64            Incomplete::Cycle => Dag::Nullary,
65            Incomplete::Free(s) => {
66                // SAFETY: see above. We are manually dropping `s` here, which we have to do
67                // by reading it out of &mut self, because Rust is stupid.
68                unsafe {
69                    ptr::read(s);
70                }
71                Dag::Nullary
72            }
73            Incomplete::Final(fin) => {
74                // SAFETY: see above
75                unsafe {
76                    ptr::read(fin);
77                }
78                Dag::Nullary
79            }
80        };
81        // Because `Incomplete::drop` calls this method, we cannot allow `self` to be dropped
82        // under any circumstances, or else we will infinitely recurse and stack-overflow. (This
83        // is why we had to do ptr::read in every branch above).
84        mem::forget(self);
85        ret
86    }
87}
88
89impl DagLike for &'_ Incomplete {
90    type Node = Incomplete;
91    fn data(&self) -> &Incomplete {
92        self
93    }
94    fn as_dag_node(&self) -> Dag<Self> {
95        match *self {
96            Incomplete::Free(_) | Incomplete::Cycle | Incomplete::Final(_) => Dag::Nullary,
97            Incomplete::Sum(ref left, ref right) | Incomplete::Product(ref left, ref right) => {
98                Dag::Binary(left, right)
99            }
100        }
101    }
102}
103
104impl fmt::Debug for Incomplete {
105    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
106        fmt::Display::fmt(self, f)
107    }
108}
109
110impl fmt::Display for Incomplete {
111    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
112        let mut skip_next = false;
113        for data in self.verbose_pre_order_iter::<NoSharing>(Some(MAX_DISPLAY_DEPTH)) {
114            if data.index > MAX_DISPLAY_LENGTH {
115                write!(f, "... [truncated type after {} nodes]", MAX_DISPLAY_LENGTH)?;
116                return Ok(());
117            }
118            if data.depth == MAX_DISPLAY_DEPTH {
119                if data.n_children_yielded == 0 {
120                    f.write_str("...")?;
121                }
122                continue;
123            }
124
125            if skip_next {
126                skip_next = false;
127                continue;
128            }
129
130            match (data.node, data.n_children_yielded) {
131                (Incomplete::Free(ref s), _) => f.write_str(s)?,
132                (Incomplete::Cycle, _) => f.write_str("<self-reference>")?,
133                // special-case 1 + A as A?
134                (Incomplete::Sum(ref left, _), 0) if left.is_unit() => {
135                    skip_next = true;
136                }
137                (Incomplete::Sum(ref left, _), 1) if left.is_unit() => {}
138                (Incomplete::Sum(ref left, _), 2) if left.is_unit() => {
139                    f.write_str("?")?;
140                }
141                // other sums and products
142                (Incomplete::Sum(..), 0) | (Incomplete::Product(..), 0) => {
143                    if data.index > 0 {
144                        f.write_str("(")?;
145                    }
146                }
147                (Incomplete::Sum(..), 2) | (Incomplete::Product(..), 2) => {
148                    if data.index > 0 {
149                        f.write_str(")")?;
150                    }
151                }
152                (Incomplete::Sum(..), _) => f.write_str(" + ")?,
153                (Incomplete::Product(..), _) => f.write_str(" × ")?,
154                (Incomplete::Final(ref fnl), _) => fnl.fmt(f)?,
155            }
156        }
157        Ok(())
158    }
159}
160
161impl Drop for Incomplete {
162    fn drop(&mut self) {
163        // Note: this is basically identical to the drop impl for node::Node.
164        fn push_children(stack: &mut Vec<Arc<Incomplete>>, inner: Incomplete) {
165            use crate::dag::Dag;
166            match inner.into_dag() {
167                Dag::Nullary => {}
168                Dag::Unary(child) => stack.push(child),
169                Dag::Binary(left, right) => {
170                    stack.push(left);
171                    stack.push(right);
172                }
173            }
174        }
175
176        let mut stack = Vec::new();
177        push_children(&mut stack, std::mem::replace(self, Incomplete::Cycle));
178        while let Some(child) = stack.pop() {
179            if let Some(mut child) = Arc::into_inner(child) {
180                push_children(&mut stack, std::mem::replace(&mut child, Incomplete::Cycle));
181            }
182        }
183    }
184}
185
186impl Incomplete {
187    /// Whether this "incomplete bound" is the unit type.
188    pub fn is_unit(&self) -> bool {
189        if let Incomplete::Final(ref fnl) = self {
190            fnl.is_unit()
191        } else {
192            false
193        }
194    }
195
196    /// Does the occurs-check on a type bound.
197    ///
198    /// Returns None on success, and a Some(Incomplete) indicating the occurs-check
199    /// failure if there is a cyclic reference.
200    pub(super) fn occurs_check<'brand>(
201        ctx: &Context<'brand>,
202        bound_ref: BoundRef<'brand>,
203    ) -> Option<Arc<Self>> {
204        use std::collections::HashSet;
205
206        use super::context::OccursCheckId;
207
208        /// Helper type for the occurs-check.
209        enum OccursCheckStack<'brand> {
210            Iterate(BoundRef<'brand>),
211            Complete(OccursCheckId<'brand>),
212        }
213
214        // First, do occurs-check to ensure that we have no infinitely sized types.
215        let mut stack = vec![OccursCheckStack::Iterate(bound_ref)];
216        let mut in_progress = HashSet::new();
217        let mut completed = HashSet::new();
218        while let Some(top) = stack.pop() {
219            let bound = match top {
220                OccursCheckStack::Complete(id) => {
221                    in_progress.remove(&id);
222                    completed.insert(id);
223                    continue;
224                }
225                OccursCheckStack::Iterate(b) => b,
226            };
227
228            let id = bound.occurs_check_id();
229            if completed.contains(&id) {
230                // Once we have iterated through a type, we don't need to check it again.
231                // Without this shortcut the occurs-check would take exponential time.
232                continue;
233            }
234            if !in_progress.insert(id) {
235                // FIXME unwind the stack to somehow provide a more useful trace of the occurs-check failure
236                return Some(Arc::new(Self::Cycle));
237            }
238
239            stack.push(OccursCheckStack::Complete(id));
240            if let Some((_, child)) = (ctx, bound.shallow_clone()).right_child() {
241                stack.push(OccursCheckStack::Iterate(child));
242            }
243            if let Some((_, child)) = (ctx, bound).left_child() {
244                stack.push(OccursCheckStack::Iterate(child));
245            }
246        }
247
248        None
249    }
250
251    pub(super) fn from_bound_ref<'brand>(
252        ctx: &Context<'brand>,
253        bound_ref: BoundRef<'brand>,
254    ) -> Arc<Self> {
255        if let Some(err) = Self::occurs_check(ctx, bound_ref.shallow_clone()) {
256            return err;
257        }
258
259        // Now that we know our bound has finite size, we can safely use a
260        // post-order iterator on it.
261        let mut finalized = vec![];
262        for data in (ctx, bound_ref).post_order_iter::<BoundRefSharing<'_>>() {
263            let bound_get = data.node.0.get(&data.node.1);
264            let final_data = match bound_get {
265                Bound::Free(s) => Incomplete::Free(s),
266                Bound::Complete(ref arc) => Incomplete::Final(Arc::clone(arc)),
267                Bound::Sum(..) => Incomplete::Sum(
268                    Arc::clone(&finalized[data.left_index.unwrap()]),
269                    Arc::clone(&finalized[data.right_index.unwrap()]),
270                ),
271                Bound::Product(..) => Incomplete::Product(
272                    Arc::clone(&finalized[data.left_index.unwrap()]),
273                    Arc::clone(&finalized[data.right_index.unwrap()]),
274                ),
275            };
276
277            finalized.push(Arc::new(final_data));
278        }
279        finalized.pop().unwrap()
280    }
281}