veloren_common/
typed.rs

1use core::marker::PhantomData;
2use serde::{Deserialize, Serialize};
3
4pub trait SubContext<Context> {
5    fn sub_context(self) -> Context;
6}
7
8impl<Context> SubContext<Context> for Context {
9    fn sub_context(self) -> Context { self }
10}
11
12impl<Head, Tail> SubContext<Tail> for (Head, Tail) {
13    fn sub_context(self) -> Tail { self.1 }
14}
15
16pub trait Typed<Context, Type, S> {
17    fn reduce(self, context: Context) -> (Type, S);
18}
19
20/// Given a head expression (Self) and a target type (Type),
21/// attempt to synthesize a term that reduces head into the target type.
22///
23/// How we do this depends on the type of the head expression:
24///
25/// - For enums, we synthesize a match on the current head.  For each match arm,
26///   we then repeat this process on the constructor arguments; if there are no
27///   constructor arguments, we synthesize a literal (Pure term). (TODO: Handle
28///   larger than 1 tuple properly--for now we just synthesize a Pure term for
29///   these cases).
30///
31/// - For structs, we synthesize a projection on the current head.  For each
32///   projection, we then repeat this process on the type of the projected
33///   field.
34///
35/// - For other types (which currently have to opt out during the field
36///   declaration), we synthesize a literal.
37///
38/// TODO: Differentiate between the context and the stack at some point; for
39/// now, we only use the context as a stack.
40pub trait SynthTyped<Context, Target> {
41    type Expr;
42}
43
44/// Weak head reduction type (equivalent to applying a reduction to the head
45/// variable, but this way we don't have to implement variable lookup and it
46/// doesn't serialize with variables).
47#[fundamental]
48#[derive(Deserialize, Serialize)]
49#[serde(transparent)]
50pub struct WeakHead<Reduction, Type> {
51    pub red: Reduction,
52    #[serde(skip)]
53    pub ty: PhantomData<Type>,
54}
55
56#[derive(Deserialize, Serialize)]
57#[serde(transparent)]
58pub struct Pure<T>(pub T);
59
60impl<'a, Context: SubContext<S>, T, S> Typed<Context, &'a T, S> for &'a Pure<T> {
61    fn reduce(self, context: Context) -> (&'a T, S) { (&self.0, context.sub_context()) }
62}
63
64impl<Context, Target> SynthTyped<Context, Target> for WeakHead<Pure<Target>, Target> {
65    type Expr = Pure<Target>;
66}
67
68/// A lazy pattern match reified as a Rust type.
69///
70/// `expr` is the expression being matched on, generally of some enum type `Ty`.
71///
72/// `case` represents the pattern match--it will generally be a structure with
73/// one field per constructor in `Ty`.  The field should contain enough
74/// information to run the match arm for that constructor, given the information
75/// contained in the constructor arguments.
76///
77/// `ty` represents the return type of the match expression.  It does not carry
78/// any runtime-relevant information, but is needed in order to simplify our
79/// trait definitions.
80///
81/// The intent is that you should not construct this structure directly, nor
82/// should you define or construct the `Cases` structure directly.  Instead, to
83/// use this you are expected to wrap your enum declaration in a call to
84/// [make_case_elim!], as follows:
85///
86/// ```
87/// veloren_common::make_case_elim!(
88///     my_type_module,
89///     #[repr(u32)]
90///     #[derive(Clone,Copy)]
91///     pub enum MyType {
92///         Constr1 = 0,
93///         #[typed(pure)] Constr2(arg : u8) = 1,
94///         /* ..., */
95///     }
96/// );
97///
98/// # fn main() {
99/// #   println!("some_dummy_main");
100/// # }
101/// ```
102///
103/// This macro automatically does a few things.  First, it creates the `enum`
104/// type `MyType` in the current scope, as expected.  Second, it creates a
105/// module named `my_type_module` in the current scope, into which it dumps a
106/// few things.  In this case:
107///
108/// ```
109/// #[repr(u32)]
110/// #[derive(Clone, Copy)]
111/// pub enum MyType {
112///     Constr1 = 0,
113///     Constr2(u8) = 1,
114///     /* ..., */
115/// }
116///
117/// # #[allow(non_snake_case)]
118/// # #[allow(dead_code)]
119/// mod my_type_module {
120///     use serde::{Deserialize, Serialize};
121///
122///     /// The number of variants in this enum.
123///     pub const NUM_VARIANTS: usize = 2;
124///
125///     /// An array of all the variant indices (in theory, this can be used by this or other
126///     /// macros in order to easily build up things like uniform random samplers).
127///     pub const ALL_INDICES: [u32; NUM_VARIANTS] = [0, 1];
128///
129///     /// A convenience trait used to store a different type for each constructor in this
130///     /// pattern.
131///     pub trait PackedElim {
132///         type Constr1;
133///         type Constr2;
134///     }
135///
136///     /// The actual *cases.*  If you think of pattern match arms as being closures that accept
137///     /// the constructor types as arguments, you can think of this structure as somehow
138///     /// representing just the data *owned* by the closure.  This is also what you will
139///     /// generally store in your ron file--it has a field for each constructor of your enum,
140///     /// with the types of all the fields specified by the implementation of [PackedElim] for
141///     /// the [Elim] argument.  Each field has the same name as the constructor it represents.
142///     #[derive(Serialize, Deserialize)]
143///     pub struct Cases<Elim: PackedElim> {
144///         pub Constr1: Elim::Constr1,
145///         pub Constr2: Elim::Constr2,
146///     }
147///
148///     /// Finally, because it represents by an overwhelming margin the most common usecase, we
149///     /// predefine a particular pattern matching strategy--"pure"--where every arm holds data of
150///     /// the exact same type, T.
151///     impl<T> PackedElim for veloren_common::typed::Pure<T> {
152///         type Constr1 = T;
153///         type Constr2 = T;
154///     }
155///
156///     /// Because PureCases is so convenient, we have an alias for it.  Thus, in order to
157///     /// represent a pattern match on an argument that returns a constant of type (u8,u8,u8) for
158///     /// each arm, you'd use the type `PureCases<(u8, u8, u8)>`.
159///     pub type PureCases<Elim> = Cases<veloren_common::typed::Pure<Elim>>;
160/// }
161/// ```
162///
163/// Finally, a useful implementation of the [Typed] trait completes this story,
164/// providing a way to evaluate this lazy math statement within Rust.
165/// Unfortunately, [Typed] is quite complicated, and this story is still being
166/// fully evaluated, so showing teh type may not be that elucidating.
167/// Instead, we'll just present the method you can use most easily to pattern
168/// match using the PureCases pattern we mentioned earlier:
169///
170/// pub fn elim_case_pure<'a, Type>(&'a self, cases: &'a $mod::PureCases<Type>)
171/// -> &'a Type
172///
173/// If self is expression of your defined enum type, and match data defined by
174/// PureCases, this evaluates the pattern match on self and returns the matched
175/// case.
176///
177/// To see how this is used in more detail, check out
178/// `common/src/body/humanoid.rs`; it is also used extensively in the world
179/// repository.
180///
181/// ---
182///
183/// Limitations:
184///
185/// Unfortunately, due to restrictions on macro_rules, we currently always
186/// require the types defined to #[repr(inttype)] as you can see above.  There
187/// are also some other current limitations that we hopefully will be able to
188/// lift at some point; struct variants are not yet supported, and neither
189/// attributes on fields.
190#[fundamental]
191#[derive(Deserialize, Serialize)]
192#[serde(transparent)]
193pub struct ElimCase<Cases> {
194    pub cases: Cases,
195}
196
197#[fundamental]
198#[derive(Deserialize, Serialize)]
199#[serde(transparent)]
200pub struct ElimProj<Proj> {
201    pub proj: Proj,
202}
203pub type ElimWeak<Type, Elim> = <WeakHead<Type, Elim> as SynthTyped<((Type,), ()), Elim>>::Expr;
204
205#[macro_export]
206macro_rules! as_item {
207    ($i:item) => {
208        $i
209    };
210}
211
212#[macro_export]
213/// This macro is used internally by typed.
214///
215/// We use this in order to reliably construct a "representative" type for the
216/// weak head reduction type.  We need this because for some types of arguments
217/// (empty variants for an enum, fields or constructor arguments explicitly
218/// marked as #[typed(pure)], etc.) won't directly implement the WeakHead trait;
219/// in such cases, we just synthesize a literal of the appropriate type.
220macro_rules! make_weak_head_type {
221    ($Target:ty, $( #[$attr:meta] )* , ) => {
222        $crate::typed::Pure<$Target>
223    };
224    ($Target:ty, #[ typed(pure) ] , $( $extra:tt )*) => {
225        $crate::typed::Pure<$Target>
226    };
227    ($Target:ty, , $Type:ty, $( $extra:tt )*) => {
228        $crate::typed::Pure<$Target>
229    };
230    ($Target:ty, , $Type:ty) => {
231        $Type
232    }
233}
234
235#[macro_export]
236macro_rules! make_case_elim {
237    ($mod:ident, $( #[ $ty_attr:meta ] )* $vis:vis enum $ty:ident {
238        $( $( #[$( $constr_attr:tt )*] )* $constr:ident $( ( $( $arg_name:ident : $arg_ty:ty ),* ) )? = $index:expr ),* $(,)?
239    }) => {
240        $crate::as_item! {
241            $( #[$ty_attr] )*
242            $vis enum $ty {
243                $( $constr $( ($( $arg_ty, )*) )? = $index, )*
244            }
245        }
246
247        #[allow(non_snake_case)]
248        #[allow(dead_code)]
249        $vis mod $mod {
250            use ::serde::{Deserialize, Serialize};
251
252            pub const NUM_VARIANTS: usize = 0 $( + { let _ = $index; 1 } )*;
253
254            pub const ALL_INDICES: [u32; NUM_VARIANTS] = [ $( $index, )* ];
255
256            pub trait PackedElim {
257                $( type $constr; )*
258            }
259
260            #[derive(Serialize, Deserialize)]
261            pub struct Cases<Elim: PackedElim> {
262                $( pub $constr : Elim::$constr, )*
263            }
264
265            pub type PureCases<Elim> = $crate::typed::ElimCase<Cases<$crate::typed::Pure<Elim>>>;
266        }
267
268        impl<T> $mod::PackedElim for $crate::typed::Pure<T> {
269            $( type $constr = $crate::typed::Pure<T>; )*
270        }
271
272        #[allow(unused_parens)]
273        impl<Target> $mod::PackedElim for $crate::typed::WeakHead<$ty, Target>
274            where $(
275                $crate::typed::WeakHead<$crate::make_weak_head_type!(Target, $( #[$( $constr_attr )*] )* , $( $( $arg_ty ),* )?), Target> :
276                $crate::typed::SynthTyped<($( ($( $arg_ty, )*), )? ()), Target>,
277            )*
278        {
279            $( type $constr =
280               <$crate::typed::WeakHead<$crate::make_weak_head_type!(Target, $( #[$( $constr_attr )*] )* , $( $( $arg_ty ),* )?), Target>
281               as $crate::typed::SynthTyped<($( ($( $arg_ty, )*), )? ()), Target>>::Expr;
282            )*
283        }
284
285        #[allow(unused_parens)]
286        impl<Context, Target> $crate::typed::SynthTyped<(($ty,), Context), Target> for $crate::typed::WeakHead<$ty, Target>
287            where $(
288                $crate::typed::WeakHead<$crate::make_weak_head_type!(Target, $( #[$( $constr_attr )*] )* , $( $( $arg_ty ),* )?), Target> :
289                $crate::typed::SynthTyped<($( ($( $arg_ty, )*), )? ()), Target>,
290            )*
291        {
292            type Expr = $crate::typed::ElimCase<$mod::Cases<$crate::typed::WeakHead<$ty, Target>>>;
293        }
294
295        #[allow(unused_parens)]
296        impl<'a, 'b, Elim: $mod::PackedElim, Context, Type, S>
297            $crate::typed::Typed<((&'a $ty,), Context), Type, S> for &'b $crate::typed::ElimCase<$mod::Cases<Elim>>
298            where
299                $( &'b Elim::$constr: $crate::typed::Typed<($( ($( &'a $arg_ty, )*), )? Context), Type, S> ),*
300        {
301            fn reduce(self, ((head,), context): ((&'a $ty,), Context)) -> (Type, S)
302            {
303                match head {
304                    $( $ty::$constr $( ($( $arg_name, )*) )? =>
305                        <_ as $crate::typed::Typed<_, Type, _>>::reduce(
306                            &self.cases.$constr,
307                            ($( ($( $arg_name, )*), )? context),
308                        ),
309                    )*
310                }
311            }
312        }
313
314        impl $ty {
315            pub fn elim<'a, Elim, Context, S, Type>(&'a self, elim: Elim, context: Context) -> (Type, S)
316            where
317                Elim : $crate::typed::Typed<((&'a $ty,), Context), Type, S>,
318            {
319                elim.reduce(((self,), context))
320            }
321
322            pub fn elim_case_pure<'a, Type>(&self, cases: &'a $mod::PureCases<Type>) -> &'a Type
323            {
324                let (expr, ()) = self.elim(cases, ());
325                expr
326            }
327
328            #[allow(unused_parens)]
329            pub fn elim_case_weak<'a, 'b, Type>(&'a self, cases: &'b $crate::typed::ElimWeak<Self, Type>) -> &'b Type
330            where $(
331                $crate::typed::WeakHead<$crate::make_weak_head_type!(Type, $( #[$( $constr_attr )*] )* , $( $( $arg_ty ),* )?), Type> :
332                $crate::typed::SynthTyped<($( ($( $arg_ty, )*), )? ()), Type>,
333            )*
334                &'b $crate::typed::ElimWeak<Self, Type> : $crate::typed::Typed<((&'a $ty,), ()), &'b Type, ()>,
335            {
336                let (expr, ()) = self.elim(cases, ());
337                expr
338            }
339        }
340    }
341}
342
343#[macro_export]
344macro_rules! make_proj_elim {
345    ($mod:ident, $( #[ $ty_attr:meta ] )* $vis:vis struct $ty:ident {
346        $( $( #[$( $constr_attr:tt )*] )* $field_vis:vis $constr:ident : $arg_ty:ty ),* $(,)?
347    }) => {
348        $crate::as_item! {
349            $( #[$ty_attr] )*
350            $vis struct $ty {
351                $( $field_vis $constr : $arg_ty, )*
352            }
353        }
354
355        #[allow(non_camel_case_types)]
356        #[allow(dead_code)]
357        $vis mod $mod {
358            use ::serde::{Deserialize, Serialize};
359
360            pub trait PackedElim {
361                $( type $constr; )*
362            }
363
364            #[derive(Serialize, Deserialize)]
365            pub enum Proj<Elim: PackedElim> {
366                $( $constr(Elim::$constr), )*
367            }
368
369            pub type PureProj<Elim> = $crate::typed::ElimProj<Proj<$crate::typed::Pure<Elim>>>;
370        }
371
372        impl<T> $mod::PackedElim for $crate::typed::Pure<T> {
373            $( type $constr = $crate::typed::Pure<T>; )*
374        }
375
376        #[allow(unused_parens)]
377        impl<Target> $mod::PackedElim for $crate::typed::WeakHead<$ty, Target>
378            where $(
379                $crate::typed::WeakHead<$crate::make_weak_head_type!(Target, $( #[$( $constr_attr )*] )* , $arg_ty), Target> :
380                $crate::typed::SynthTyped<(($arg_ty,), ()), Target>,
381            )*
382        {
383            $( type $constr =
384               <$crate::typed::WeakHead<$crate::make_weak_head_type!(Target, $( #[$( $constr_attr )*] )* , $arg_ty), Target>
385               as $crate::typed::SynthTyped<(($arg_ty,), ()), Target>>::Expr;
386            )*
387        }
388
389        #[allow(unused_parens)]
390        impl<Context, Target> $crate::typed::SynthTyped<(($ty,), Context), Target> for $crate::typed::WeakHead<$ty, Target>
391            where $(
392                $crate::typed::WeakHead<$crate::make_weak_head_type!(Target, $( #[$( $constr_attr )*] )* , $arg_ty), Target> :
393                $crate::typed::SynthTyped<(($arg_ty,), ()), Target>,
394            )*
395        {
396            type Expr = $crate::typed::ElimProj<$mod::Proj<$crate::typed::WeakHead<$ty, Target>>>;
397        }
398
399        #[allow(unused_parens)]
400        impl<'a, 'b, Elim: $mod::PackedElim, Context, Type, S>
401            $crate::typed::Typed<((&'a $ty,), Context), Type, S> for &'b $crate::typed::ElimProj<$mod::Proj<Elim>>
402            where
403                $( &'b Elim::$constr: $crate::typed::Typed<((&'a $arg_ty,), Context), Type, S> ),*
404        {
405            fn reduce(self, ((head,), context): ((&'a $ty,), Context)) -> (Type, S)
406            {
407                match self.proj {
408                    $( $mod::Proj::$constr(ref projection) =>
409                        <_ as $crate::typed::Typed<_, Type, _>>::reduce(
410                            projection,
411                            ((&head.$constr,), context),
412                        ),
413                    )*
414                }
415            }
416        }
417
418        impl $ty {
419            pub fn elim<'a, Elim, Context, S, Type>(&'a self, elim: Elim, context: Context) -> (Type, S)
420            where
421                Elim : $crate::typed::Typed<((&'a $ty,), Context), Type, S>,
422            {
423                elim.reduce(((self,), context))
424            }
425
426            pub fn elim_proj_pure<'a, Type>(&self, cases: &'a $mod::PureProj<Type>) -> &'a Type
427            {
428                let (expr, ()) = self.elim(cases, ());
429                expr
430            }
431
432            #[allow(unused_parens)]
433            pub fn elim_proj_weak<'a, 'b, Type>(&'a self, cases: &'b $crate::typed::ElimWeak<Self, Type>) -> &'b Type
434            where $(
435                $crate::typed::WeakHead<$crate::make_weak_head_type!(Type, $( #[$( $constr_attr )*] )* , $arg_ty), Type> :
436                $crate::typed::SynthTyped<(($arg_ty,), ()), Type>,
437            )*
438                &'b $crate::typed::ElimWeak<Self, Type> : $crate::typed::Typed<((&'a $ty,), ()), &'b Type, ()>,
439            {
440                let (expr, ()) = self.elim(cases, ());
441                expr
442            }
443        }
444    }
445}