Skip to main content

formality_core/
judgment.rs

1use std::{cell::RefCell, collections::BTreeSet};
2
3use crate::fixed_point::FixedPointStack;
4
5mod test_filtered;
6mod test_reachable;
7
8pub type JudgmentStack<J, O> = RefCell<FixedPointStack<J, BTreeSet<O>>>;
9
10#[macro_export]
11macro_rules! judgment_fn {
12    (
13        $(#[$attr:meta])*
14        $v:vis fn $name:ident($($input_name:ident : $input_ty:ty),* $(,)?) => $output:ty {
15            debug($($debug_input_name:ident),*)
16            $(assert($assert_expr:expr))*
17            $(trivial($trivial_expr:expr => $trivial_result:expr))*
18            $(($($rule:tt)*))*
19        }
20    ) => {
21        $(#[$attr])*
22        $v fn $name($($input_name : impl $crate::Upcast<$input_ty>),*) -> $crate::Set<$output> {
23            #[derive(Ord, PartialOrd, Eq, PartialEq, Hash, Clone)]
24            struct __JudgmentStruct($($input_ty),*);
25
26            $crate::cast_impl!(__JudgmentStruct);
27
28            impl std::fmt::Debug for __JudgmentStruct {
29                fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30                    let mut f = fmt.debug_struct(stringify!($name));
31                    let __JudgmentStruct($($input_name),*) = self;
32                    $(
33                        f.field(stringify!($debug_input_name), $input_name);
34                    )*
35                    f.finish()
36                }
37            }
38
39            $(let $input_name: $input_ty = $crate::Upcast::upcast($input_name);)*
40
41            $(
42                // Assertions are preconditions
43                assert!($assert_expr);
44            )*
45
46            $(
47                // Trivial cases are an (important) optimization that lets
48                // you cut out all the normal rules.
49                if $trivial_expr {
50                    return std::iter::once($trivial_result).collect();
51                }
52            )*
53
54            $crate::fixed_point::fixed_point::<
55                __JudgmentStruct,
56                $crate::Set<$output>,
57            >(
58                // Tracing span:
59                |input| {
60                    let __JudgmentStruct($($input_name),*) = input;
61                    tracing::debug_span!(
62                        stringify!($name),
63                        $(?$debug_input_name),*
64                    )
65                },
66
67                // Stack:
68                {
69                    thread_local! {
70                        static R: $crate::judgment::JudgmentStack<__JudgmentStruct, $output> = Default::default()
71                    }
72                    &R
73                },
74
75                // Input:
76                __JudgmentStruct($($input_name),*),
77
78                // Default value:
79                |_| Default::default(),
80
81                // Next value:
82                |input: __JudgmentStruct| {
83                    let mut output = $crate::Set::new();
84
85                    $crate::push_rules!(
86                        $name,
87                        &input,
88                        output,
89                        ($($input_name),*) => $output,
90                        $(($($rule)*))*
91                    );
92
93                    output
94                },
95            )
96        }
97    }
98}
99
100/// push_rules! allows construction of inference rules using a more logic-like notation.
101///
102/// The macro input looks like: `push_rules!(builder, (...) (...) (...))` where each
103/// parenthesized group `(...)` is an inference rule. Inference rules are written like so:
104///
105/// ```ignore
106/// (
107///     ( /* condition 1 */) // each condition is a parenthesized group
108///     ( /* condition 2 */)
109///     -------------------- // 3 or more `-` separate the condition from the conclusion
110///     ( /* conclusion */)  // as is the conclusion
111/// )
112/// ```
113///
114/// The conditions can be the following
115///
116/// * `(<expr> => <binding>)` -- used to apply judgments, but really `<expr>` can be anything with an `into_iter` method.
117/// * `(if <expr>)`
118/// * `(if let <pat> = <expr>)`
119/// * `(let <binding> = <expr>)`
120///
121/// The conclusions can be the following
122///
123/// * `(<pat> => <binding>)
124#[macro_export]
125macro_rules! push_rules {
126    ($judgment_name:ident, $input_value:expr, $output:expr, $input_names:tt => $output_ty:ty, $($rule:tt)*) => {
127        $($crate::push_rules!(@rule ($judgment_name, $input_value, $output, $input_names => $output_ty) $rule);)*
128    };
129
130    // `@rule (builder) rule` phase: invoked for each rule, emits `push_rule` call
131
132    (@rule ($judgment_name:ident, $input_value:expr, $output:expr, $input_names:tt => $output_ty:ty) ($($m:tt)*)) => {
133        $crate::push_rules!(@accum
134            args($judgment_name, $input_value, $output, $input_names => $output_ty)
135            accum()
136            $($m)*
137        );
138    };
139
140    // `@accum (conditions)` phase: accumulates the contents of a given rule,
141    // pushing tokens into `conditions` until the `-----` and conclusion are found.
142
143    (@accum
144        args($judgment_name:ident, $input_value:expr, $output:expr, ($($input_names:ident),*) => $output_ty:ty)
145        accum($($m:tt)*)
146        ---$(-)* ($n:literal)
147        ($conclusion_name:ident($($patterns:tt)*) => $v:expr)
148    ) => {
149        // Found the conclusion.
150        {
151            // give the user a type error if the name they gave
152            // in the conclusion is not the same as the name of the
153            // function
154            #[allow(dead_code)]
155            struct WrongJudgmentNameInConclusion;
156            const _: WrongJudgmentNameInConclusion = {
157                let $judgment_name = WrongJudgmentNameInConclusion;
158                $conclusion_name
159            };
160
161            if let Some(__JudgmentStruct($($input_names),*)) = Some($input_value) {
162                $crate::push_rules!(@match inputs($($input_names)*) patterns($($patterns)*) args($judgment_name; $n; $v; $output; $($m)*));
163            }
164        }
165    };
166
167    (@accum args $args:tt accum($($m:tt)*) ($($n:tt)*) $($o:tt)*) => {
168        // Push the condition into the list `$m`.
169        $crate::push_rules!(@accum args $args accum($($m)* ($($n)*)) $($o)*)
170    };
171
172    // Matching phase: peel off the patterns one by one and match them against the values
173    // extracted from the input. For anything that is not an identity pattern, invoke `downcast`.
174
175    (@match inputs() patterns() args($judgment_name:ident; $n:literal; $v:expr; $output:expr; $($m:tt)*)) => {
176        tracing::trace_span!("matched rule", rule = $n, judgment = stringify!($judgment_name)).in_scope(|| {
177            $crate::push_rules!(@body ($judgment_name, $n, $v, $output) $($m)*);
178        });
179    };
180
181    (@match inputs() patterns(,) args $args:tt) => {
182        $crate::push_rules!(@match inputs() patterns() args $args);
183    };
184
185    (@match inputs($in0:ident $($inputs:tt)*) patterns($pat0:ident, $($pats:tt)*) args $args:tt) => {
186        {
187            let $pat0 = Clone::clone($in0);
188            $crate::push_rules!(@match inputs($($inputs)*) patterns($($pats)*) args $args);
189        }
190    };
191
192    (@match inputs($in0:ident) patterns($pat0:ident) args $args:tt) => {
193        {
194            let $pat0 = Clone::clone($in0);
195            $crate::push_rules!(@match inputs() patterns() args $args);
196        }
197    };
198
199    (@match inputs($in0:ident $($inputs:tt)*) patterns($pat0:ident : $ty0:ty, $($pats:tt)*) args $args:tt) => {
200        {
201            if let Some($pat0) = $crate::Downcast::downcast::<$ty0>($in0) {
202                $crate::push_rules!(@match inputs($($inputs)*) patterns($($pats)*) args $args);
203            }
204        }
205    };
206
207    (@match inputs($in0:ident) patterns($pat0:ident : $ty0:ty) args $args:tt) => {
208        {
209            if let Some($pat0) = $crate::Downcast::downcast::<$ty0>($in0) {
210                $crate::push_rules!(@match inputs() patterns() args $args);
211            }
212        }
213    };
214
215    (@match inputs($in0:ident $($inputs:tt)*) patterns($pat0:pat, $($pats:tt)*) args $args:tt) => {
216        if let Some($pat0) = $crate::Downcast::downcast(&$in0) {
217            $crate::push_rules!(@match inputs($($inputs)*) patterns($($pats)*) args $args);
218        }
219    };
220
221    (@match inputs($in0:ident) patterns($pat0:pat) args $args:tt) => {
222        if let Some($pat0) = $crate::Downcast::downcast(&$in0) {
223            $crate::push_rules!(@match inputs() patterns() args $args);
224        }
225    };
226
227    // (@match (($arg0:ident @ $pat0:pat) $($args:tt)*) ($n:literal; $v:expr; $output:ident) $($m:tt)*) => {
228    //     if let Some($pat0) = $crate::cast::Downcast::downcast(&$arg0) {
229    //         $crate::push_rules!(@match ($($args)*) ($n; $v; $output) $($m)*);
230    //     }
231    // };
232
233    // `@body (v)` phase: processes the conditions, generating the code
234    // to evaluate the rule. This is effectively an iterator chain. The
235    // expression `v` is carried in from the conclusion and forms the final
236    // output of this rule, once all the conditions are evaluated.
237
238    (@body $args:tt (if $c:expr) $($m:tt)*) => {
239        if $c {
240            $crate::push_rules!(@body $args $($m)*);
241        } else {
242            tracing::trace!("failed to match if condition {:?}", stringify!($c))
243        }
244    };
245
246    (@body $args:tt (assert $c:expr) $($m:tt)*) => {
247        assert!($c);
248        $crate::push_rules!(@body $args $($m)*);
249    };
250
251    (@body $args:tt (if let $p:pat = $e:expr) $($m:tt)*) => {
252        if let $p = $e {
253            $crate::push_rules!(@body $args $($m)*);
254        } else {
255            tracing::trace!("failed to match pattern {:?}", stringify!($p))
256        }
257    };
258
259    (@body $args:tt ($i:expr => $p:pat) $($m:tt)*) => {
260        for $p in $i {
261            $crate::push_rules!(@body $args $($m)*);
262        }
263    };
264
265    (@body $args:tt (let $p:pat = $i:expr) $($m:tt)*) => {
266        {
267            let $p = $i;
268            $crate::push_rules!(@body $args $($m)*);
269        }
270    };
271
272    (@body ($judgment_name:ident, $rule_name:literal, $v:expr, $output:expr)) => {
273        {
274            let result = $crate::Upcast::upcast($v);
275            tracing::debug!("produced {:?} from rule {:?} in judgment {:?}", result, $rule_name, stringify!($judgment_name));
276            $output.insert(result)
277        }
278    };
279}