beck_macro/
typed.rs

1//! Typed macros: the half of §2.4 that wants the checker's answers.
2//!
3//! An ordinary `macro` runs before anything has been inferred, which is why `derive_json` is
4//! handed a **declaration** rather than an expression — a model's fields are in its syntax, so no
5//! type information is needed and none can be had. A `typed macro` is the other case: it is called
6//! with expressions, and what it wants to know is what those expressions *are*.
7//!
8//! So a typed macro is expanded by the **checker**, at the call site, once the arguments have been
9//! inferred. The body is the same language an untyped macro body is ([`crate::interp`]), with one
10//! name added: `node_ty(e)` answers with the type the checker gave `e`, as a value the body can
11//! ask questions of.
12//!
13//! # What a body sees
14//!
15//! [`TyRepr`] is the value, and it is reached through the ordinary record notation rather than
16//! through a family of builtins:
17//!
18//! | Written | Answers |
19//! |---|---|
20//! | `t.name` | `"Int"`, `"list"`, `"Todo"` — the head, with no arguments |
21//! | `t.kind` | `"builtin"`, `"model"`, `"union"`, `"newtype"`, `"fn"`, `"param"`, `"unknown"` |
22//! | `t.args` | `list[Int]` answers `[Int]`; a function answers its parameter types |
23//! | `t.result` | a function's result type |
24//! | `t.fields` | a model's fields, as `{name, ty}` records, with the type's own arguments substituted in |
25//! | `t.variants` | a union's variants, as `{name, fields}` records |
26//! | `t.inner` | what a `newtype` wraps |
27//!
28//! `fields`, `variants` and `inner` are read **on access** rather than carried in the value, and
29//! that is not an optimisation: `model Tree: left: Tree` is a type whose fields mention itself, so
30//! a value holding its own fields eagerly would not be a finite value. A *type expression* is
31//! always finite; only looking into a declaration recurses, and a body that recurses without a base
32//! case is stopped by the same nesting bound every other compile-time call is.
33
34use std::collections::HashMap;
35use std::fmt;
36use std::sync::Arc;
37
38use beck_diag::depth::Nesting;
39use beck_diag::{Diagnostics, Span};
40use beck_syntax::Node;
41
42use crate::{declares_a_macro, declares_a_typed_macro, Expander};
43
44/// What a declaration is, for a macro asking what it may look into.
45#[derive(Clone, Copy, Debug, PartialEq, Eq)]
46pub enum TyKind {
47    /// The language's own: `Int`, `Str`, `list`, `Map`, `Option`, and everything else in the
48    /// prelude. A macro may read its arguments and not its insides.
49    Builtin,
50    Model,
51    Union,
52    Newtype,
53}
54
55impl TyKind {
56    pub fn name(self) -> &'static str {
57        match self {
58            TyKind::Builtin => "builtin",
59            TyKind::Model => "model",
60            TyKind::Union => "union",
61            TyKind::Newtype => "newtype",
62        }
63    }
64}
65
66/// The type of an expression, as a macro body sees it.
67///
68/// A projection of the checker's `Ty` rather than the thing itself: this crate must not depend on
69/// the type checker, which depends on it. What is dropped is what a macro has no use for — a
70/// function's effect row, and the identity of a unification variable.
71#[derive(Clone, Debug, PartialEq)]
72pub enum TyRepr {
73    /// A named type applied to arguments: `Int`, `list[Str]`, `Todo`.
74    Con {
75        name: Arc<str>,
76        kind: TyKind,
77        args: Vec<TyRepr>,
78    },
79    /// A declaration's own type parameter, seen while looking into that declaration — the `T` of
80    /// `model Box[T]`. Substituted away by [`TypeEnv::fields`] when the type being looked into
81    /// carries arguments, so a body meets one only where the type it asked about was generic.
82    Param { name: Arc<str>, index: usize },
83    Fun {
84        params: Vec<TyRepr>,
85        result: Box<TyRepr>,
86    },
87    /// Inference had no answer here — an argument whose type is still open, or one whose own
88    /// checking failed.
89    Unknown,
90}
91
92impl TyRepr {
93    pub fn kind_name(&self) -> &'static str {
94        match self {
95            TyRepr::Con { kind, .. } => kind.name(),
96            TyRepr::Param { .. } => "param",
97            TyRepr::Fun { .. } => "fn",
98            TyRepr::Unknown => "unknown",
99        }
100    }
101
102    /// The head, with no arguments — what a body matches on.
103    pub fn head(&self) -> Arc<str> {
104        match self {
105            TyRepr::Con { name, .. } | TyRepr::Param { name, .. } => name.clone(),
106            TyRepr::Fun { .. } => Arc::from("->"),
107            TyRepr::Unknown => Arc::from("?"),
108        }
109    }
110
111    pub fn args(&self) -> Vec<TyRepr> {
112        match self {
113            TyRepr::Con { args, .. } => args.clone(),
114            TyRepr::Fun { params, .. } => params.clone(),
115            _ => Vec::new(),
116        }
117    }
118
119    /// This type with a declaration's parameters replaced by the arguments a mention of it carried.
120    fn substitute(&self, args: &[TyRepr]) -> TyRepr {
121        match self {
122            TyRepr::Param { index, .. } => args.get(*index).cloned().unwrap_or(TyRepr::Unknown),
123            TyRepr::Con {
124                name,
125                kind,
126                args: inner,
127            } => TyRepr::Con {
128                name: name.clone(),
129                kind: *kind,
130                args: inner.iter().map(|a| a.substitute(args)).collect(),
131            },
132            TyRepr::Fun { params, result } => TyRepr::Fun {
133                params: params.iter().map(|p| p.substitute(args)).collect(),
134                result: Box::new(result.substitute(args)),
135            },
136            TyRepr::Unknown => TyRepr::Unknown,
137        }
138    }
139}
140
141impl fmt::Display for TyRepr {
142    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143        match self {
144            TyRepr::Con { name, args, .. } if args.is_empty() => write!(f, "{name}"),
145            TyRepr::Con { name, args, .. } => {
146                write!(f, "{name}[")?;
147                for (i, a) in args.iter().enumerate() {
148                    if i > 0 {
149                        write!(f, ", ")?;
150                    }
151                    write!(f, "{a}")?;
152                }
153                write!(f, "]")
154            }
155            TyRepr::Param { name, .. } => write!(f, "{name}"),
156            TyRepr::Fun { params, result } => {
157                write!(f, "(")?;
158                for (i, p) in params.iter().enumerate() {
159                    if i > 0 {
160                        write!(f, ", ")?;
161                    }
162                    write!(f, "{p}")?;
163                }
164                write!(f, ") -> {result}")
165            }
166            TyRepr::Unknown => write!(f, "?"),
167        }
168    }
169}
170
171/// A declaration's fields, or one variant's: a name and the type written beside it.
172pub type Fields = Vec<(Arc<str>, TyRepr)>;
173
174/// A union's variants: a name and what that variant holds.
175pub type Variants = Vec<(Arc<str>, Fields)>;
176
177/// What one `model`, `union` or `newtype` declaration holds.
178///
179/// Field types are written with the declaration's parameters in place ([`TyRepr::Param`]);
180/// [`TypeEnv::fields`] is what puts a mention's arguments back in.
181#[derive(Clone, Debug, Default)]
182pub struct DeclInfo {
183    pub fields: Fields,
184    pub variants: Variants,
185    pub inner: Option<TyRepr>,
186}
187
188/// What the checker inferred, for the one call being expanded.
189///
190/// Keyed by [`Span`], which is what a `Node` carries and therefore the only identity an argument
191/// has once it has been substituted into a template. Where several nodes share one position —
192/// code an earlier macro expansion generated borrows the call site's — the **outermost** wins,
193/// because the checker records a parent after its children.
194#[derive(Clone, Debug, Default)]
195pub struct TypeEnv {
196    nodes: HashMap<Span, TyRepr>,
197    decls: HashMap<Arc<str>, DeclInfo>,
198}
199
200impl TypeEnv {
201    pub fn new() -> TypeEnv {
202        TypeEnv::default()
203    }
204
205    /// Forget the last call's expressions, keeping the module's declarations.
206    ///
207    /// The declarations are the whole module's and are collected once; what a body may ask about
208    /// belongs to one call site and must not outlive it.
209    pub fn clear_nodes(&mut self) {
210        self.nodes.clear();
211    }
212
213    pub fn record(&mut self, span: Span, ty: TyRepr) {
214        if !span.is_none() {
215            self.nodes.insert(span, ty);
216        }
217    }
218
219    pub fn declare(&mut self, name: Arc<str>, decl: DeclInfo) {
220        self.decls.insert(name, decl);
221    }
222
223    pub fn of(&self, span: Span) -> Option<&TyRepr> {
224        self.nodes.get(&span)
225    }
226
227    /// A model's fields, or a newtype's one field, with the mention's arguments substituted in.
228    pub fn fields(&self, t: &TyRepr) -> Fields {
229        let TyRepr::Con { name, args, .. } = t else {
230            return Vec::new();
231        };
232        let Some(decl) = self.decls.get(name) else {
233            return Vec::new();
234        };
235        decl.fields
236            .iter()
237            .map(|(n, ft)| (n.clone(), ft.substitute(args)))
238            .collect()
239    }
240
241    /// A union's variants, each with its own fields, substituted the same way.
242    pub fn variants(&self, t: &TyRepr) -> Variants {
243        let TyRepr::Con { name, args, .. } = t else {
244            return Vec::new();
245        };
246        let Some(decl) = self.decls.get(name) else {
247            return Vec::new();
248        };
249        decl.variants
250            .iter()
251            .map(|(n, fs)| {
252                let fields = fs
253                    .iter()
254                    .map(|(fname, ft)| (fname.clone(), ft.substitute(args)))
255                    .collect();
256                (n.clone(), fields)
257            })
258            .collect()
259    }
260
261    /// What a `newtype` wraps, or [`TyRepr::Unknown`] for anything else.
262    pub fn inner(&self, t: &TyRepr) -> TyRepr {
263        let TyRepr::Con { name, args, .. } = t else {
264            return TyRepr::Unknown;
265        };
266        match self.decls.get(name).and_then(|d| d.inner.as_ref()) {
267            Some(i) => i.substitute(args),
268            None => TyRepr::Unknown,
269        }
270    }
271}
272
273/// Where a typed expansion starts minting hygiene scopes: the **even** numbers.
274///
275/// The two expanders run over one module and must not mint the same scope, or a binding one
276/// introduced would be visible to a reference the other introduced — hygiene failing silently,
277/// which is the one way it can fail. Parity keeps them apart *by construction*, which is what this
278/// wants rather than a bound: [`Expander::fresh_scope`] counts the odd numbers, this counts the
279/// even ones, and neither has to know how many the other spent.
280const SCOPE_BASE: u32 = 2;
281
282/// The typed macros a module has, and everything their bodies may reach.
283///
284/// Built where the untyped expansion ends and handed to the checker, which is the only thing that
285/// can expand one: a typed macro's body asks what its arguments *are*, and until the checker has
286/// run there is no answer.
287#[derive(Clone, Debug)]
288pub struct TypedExpander {
289    /// Whether anything in scope is a typed macro. Held rather than derived, because the checker
290    /// asks at every call it walks.
291    has_typed: bool,
292    macros: HashMap<Arc<str>, crate::MacroDef>,
293    defs: HashMap<Arc<str>, crate::interp::FnDef>,
294    next_scope: u32,
295    steps: u64,
296    steps_spent: bool,
297    fuel: u64,
298    spent: bool,
299}
300
301impl Default for TypedExpander {
302    fn default() -> TypedExpander {
303        TypedExpander {
304            has_typed: false,
305            macros: HashMap::new(),
306            defs: HashMap::new(),
307            next_scope: SCOPE_BASE,
308            steps: crate::interp::MAX_STEPS,
309            steps_spent: false,
310            fuel: crate::MAX_EXPANSION,
311            spent: false,
312        }
313    }
314}
315
316impl TypedExpander {
317    /// The typed macros of a module and of the modules it imports.
318    ///
319    /// `imported` are **parsed** modules, for the reason [`crate::expand_module_with`] gives: a
320    /// macro is published by a module's source, because it has no signature for an interface to
321    /// carry.
322    ///
323    /// Collection reports nothing. Duplicate names are refused where the untyped expander collects
324    /// the same declarations one phase earlier, and reporting them again here would be the same
325    /// `B0200` twice.
326    pub fn collect(module: &Node, imported: &[&Node]) -> TypedExpander {
327        // Nothing is copied for a module with no typed macro in scope, which is nearly every
328        // module: collecting the compile-time-callable `def`s copies a body each, and this pass
329        // would otherwise pay that a second time for every module that has an ordinary macro.
330        if !declares_a_typed_macro(module) && !imported.iter().any(|m| declares_a_typed_macro(m)) {
331            return TypedExpander::default();
332        }
333        let mut quiet = Diagnostics::new();
334        let mut ex = Expander::collecting(&mut quiet);
335        let brings_macros = imported.iter().any(|m| declares_a_macro(m));
336        for m in imported {
337            ex.collect_macros_from(m, brings_macros);
338        }
339        ex.collect_macros_from(module, brings_macros);
340        TypedExpander {
341            has_typed: true,
342            macros: std::mem::take(&mut ex.macros),
343            defs: std::mem::take(&mut ex.defs),
344            ..TypedExpander::default()
345        }
346    }
347
348    /// Whether this module has any typed macro at all — the question worth asking before the
349    /// checker carries a probe around, and the one asked at every call site in the module.
350    pub fn is_empty(&self) -> bool {
351        !self.has_typed
352    }
353
354    /// Whether a module-wide budget has run out, so nothing more will ever expand.
355    ///
356    /// A budget is spent **once** and reported once, and the checker infers a call's arguments
357    /// inside a rollback — so a caller that discards what a probe reported has to ask this, or the
358    /// only report there will ever be is the one it just deleted, and every expansion afterwards
359    /// quietly produces nothing.
360    pub fn exhausted(&self) -> bool {
361        self.spent || self.steps_spent
362    }
363
364    /// Say again that a budget ran out, for a caller that threw the first report away.
365    pub fn report_exhaustion(&self, span: Span, diags: &mut Diagnostics) {
366        if self.spent {
367            diags.push(crate::too_much(span));
368        } else if self.steps_spent {
369            diags.push(crate::interp::ran_too_long(span));
370        }
371    }
372
373    /// Whether `name` is a typed macro, and therefore this expander's to expand.
374    pub fn declares(&self, name: &str) -> bool {
375        self.has_typed && self.macros.get(name).is_some_and(|d| d.typed)
376    }
377
378    /// Every typed macro's name, for a diagnostic that wants to say what one is.
379    pub fn names(&self) -> Vec<Arc<str>> {
380        let mut out: Vec<Arc<str>> = self
381            .macros
382            .values()
383            .filter(|d| d.typed)
384            .map(|d| d.name.clone())
385            .collect();
386        out.sort();
387        out
388    }
389
390    /// Charge an expansion this call site has **already** produced, without producing it again.
391    ///
392    /// The budget bounds what expansion *produces* (`docs/42` §42.6), and an expansion the checker
393    /// is handed twice puts two copies in the program — so the second copy is charged even though
394    /// the body ran once. That is the difference between a macro whose output keeps its argument
395    /// and one that writes it twice, and charging per call site rather than per use would erase it:
396    /// a doubling macro nested `d` deep really does produce `2^d` nodes, and really does owe them.
397    pub fn charge_expansion(&mut self, out: &Node, span: Span, diags: &mut Diagnostics) -> bool {
398        crate::charge_nodes(&mut self.fuel, &mut self.spent, out, span, diags)
399    }
400
401    /// Expand one call, with what the checker inferred about its arguments.
402    ///
403    /// The four-part hygiene dance is the untyped expander's, unchanged — a typed macro differs in
404    /// *when* it runs and in what its body may ask, not in how a name it introduces is scoped.
405    pub fn expand(
406        &mut self,
407        call: &Node,
408        types: &TypeEnv,
409        diags: &mut Diagnostics,
410    ) -> Option<Node> {
411        let name = call.head_name()?.to_string();
412        let def = self.macros.get(name.as_str()).filter(|d| d.typed)?.clone();
413        let mut ex = Expander {
414            macros: std::mem::take(&mut self.macros),
415            defs: std::mem::take(&mut self.defs),
416            next_scope: self.next_scope,
417            steps: self.steps,
418            steps_spent: self.steps_spent,
419            fuel: self.fuel,
420            spent: self.spent,
421            nesting: Nesting::new(),
422            types: Some(types),
423            diags,
424        };
425        // An untyped macro inside what a typed one produced still expands. The two phases are
426        // ordered, not exclusive: a template that writes `unless(…)` means what it says wherever it
427        // was written, and by this point nothing else will ever look at that call.
428        let out = match ex.apply_macro(&def, call) {
429            Some(out) if ex.charge(&out, call.span()) => Some(ex.expand(&out, 1)),
430            _ => None,
431        };
432        self.next_scope = ex.next_scope;
433        self.steps = ex.steps;
434        self.steps_spent = ex.steps_spent;
435        self.fuel = ex.fuel;
436        self.spent = ex.spent;
437        self.macros = std::mem::take(&mut ex.macros);
438        self.defs = std::mem::take(&mut ex.defs);
439        out
440    }
441}