Skip to main content

simplicity/types/
arrow.rs

1// SPDX-License-Identifier: CC0-1.0
2
3//! Types Arrows
4//!
5//! Every Simplicity expression has two types associated with it: a source and
6//! a target. See the `types` module for more information. We refer to this
7//! pair of types as an "arrow", since the expression can be thought of as
8//! mapping a value of the source type to a value of the target type.
9//!
10//! This module defines the specific arrows associated with each kind of node.
11//!
12//! See the `types` module above this one for more information.
13
14use std::fmt;
15use std::sync::Arc;
16
17use crate::jet::Jet;
18use crate::node::{DisconnectConstructible, NoDisconnect};
19use crate::types::{Context, Error, Final, Type};
20use crate::value::Word;
21
22use super::variable::new_name;
23
24/// A container for an expression's source and target types, whether or not
25/// these types are complete.
26#[derive(Debug)]
27pub struct Arrow<'brand> {
28    /// The source type
29    pub source: Type<'brand>,
30    /// The target type
31    pub target: Type<'brand>,
32    /// Type inference context for both types.
33    pub inference_context: Context<'brand>,
34}
35
36// Having `Clone` makes it easier to derive Clone on structures
37// that contain Arrow, even though it is potentially confusing
38// to use `.clone` to mean a shallow clone.
39impl Clone for Arrow<'_> {
40    fn clone(&self) -> Self {
41        self.shallow_clone()
42    }
43}
44
45impl fmt::Display for Arrow<'_> {
46    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47        write!(f, "{} → {}", self.source, self.target)
48    }
49}
50
51/// A container for the type data associated with an expression's source and
52/// target types, if both types are complete.
53#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
54pub struct FinalArrow {
55    /// The source type
56    pub source: Arc<Final>,
57    /// The target type
58    pub target: Arc<Final>,
59}
60
61impl fmt::Display for FinalArrow {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        write!(f, "{} → {}", self.source, self.target)
64    }
65}
66
67impl FinalArrow {
68    /// Same as [`Self::clone`] but named to make it clearer that this is cheap
69    pub fn shallow_clone(&self) -> Self {
70        Self {
71            source: Arc::clone(&self.source),
72            target: Arc::clone(&self.target),
73        }
74    }
75}
76
77impl<'brand> Arrow<'brand> {
78    /// Finalize the source and target types in the arrow
79    pub fn finalize(&self) -> Result<FinalArrow, Error> {
80        Ok(FinalArrow {
81            source: self.source.finalize()?,
82            target: self.target.finalize()?,
83        })
84    }
85
86    /// Same as [`Self::clone`] but named to make it clearer that this is cheap
87    pub fn shallow_clone(&self) -> Self {
88        Self {
89            source: self.source.shallow_clone(),
90            target: self.target.shallow_clone(),
91            inference_context: self.inference_context.shallow_clone(),
92        }
93    }
94
95    /// Create a unification arrow for a fresh `case` combinator
96    ///
97    /// Either child may be `None`, in which case the combinator is assumed to be
98    /// an assertion, which for type-inference purposes means there are no bounds
99    /// on the missing child.
100    ///
101    /// # Panics
102    ///
103    /// If neither child is provided, this function will panic.
104    fn for_case(lchild_arrow: Option<&Self>, rchild_arrow: Option<&Self>) -> Result<Self, Error> {
105        if let (Some(left), Some(right)) = (lchild_arrow, rchild_arrow) {
106            left.inference_context.check_eq(&right.inference_context)?;
107        }
108
109        let ctx = match (lchild_arrow, rchild_arrow) {
110            (Some(left), _) => left.inference_context.shallow_clone(),
111            (_, Some(right)) => right.inference_context.shallow_clone(),
112            (None, None) => panic!("called `for_case` with no children"),
113        };
114
115        let a = Type::free(&ctx, new_name("case_a_"));
116        let b = Type::free(&ctx, new_name("case_b_"));
117        let c = Type::free(&ctx, new_name("case_c_"));
118
119        let sum_a_b = Type::sum(&ctx, a.shallow_clone(), b.shallow_clone());
120        let prod_sum_a_b_c = Type::product(&ctx, sum_a_b, c.shallow_clone());
121
122        let target = Type::free(&ctx, String::new());
123        if let Some(lchild_arrow) = lchild_arrow {
124            ctx.bind_product(
125                &lchild_arrow.source,
126                &a,
127                &c,
128                "case combinator: left source = A × C",
129            )?;
130            ctx.unify(&target, &lchild_arrow.target, "").unwrap();
131        }
132        if let Some(rchild_arrow) = rchild_arrow {
133            ctx.bind_product(
134                &rchild_arrow.source,
135                &b,
136                &c,
137                "case combinator: left source = B × C",
138            )?;
139            ctx.unify(
140                &target,
141                &rchild_arrow.target,
142                "case combinator: left target = right target",
143            )?;
144        }
145
146        Ok(Self {
147            source: prod_sum_a_b_c,
148            target,
149            inference_context: ctx,
150        })
151    }
152
153    /// Helper function to combine code for the two `DisconnectConstructible` impls for [`Arrow`].
154    fn for_disconnect(lchild_arrow: &Self, rchild_arrow: &Self) -> Result<Self, Error> {
155        lchild_arrow
156            .inference_context
157            .check_eq(&rchild_arrow.inference_context)?;
158
159        let ctx = lchild_arrow.inference_context();
160        let a = Type::free(ctx, new_name("disconnect_a_"));
161        let b = Type::free(ctx, new_name("disconnect_b_"));
162        let c = rchild_arrow.source.shallow_clone();
163        let d = rchild_arrow.target.shallow_clone();
164
165        ctx.bind_product(
166            &lchild_arrow.source,
167            &Type::two_two_n(ctx, 8),
168            &a,
169            "disconnect combinator: left source = 2^256 × A",
170        )?;
171        ctx.bind_product(
172            &lchild_arrow.target,
173            &b,
174            &c,
175            "disconnect combinator: left target = B × C",
176        )?;
177
178        let prod_b_d = Type::product(ctx, b, d);
179
180        Ok(Self {
181            source: a,
182            target: prod_b_d,
183            inference_context: lchild_arrow.inference_context.shallow_clone(),
184        })
185    }
186
187    /// A type arrow where both source and target types are new free variables.
188    ///
189    /// This should be used for hidden nodes or other nodes where nothing about
190    /// the node is known.
191    pub fn hidden(inference_context: &Context<'brand>) -> Self {
192        Self {
193            source: Type::free(inference_context, new_name("hidden_src_")),
194            target: Type::free(inference_context, new_name("hidden_tgt_")),
195            inference_context: inference_context.shallow_clone(),
196        }
197    }
198
199    /// A type arrow where both source and target types fixed to the unit type.
200    pub fn program(inference_context: &Context<'brand>) -> Self {
201        Self {
202            source: Type::unit(inference_context),
203            target: Type::unit(inference_context),
204            inference_context: inference_context.shallow_clone(),
205        }
206    }
207
208    pub fn iden(inference_context: &Context<'brand>) -> Self {
209        // Throughout this module, when two types are the same, we reuse a
210        // pointer to them rather than creating distinct types and unifying
211        // them. This theoretically could lead to more confusing errors for
212        // the user during type inference, but in practice type inference
213        // is completely opaque and there's no harm in making it moreso.
214        let new = Type::free(inference_context, new_name("iden_src_"));
215        Self {
216            source: new.shallow_clone(),
217            target: new,
218            inference_context: inference_context.shallow_clone(),
219        }
220    }
221
222    pub fn unit(inference_context: &Context<'brand>) -> Self {
223        Self {
224            source: Type::free(inference_context, new_name("unit_src_")),
225            target: Type::unit(inference_context),
226            inference_context: inference_context.shallow_clone(),
227        }
228    }
229
230    pub fn injl(child: &Self) -> Self {
231        Self {
232            source: child.source.shallow_clone(),
233            target: Type::sum(
234                &child.inference_context,
235                child.target.shallow_clone(),
236                Type::free(&child.inference_context, new_name("injl_tgt_")),
237            ),
238            inference_context: child.inference_context.shallow_clone(),
239        }
240    }
241
242    pub fn injr(child: &Self) -> Self {
243        Self {
244            source: child.source.shallow_clone(),
245            target: Type::sum(
246                &child.inference_context,
247                Type::free(&child.inference_context, new_name("injr_tgt_")),
248                child.target.shallow_clone(),
249            ),
250            inference_context: child.inference_context.shallow_clone(),
251        }
252    }
253
254    pub fn take(child: &Self) -> Self {
255        Self {
256            source: Type::product(
257                &child.inference_context,
258                child.source.shallow_clone(),
259                Type::free(&child.inference_context, new_name("take_src_")),
260            ),
261            target: child.target.shallow_clone(),
262            inference_context: child.inference_context.shallow_clone(),
263        }
264    }
265
266    pub fn drop_(child: &Self) -> Self {
267        Self {
268            source: Type::product(
269                &child.inference_context,
270                Type::free(&child.inference_context, new_name("drop_src_")),
271                child.source.shallow_clone(),
272            ),
273            target: child.target.shallow_clone(),
274            inference_context: child.inference_context.shallow_clone(),
275        }
276    }
277
278    pub fn comp(left: &Self, right: &Self) -> Result<Self, Error> {
279        left.inference_context.check_eq(&right.inference_context)?;
280        left.inference_context.unify(
281            &left.target,
282            &right.source,
283            "comp combinator: left target = right source",
284        )?;
285        Ok(Self {
286            source: left.source.shallow_clone(),
287            target: right.target.shallow_clone(),
288            inference_context: left.inference_context.shallow_clone(),
289        })
290    }
291
292    pub fn case(left: &Self, right: &Self) -> Result<Self, Error> {
293        Self::for_case(Some(left), Some(right))
294    }
295
296    pub fn assertl(left: &Self) -> Result<Self, Error> {
297        Self::for_case(Some(left), None)
298    }
299
300    pub fn assertr(right: &Self) -> Result<Self, Error> {
301        Self::for_case(None, Some(right))
302    }
303
304    pub fn pair(left: &Self, right: &Self) -> Result<Self, Error> {
305        left.inference_context.check_eq(&right.inference_context)?;
306        left.inference_context.unify(
307            &left.source,
308            &right.source,
309            "pair combinator: left source = right source",
310        )?;
311        Ok(Self {
312            source: left.source.shallow_clone(),
313            target: Type::product(
314                &left.inference_context,
315                left.target.shallow_clone(),
316                right.target.shallow_clone(),
317            ),
318            inference_context: left.inference_context.shallow_clone(),
319        })
320    }
321
322    pub fn fail(inference_context: &Context<'brand>) -> Self {
323        Self {
324            source: Type::free(inference_context, new_name("fail_src_")),
325            target: Type::free(inference_context, new_name("fail_tgt_")),
326            inference_context: inference_context.shallow_clone(),
327        }
328    }
329
330    pub fn const_word(inference_context: &Context<'brand>, word: &Word) -> Self {
331        Self {
332            source: Type::unit(inference_context),
333            target: Type::two_two_n(inference_context, word.n()),
334            inference_context: inference_context.shallow_clone(),
335        }
336    }
337
338    pub fn jet(inference_context: &Context<'brand>, jet: &dyn Jet) -> Self {
339        inference_context.check_jet(jet);
340
341        Self {
342            source: jet.source_ty().to_type(inference_context),
343            target: jet.target_ty().to_type(inference_context),
344            inference_context: inference_context.shallow_clone(),
345        }
346    }
347
348    pub fn witness(inference_context: &Context<'brand>) -> Self {
349        Self {
350            source: Type::free(inference_context, new_name("witness_src_")),
351            target: Type::free(inference_context, new_name("witness_tgt_")),
352            inference_context: inference_context.shallow_clone(),
353        }
354    }
355
356    pub fn inference_context(&self) -> &Context<'brand> {
357        &self.inference_context
358    }
359}
360
361impl<'brand> DisconnectConstructible<'brand, Self> for Arrow<'brand> {
362    fn disconnect(left: &Self, right: &Self) -> Result<Self, Error> {
363        Self::for_disconnect(left, right)
364    }
365}
366
367impl<'brand> DisconnectConstructible<'brand, NoDisconnect> for Arrow<'brand> {
368    fn disconnect(left: &Self, _: &NoDisconnect) -> Result<Self, Error> {
369        let source = Type::free(&left.inference_context, "disc_src".into());
370        let target = Type::free(&left.inference_context, "disc_tgt".into());
371        Self::for_disconnect(
372            left,
373            &Self {
374                source,
375                target,
376                inference_context: left.inference_context.shallow_clone(),
377            },
378        )
379    }
380}
381
382impl<'brand> DisconnectConstructible<'brand, Option<&Self>> for Arrow<'brand> {
383    fn disconnect(left: &Self, right: &Option<&Self>) -> Result<Self, Error> {
384        match *right {
385            Some(right) => Self::disconnect(left, right),
386            None => Self::disconnect(left, &NoDisconnect),
387        }
388    }
389}