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}