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}