Skip to main content

gruel_cache/
remap.rs

1//! Spur remapping after cache load (ADR-0074 Phase 2b).
2//!
3//! Cached AST and RIR carry `Spur` values that index into the cached
4//! per-file interner snapshot. After we re-intern each cached string into
5//! the build's shared `ThreadedRodeo` (via [`InternerSnapshot::restore_into`])
6//! we have a remap table: `cached_spur.into_usize() → build_spur`.
7//!
8//! This module walks the IR tree and substitutes every `Spur` via that
9//! remap table.
10//!
11//! ## Status
12//!
13//! - **AST walker:** complete. Every `Expr`, `Statement`, `Pattern`,
14//!   declaration-level type, and sub-type has a `RemapSpurs` impl;
15//!   `remap_spurs` on a fully-deserialized AST substitutes every Spur
16//!   via the remap table.
17//! - **RIR walker:** stubbed. RIR's `extra` array packs heterogeneous
18//!   payloads (call args, directives, match arms, …) whose Spur layout
19//!   depends on which inst-data variant owns each region. A correct
20//!   walker requires per-region typed access via Rir's existing
21//!   `get_call_args`/`get_directives`/etc. accessors, threaded through
22//!   each inst variant. The current pipeline never invokes this walker
23//!   because RIR is regenerated from the cached AST rather than
24//!   serialized; the stub remains as a guard against accidental use.
25
26use lasso::{Key, Spur};
27use smallvec::SmallVec;
28
29use gruel_parser::ast::*;
30use gruel_rir::Rir;
31
32/// Type capable of substituting cached `Spur` values via a remap table.
33///
34/// `table[cached_spur.into_usize()]` is the build-interner `Spur` that
35/// should replace the cached one.
36pub trait RemapSpurs {
37    fn remap_spurs(&mut self, table: &[Spur]);
38}
39
40// =====================================================================
41// Leaf impls
42// =====================================================================
43
44impl RemapSpurs for Spur {
45    fn remap_spurs(&mut self, table: &[Spur]) {
46        let idx = self.into_usize();
47        debug_assert!(
48            idx < table.len(),
49            "cached Spur out of remap-table range ({} >= {})",
50            idx,
51            table.len()
52        );
53        *self = table[idx];
54    }
55}
56
57// Primitives and types that contain no Spurs are no-ops. We list each one
58// rather than blanket-impl over `Copy` so that adding a new field type
59// fails to compile until we've decided whether it carries Spurs.
60macro_rules! impl_no_op {
61    ($($t:ty),* $(,)?) => {
62        $(
63            impl RemapSpurs for $t {
64                fn remap_spurs(&mut self, _table: &[Spur]) {}
65            }
66        )*
67    };
68}
69
70impl_no_op!(
71    bool,
72    u8,
73    u16,
74    u32,
75    u64,
76    i8,
77    i16,
78    i32,
79    i64,
80    f32,
81    f64,
82    String,
83    gruel_util::Span,
84    gruel_util::FileId,
85    gruel_util::BinOp,
86    gruel_util::UnaryOp,
87    // ADR-0089: `Doc` only contains a String body and a Span — no Spurs.
88    gruel_parser::ast::Doc,
89);
90
91// =====================================================================
92// Container impls
93// =====================================================================
94
95impl<T: RemapSpurs> RemapSpurs for Box<T> {
96    fn remap_spurs(&mut self, table: &[Spur]) {
97        (**self).remap_spurs(table);
98    }
99}
100
101impl<T: RemapSpurs> RemapSpurs for Option<T> {
102    fn remap_spurs(&mut self, table: &[Spur]) {
103        if let Some(v) = self {
104            v.remap_spurs(table);
105        }
106    }
107}
108
109impl<T: RemapSpurs> RemapSpurs for Vec<T> {
110    fn remap_spurs(&mut self, table: &[Spur]) {
111        for v in self {
112            v.remap_spurs(table);
113        }
114    }
115}
116
117impl<T: RemapSpurs, const N: usize> RemapSpurs for SmallVec<[T; N]>
118where
119    [T; N]: smallvec::Array<Item = T>,
120{
121    fn remap_spurs(&mut self, table: &[Spur]) {
122        for v in self.iter_mut() {
123            v.remap_spurs(table);
124        }
125    }
126}
127
128// =====================================================================
129// AST impls
130//
131// Top-down: Ast → Item → Function/StructDecl/Enum/Interface/Derive/etc.
132// Each type's impl recurses into its Spur-bearing fields. Types whose
133// only fields are Spans / primitives are no-ops via `impl_no_op` above.
134// =====================================================================
135
136impl RemapSpurs for Ast {
137    fn remap_spurs(&mut self, table: &[Spur]) {
138        self.items.remap_spurs(table);
139    }
140}
141
142impl RemapSpurs for Ident {
143    fn remap_spurs(&mut self, table: &[Spur]) {
144        // Exhaustive destructuring: adding a field to Ident becomes a
145        // compile error here until the new field is handled. The same
146        // pattern repeats below for every struct in this file (ADR-0088
147        // follow-up).
148        let Ident { name, span } = self;
149        name.remap_spurs(table);
150        span.remap_spurs(table);
151    }
152}
153
154impl RemapSpurs for Directive {
155    fn remap_spurs(&mut self, table: &[Spur]) {
156        let Directive { name, args, span } = self;
157        name.remap_spurs(table);
158        args.remap_spurs(table);
159        span.remap_spurs(table);
160    }
161}
162
163impl RemapSpurs for DirectiveArg {
164    fn remap_spurs(&mut self, table: &[Spur]) {
165        match self {
166            DirectiveArg::Ident(i) => i.remap_spurs(table),
167            DirectiveArg::String(s) => s.remap_spurs(table),
168        }
169    }
170}
171
172impl RemapSpurs for Item {
173    fn remap_spurs(&mut self, table: &[Spur]) {
174        match self {
175            Item::Function(f) => f.remap_spurs(table),
176            Item::Struct(s) => s.remap_spurs(table),
177            Item::Enum(e) => e.remap_spurs(table),
178            Item::Interface(i) => i.remap_spurs(table),
179            Item::Derive(d) => d.remap_spurs(table),
180            Item::Const(c) => c.remap_spurs(table),
181            Item::LinkExtern(b) => b.remap_spurs(table),
182            Item::Error(_) => {}
183        }
184    }
185}
186
187impl RemapSpurs for gruel_parser::ast::LinkExternBlock {
188    fn remap_spurs(&mut self, table: &[Spur]) {
189        let gruel_parser::ast::LinkExternBlock {
190            doc,
191            library,
192            items,
193            link_mode,
194            span,
195        } = self;
196        doc.remap_spurs(table);
197        library.remap_spurs(table);
198        items.remap_spurs(table);
199        link_mode.remap_spurs(table);
200        span.remap_spurs(table);
201    }
202}
203
204impl RemapSpurs for gruel_parser::ast::LinkMode {
205    fn remap_spurs(&mut self, _table: &[Spur]) {}
206}
207
208impl RemapSpurs for gruel_parser::ast::ExternFn {
209    fn remap_spurs(&mut self, table: &[Spur]) {
210        let gruel_parser::ast::ExternFn {
211            doc,
212            directives,
213            name,
214            params,
215            return_type,
216            span,
217        } = self;
218        doc.remap_spurs(table);
219        directives.remap_spurs(table);
220        name.remap_spurs(table);
221        params.remap_spurs(table);
222        return_type.remap_spurs(table);
223        span.remap_spurs(table);
224    }
225}
226
227impl RemapSpurs for ConstDecl {
228    fn remap_spurs(&mut self, table: &[Spur]) {
229        let ConstDecl {
230            doc,
231            directives,
232            visibility,
233            name,
234            ty,
235            init,
236            span,
237        } = self;
238        doc.remap_spurs(table);
239        directives.remap_spurs(table);
240        visibility.remap_spurs(table);
241        name.remap_spurs(table);
242        ty.remap_spurs(table);
243        init.remap_spurs(table);
244        span.remap_spurs(table);
245    }
246}
247
248impl RemapSpurs for Visibility {
249    fn remap_spurs(&mut self, _table: &[Spur]) {}
250}
251
252impl RemapSpurs for gruel_builtins::Posture {
253    fn remap_spurs(&mut self, _table: &[Spur]) {}
254}
255
256impl RemapSpurs for StructDecl {
257    fn remap_spurs(&mut self, table: &[Spur]) {
258        let StructDecl {
259            doc,
260            directives,
261            visibility,
262            posture,
263            name,
264            fields,
265            methods,
266            span,
267        } = self;
268        doc.remap_spurs(table);
269        directives.remap_spurs(table);
270        visibility.remap_spurs(table);
271        posture.remap_spurs(table);
272        name.remap_spurs(table);
273        fields.remap_spurs(table);
274        methods.remap_spurs(table);
275        span.remap_spurs(table);
276    }
277}
278
279impl RemapSpurs for FieldDecl {
280    fn remap_spurs(&mut self, table: &[Spur]) {
281        let FieldDecl {
282            doc,
283            visibility,
284            name,
285            ty,
286            span,
287        } = self;
288        doc.remap_spurs(table);
289        visibility.remap_spurs(table);
290        name.remap_spurs(table);
291        ty.remap_spurs(table);
292        span.remap_spurs(table);
293    }
294}
295
296impl RemapSpurs for EnumDecl {
297    fn remap_spurs(&mut self, table: &[Spur]) {
298        // ADR-0086: enums can now carry directives (notably `@mark(c)`).
299        // ADR-0088 follow-up: destructuring catches future-added fields
300        // at compile time.
301        let EnumDecl {
302            doc,
303            directives,
304            visibility,
305            posture,
306            name,
307            variants,
308            methods,
309            span,
310        } = self;
311        doc.remap_spurs(table);
312        directives.remap_spurs(table);
313        visibility.remap_spurs(table);
314        posture.remap_spurs(table);
315        name.remap_spurs(table);
316        variants.remap_spurs(table);
317        methods.remap_spurs(table);
318        span.remap_spurs(table);
319    }
320}
321
322impl RemapSpurs for EnumVariant {
323    fn remap_spurs(&mut self, table: &[Spur]) {
324        let EnumVariant {
325            doc,
326            name,
327            kind,
328            span,
329        } = self;
330        doc.remap_spurs(table);
331        name.remap_spurs(table);
332        kind.remap_spurs(table);
333        span.remap_spurs(table);
334    }
335}
336
337impl RemapSpurs for EnumVariantKind {
338    fn remap_spurs(&mut self, table: &[Spur]) {
339        match self {
340            EnumVariantKind::Unit => {}
341            EnumVariantKind::Tuple(types) => types.remap_spurs(table),
342            EnumVariantKind::Struct(fields) => fields.remap_spurs(table),
343        }
344    }
345}
346
347impl RemapSpurs for EnumVariantField {
348    fn remap_spurs(&mut self, table: &[Spur]) {
349        let EnumVariantField {
350            doc,
351            visibility,
352            name,
353            ty,
354            span,
355        } = self;
356        doc.remap_spurs(table);
357        visibility.remap_spurs(table);
358        name.remap_spurs(table);
359        ty.remap_spurs(table);
360        span.remap_spurs(table);
361    }
362}
363
364impl RemapSpurs for InterfaceDecl {
365    fn remap_spurs(&mut self, table: &[Spur]) {
366        // ADR-0088 follow-up: the previous impl didn't remap
367        // `directives`, which would have leaked the same way
368        // `MethodSig` did once we add a directive to an interface
369        // head. Explicit destructuring forces every field to be
370        // handled.
371        let InterfaceDecl {
372            doc,
373            directives,
374            visibility,
375            name,
376            methods,
377            span,
378        } = self;
379        doc.remap_spurs(table);
380        directives.remap_spurs(table);
381        visibility.remap_spurs(table);
382        name.remap_spurs(table);
383        methods.remap_spurs(table);
384        span.remap_spurs(table);
385    }
386}
387
388impl RemapSpurs for MethodSig {
389    fn remap_spurs(&mut self, table: &[Spur]) {
390        let MethodSig {
391            doc,
392            directives,
393            is_unchecked,
394            name,
395            receiver,
396            params,
397            return_type,
398            span,
399        } = self;
400        doc.remap_spurs(table);
401        directives.remap_spurs(table);
402        is_unchecked.remap_spurs(table);
403        name.remap_spurs(table);
404        receiver.remap_spurs(table);
405        params.remap_spurs(table);
406        return_type.remap_spurs(table);
407        span.remap_spurs(table);
408    }
409}
410
411impl RemapSpurs for DeriveDecl {
412    fn remap_spurs(&mut self, table: &[Spur]) {
413        let DeriveDecl {
414            doc,
415            name,
416            methods,
417            span,
418        } = self;
419        doc.remap_spurs(table);
420        name.remap_spurs(table);
421        methods.remap_spurs(table);
422        span.remap_spurs(table);
423    }
424}
425
426impl RemapSpurs for Method {
427    fn remap_spurs(&mut self, table: &[Spur]) {
428        let Method {
429            doc,
430            directives,
431            visibility,
432            is_unchecked,
433            name,
434            receiver,
435            params,
436            return_type,
437            body,
438            span,
439        } = self;
440        doc.remap_spurs(table);
441        directives.remap_spurs(table);
442        visibility.remap_spurs(table);
443        is_unchecked.remap_spurs(table);
444        name.remap_spurs(table);
445        receiver.remap_spurs(table);
446        params.remap_spurs(table);
447        return_type.remap_spurs(table);
448        body.remap_spurs(table);
449        span.remap_spurs(table);
450    }
451}
452
453impl RemapSpurs for SelfParam {
454    fn remap_spurs(&mut self, table: &[Spur]) {
455        let SelfParam { kind, span } = self;
456        kind.remap_spurs(table);
457        span.remap_spurs(table);
458    }
459}
460
461impl RemapSpurs for SelfReceiverKind {
462    fn remap_spurs(&mut self, _table: &[Spur]) {}
463}
464
465impl RemapSpurs for Function {
466    fn remap_spurs(&mut self, table: &[Spur]) {
467        let Function {
468            doc,
469            directives,
470            visibility,
471            is_unchecked,
472            name,
473            params,
474            return_type,
475            body,
476            span,
477        } = self;
478        doc.remap_spurs(table);
479        directives.remap_spurs(table);
480        visibility.remap_spurs(table);
481        is_unchecked.remap_spurs(table);
482        name.remap_spurs(table);
483        params.remap_spurs(table);
484        return_type.remap_spurs(table);
485        body.remap_spurs(table);
486        span.remap_spurs(table);
487    }
488}
489
490impl RemapSpurs for ParamMode {
491    fn remap_spurs(&mut self, _table: &[Spur]) {}
492}
493
494impl RemapSpurs for Param {
495    fn remap_spurs(&mut self, table: &[Spur]) {
496        let Param {
497            is_comptime,
498            mode,
499            name,
500            ty,
501            span,
502        } = self;
503        is_comptime.remap_spurs(table);
504        mode.remap_spurs(table);
505        name.remap_spurs(table);
506        ty.remap_spurs(table);
507        span.remap_spurs(table);
508    }
509}
510
511impl RemapSpurs for TypeExpr {
512    fn remap_spurs(&mut self, table: &[Spur]) {
513        match self {
514            TypeExpr::Named(i) => i.remap_spurs(table),
515            TypeExpr::Unit(_) | TypeExpr::Never(_) => {}
516            TypeExpr::Array { element, .. } => element.remap_spurs(table),
517            TypeExpr::AnonymousStruct {
518                directives,
519                fields,
520                methods,
521                ..
522            } => {
523                directives.remap_spurs(table);
524                fields.remap_spurs(table);
525                methods.remap_spurs(table);
526            }
527            TypeExpr::AnonymousEnum {
528                directives,
529                variants,
530                methods,
531                ..
532            } => {
533                directives.remap_spurs(table);
534                variants.remap_spurs(table);
535                methods.remap_spurs(table);
536            }
537            TypeExpr::AnonymousInterface { methods, .. } => methods.remap_spurs(table),
538            TypeExpr::TypeCall { callee, args, .. } => {
539                callee.remap_spurs(table);
540                args.remap_spurs(table);
541            }
542            TypeExpr::Tuple { elems, .. } => elems.remap_spurs(table),
543        }
544    }
545}
546
547impl RemapSpurs for AnonStructField {
548    fn remap_spurs(&mut self, table: &[Spur]) {
549        let AnonStructField {
550            doc,
551            name,
552            ty,
553            span,
554        } = self;
555        doc.remap_spurs(table);
556        name.remap_spurs(table);
557        ty.remap_spurs(table);
558        span.remap_spurs(table);
559    }
560}
561
562// =====================================================================
563// Expr / Statement / Pattern walker.
564//
565// The bulk of this is mechanical: each Expr variant carries a sub-type
566// whose own RemapSpurs impl recurses into its Spur-bearing fields. The
567// types whose only Spur-bearing fields are an Ident (like FieldExpr or
568// CallExpr) recurse via their `name`/`receiver`/etc. fields; the
569// literal types (IntLit, FloatLit, BoolLit, UnitLit, NegIntLit, CharLit,
570// SelfExpr, BreakExpr, ContinueExpr) carry no Spurs and are no-ops.
571// =====================================================================
572
573// Literal expressions and other zero-Spur types: explicit no-op impls
574// so the macro-generated `impl_no_op!` doesn't grow huge.
575impl RemapSpurs for IntLit {
576    fn remap_spurs(&mut self, _table: &[Spur]) {}
577}
578impl RemapSpurs for FloatLit {
579    fn remap_spurs(&mut self, _table: &[Spur]) {}
580}
581impl RemapSpurs for BoolLit {
582    fn remap_spurs(&mut self, _table: &[Spur]) {}
583}
584impl RemapSpurs for CharLit {
585    fn remap_spurs(&mut self, _table: &[Spur]) {}
586}
587impl RemapSpurs for UnitLit {
588    fn remap_spurs(&mut self, _table: &[Spur]) {}
589}
590impl RemapSpurs for NegIntLit {
591    fn remap_spurs(&mut self, _table: &[Spur]) {}
592}
593impl RemapSpurs for SelfExpr {
594    fn remap_spurs(&mut self, _table: &[Spur]) {}
595}
596impl RemapSpurs for BreakExpr {
597    fn remap_spurs(&mut self, _table: &[Spur]) {}
598}
599impl RemapSpurs for ContinueExpr {
600    fn remap_spurs(&mut self, _table: &[Spur]) {}
601}
602impl RemapSpurs for BinaryOp {
603    fn remap_spurs(&mut self, _table: &[Spur]) {}
604}
605impl RemapSpurs for UnaryOp {
606    fn remap_spurs(&mut self, _table: &[Spur]) {}
607}
608impl RemapSpurs for ArgMode {
609    fn remap_spurs(&mut self, _table: &[Spur]) {}
610}
611
612// StringLit carries a Spur for its interned string contents.
613impl RemapSpurs for StringLit {
614    fn remap_spurs(&mut self, table: &[Spur]) {
615        let StringLit { value, span } = self;
616        value.remap_spurs(table);
617        span.remap_spurs(table);
618    }
619}
620
621impl RemapSpurs for BinaryExpr {
622    fn remap_spurs(&mut self, table: &[Spur]) {
623        let BinaryExpr {
624            left,
625            op,
626            right,
627            span,
628        } = self;
629        left.remap_spurs(table);
630        op.remap_spurs(table);
631        right.remap_spurs(table);
632        span.remap_spurs(table);
633    }
634}
635
636impl RemapSpurs for UnaryExpr {
637    fn remap_spurs(&mut self, table: &[Spur]) {
638        let UnaryExpr { op, operand, span } = self;
639        op.remap_spurs(table);
640        operand.remap_spurs(table);
641        span.remap_spurs(table);
642    }
643}
644
645impl RemapSpurs for ParenExpr {
646    fn remap_spurs(&mut self, table: &[Spur]) {
647        let ParenExpr { inner, span } = self;
648        inner.remap_spurs(table);
649        span.remap_spurs(table);
650    }
651}
652
653impl RemapSpurs for BlockExpr {
654    fn remap_spurs(&mut self, table: &[Spur]) {
655        let BlockExpr {
656            statements,
657            expr,
658            span,
659        } = self;
660        statements.remap_spurs(table);
661        expr.remap_spurs(table);
662        span.remap_spurs(table);
663    }
664}
665
666impl RemapSpurs for IfExpr {
667    fn remap_spurs(&mut self, table: &[Spur]) {
668        let IfExpr {
669            cond,
670            then_block,
671            else_block,
672            span,
673            is_comptime,
674        } = self;
675        cond.remap_spurs(table);
676        then_block.remap_spurs(table);
677        else_block.remap_spurs(table);
678        span.remap_spurs(table);
679        is_comptime.remap_spurs(table);
680    }
681}
682
683impl RemapSpurs for MatchExpr {
684    fn remap_spurs(&mut self, table: &[Spur]) {
685        let MatchExpr {
686            scrutinee,
687            arms,
688            span,
689        } = self;
690        scrutinee.remap_spurs(table);
691        arms.remap_spurs(table);
692        span.remap_spurs(table);
693    }
694}
695
696impl RemapSpurs for MatchArm {
697    fn remap_spurs(&mut self, table: &[Spur]) {
698        let MatchArm {
699            pattern,
700            body,
701            span,
702        } = self;
703        pattern.remap_spurs(table);
704        body.remap_spurs(table);
705        span.remap_spurs(table);
706    }
707}
708
709impl RemapSpurs for Pattern {
710    fn remap_spurs(&mut self, table: &[Spur]) {
711        match self {
712            Pattern::Wildcard(_) => {}
713            Pattern::Ident { name, .. } => name.remap_spurs(table),
714            Pattern::Int(_) | Pattern::NegInt(_) | Pattern::Bool(_) => {}
715            Pattern::Path(p) => p.remap_spurs(table),
716            Pattern::DataVariant {
717                base,
718                type_name,
719                variant,
720                fields,
721                ..
722            } => {
723                base.remap_spurs(table);
724                type_name.remap_spurs(table);
725                variant.remap_spurs(table);
726                fields.remap_spurs(table);
727            }
728            Pattern::StructVariant {
729                base,
730                type_name,
731                variant,
732                fields,
733                ..
734            } => {
735                base.remap_spurs(table);
736                type_name.remap_spurs(table);
737                variant.remap_spurs(table);
738                fields.remap_spurs(table);
739            }
740            Pattern::Struct {
741                type_name, fields, ..
742            } => {
743                type_name.remap_spurs(table);
744                fields.remap_spurs(table);
745            }
746            Pattern::Tuple { elems, .. } => elems.remap_spurs(table),
747            Pattern::ComptimeUnrollArm {
748                binding, iterable, ..
749            } => {
750                binding.remap_spurs(table);
751                iterable.remap_spurs(table);
752            }
753        }
754    }
755}
756
757impl RemapSpurs for TupleElemPattern {
758    fn remap_spurs(&mut self, table: &[Spur]) {
759        match self {
760            TupleElemPattern::Pattern(p) => p.remap_spurs(table),
761            TupleElemPattern::Rest(_) => {}
762        }
763    }
764}
765
766impl RemapSpurs for FieldPattern {
767    fn remap_spurs(&mut self, table: &[Spur]) {
768        let FieldPattern {
769            field_name,
770            sub,
771            is_mut,
772            span,
773        } = self;
774        field_name.remap_spurs(table);
775        sub.remap_spurs(table);
776        is_mut.remap_spurs(table);
777        span.remap_spurs(table);
778    }
779}
780
781impl RemapSpurs for PathPattern {
782    fn remap_spurs(&mut self, table: &[Spur]) {
783        let PathPattern {
784            base,
785            type_name,
786            variant,
787            span,
788        } = self;
789        base.remap_spurs(table);
790        type_name.remap_spurs(table);
791        variant.remap_spurs(table);
792        span.remap_spurs(table);
793    }
794}
795
796impl RemapSpurs for CallArg {
797    fn remap_spurs(&mut self, table: &[Spur]) {
798        let CallArg { mode, expr, span } = self;
799        mode.remap_spurs(table);
800        expr.remap_spurs(table);
801        span.remap_spurs(table);
802    }
803}
804
805impl RemapSpurs for CallExpr {
806    fn remap_spurs(&mut self, table: &[Spur]) {
807        let CallExpr { name, args, span } = self;
808        name.remap_spurs(table);
809        args.remap_spurs(table);
810        span.remap_spurs(table);
811    }
812}
813
814impl RemapSpurs for IntrinsicArg {
815    fn remap_spurs(&mut self, table: &[Spur]) {
816        match self {
817            IntrinsicArg::Expr(e) => e.remap_spurs(table),
818            IntrinsicArg::Type(t) => t.remap_spurs(table),
819        }
820    }
821}
822
823impl RemapSpurs for IntrinsicCallExpr {
824    fn remap_spurs(&mut self, table: &[Spur]) {
825        let IntrinsicCallExpr { name, args, span } = self;
826        name.remap_spurs(table);
827        args.remap_spurs(table);
828        span.remap_spurs(table);
829    }
830}
831
832impl RemapSpurs for StructLitExpr {
833    fn remap_spurs(&mut self, table: &[Spur]) {
834        let StructLitExpr {
835            base,
836            name,
837            fields,
838            span,
839        } = self;
840        base.remap_spurs(table);
841        name.remap_spurs(table);
842        fields.remap_spurs(table);
843        span.remap_spurs(table);
844    }
845}
846
847impl RemapSpurs for FieldInit {
848    fn remap_spurs(&mut self, table: &[Spur]) {
849        let FieldInit { name, value, span } = self;
850        name.remap_spurs(table);
851        value.remap_spurs(table);
852        span.remap_spurs(table);
853    }
854}
855
856impl RemapSpurs for TupleExpr {
857    fn remap_spurs(&mut self, table: &[Spur]) {
858        let TupleExpr { elems, span } = self;
859        elems.remap_spurs(table);
860        span.remap_spurs(table);
861    }
862}
863
864impl RemapSpurs for AnonFnExpr {
865    fn remap_spurs(&mut self, table: &[Spur]) {
866        let AnonFnExpr {
867            params,
868            return_type,
869            body,
870            span,
871        } = self;
872        params.remap_spurs(table);
873        return_type.remap_spurs(table);
874        body.remap_spurs(table);
875        span.remap_spurs(table);
876    }
877}
878
879impl RemapSpurs for TupleIndexExpr {
880    fn remap_spurs(&mut self, table: &[Spur]) {
881        let TupleIndexExpr {
882            base,
883            index,
884            span,
885            index_span,
886        } = self;
887        base.remap_spurs(table);
888        index.remap_spurs(table);
889        span.remap_spurs(table);
890        index_span.remap_spurs(table);
891    }
892}
893
894impl RemapSpurs for FieldExpr {
895    fn remap_spurs(&mut self, table: &[Spur]) {
896        let FieldExpr { base, field, span } = self;
897        base.remap_spurs(table);
898        field.remap_spurs(table);
899        span.remap_spurs(table);
900    }
901}
902
903impl RemapSpurs for MethodCallExpr {
904    fn remap_spurs(&mut self, table: &[Spur]) {
905        let MethodCallExpr {
906            receiver,
907            method,
908            args,
909            span,
910        } = self;
911        receiver.remap_spurs(table);
912        method.remap_spurs(table);
913        args.remap_spurs(table);
914        span.remap_spurs(table);
915    }
916}
917
918impl RemapSpurs for ArrayLitExpr {
919    fn remap_spurs(&mut self, table: &[Spur]) {
920        let ArrayLitExpr { elements, span } = self;
921        elements.remap_spurs(table);
922        span.remap_spurs(table);
923    }
924}
925
926impl RemapSpurs for IndexExpr {
927    fn remap_spurs(&mut self, table: &[Spur]) {
928        let IndexExpr { base, index, span } = self;
929        base.remap_spurs(table);
930        index.remap_spurs(table);
931        span.remap_spurs(table);
932    }
933}
934
935impl RemapSpurs for RangeExpr {
936    fn remap_spurs(&mut self, table: &[Spur]) {
937        let RangeExpr { lo, hi, span } = self;
938        lo.remap_spurs(table);
939        hi.remap_spurs(table);
940        span.remap_spurs(table);
941    }
942}
943
944impl RemapSpurs for PathExpr {
945    fn remap_spurs(&mut self, table: &[Spur]) {
946        let PathExpr {
947            base,
948            type_name,
949            variant,
950            span,
951        } = self;
952        base.remap_spurs(table);
953        type_name.remap_spurs(table);
954        variant.remap_spurs(table);
955        span.remap_spurs(table);
956    }
957}
958
959impl RemapSpurs for EnumStructLitExpr {
960    fn remap_spurs(&mut self, table: &[Spur]) {
961        let EnumStructLitExpr {
962            base,
963            type_name,
964            variant,
965            fields,
966            span,
967        } = self;
968        base.remap_spurs(table);
969        type_name.remap_spurs(table);
970        variant.remap_spurs(table);
971        fields.remap_spurs(table);
972        span.remap_spurs(table);
973    }
974}
975
976impl RemapSpurs for AssocFnCallExpr {
977    fn remap_spurs(&mut self, table: &[Spur]) {
978        let AssocFnCallExpr {
979            base,
980            type_name,
981            type_args,
982            function,
983            args,
984            span,
985        } = self;
986        base.remap_spurs(table);
987        type_name.remap_spurs(table);
988        type_args.remap_spurs(table);
989        function.remap_spurs(table);
990        args.remap_spurs(table);
991        span.remap_spurs(table);
992    }
993}
994
995impl RemapSpurs for ComptimeBlockExpr {
996    fn remap_spurs(&mut self, table: &[Spur]) {
997        let ComptimeBlockExpr { expr, span } = self;
998        expr.remap_spurs(table);
999        span.remap_spurs(table);
1000    }
1001}
1002
1003impl RemapSpurs for ComptimeUnrollForExpr {
1004    fn remap_spurs(&mut self, table: &[Spur]) {
1005        let ComptimeUnrollForExpr {
1006            binding,
1007            iterable,
1008            body,
1009            span,
1010        } = self;
1011        binding.remap_spurs(table);
1012        iterable.remap_spurs(table);
1013        body.remap_spurs(table);
1014        span.remap_spurs(table);
1015    }
1016}
1017
1018impl RemapSpurs for CheckedBlockExpr {
1019    fn remap_spurs(&mut self, table: &[Spur]) {
1020        let CheckedBlockExpr { expr, span } = self;
1021        expr.remap_spurs(table);
1022        span.remap_spurs(table);
1023    }
1024}
1025
1026impl RemapSpurs for TypeLitExpr {
1027    fn remap_spurs(&mut self, table: &[Spur]) {
1028        let TypeLitExpr { type_expr, span } = self;
1029        type_expr.remap_spurs(table);
1030        span.remap_spurs(table);
1031    }
1032}
1033
1034impl RemapSpurs for WhileExpr {
1035    fn remap_spurs(&mut self, table: &[Spur]) {
1036        let WhileExpr { cond, body, span } = self;
1037        cond.remap_spurs(table);
1038        body.remap_spurs(table);
1039        span.remap_spurs(table);
1040    }
1041}
1042
1043impl RemapSpurs for ForExpr {
1044    fn remap_spurs(&mut self, table: &[Spur]) {
1045        let ForExpr {
1046            binding,
1047            is_mut,
1048            iterable,
1049            body,
1050            span,
1051        } = self;
1052        binding.remap_spurs(table);
1053        is_mut.remap_spurs(table);
1054        iterable.remap_spurs(table);
1055        body.remap_spurs(table);
1056        span.remap_spurs(table);
1057    }
1058}
1059
1060impl RemapSpurs for LoopExpr {
1061    fn remap_spurs(&mut self, table: &[Spur]) {
1062        let LoopExpr { body, span } = self;
1063        body.remap_spurs(table);
1064        span.remap_spurs(table);
1065    }
1066}
1067
1068impl RemapSpurs for ReturnExpr {
1069    fn remap_spurs(&mut self, table: &[Spur]) {
1070        let ReturnExpr { value, span } = self;
1071        value.remap_spurs(table);
1072        span.remap_spurs(table);
1073    }
1074}
1075
1076impl RemapSpurs for Expr {
1077    fn remap_spurs(&mut self, table: &[Spur]) {
1078        match self {
1079            Expr::Int(_) | Expr::Float(_) | Expr::Bool(_) | Expr::Char(_) | Expr::Unit(_) => {}
1080            Expr::String(s) => s.remap_spurs(table),
1081            Expr::Ident(i) => i.remap_spurs(table),
1082            Expr::Binary(b) => b.remap_spurs(table),
1083            Expr::Unary(u) => u.remap_spurs(table),
1084            Expr::Paren(p) => p.remap_spurs(table),
1085            Expr::Block(b) => b.remap_spurs(table),
1086            Expr::If(i) => i.remap_spurs(table),
1087            Expr::Match(m) => m.remap_spurs(table),
1088            Expr::While(w) => w.remap_spurs(table),
1089            Expr::For(f) => f.remap_spurs(table),
1090            Expr::Loop(l) => l.remap_spurs(table),
1091            Expr::Call(c) => c.remap_spurs(table),
1092            Expr::Break(_) | Expr::Continue(_) => {}
1093            Expr::Return(r) => r.remap_spurs(table),
1094            Expr::StructLit(s) => s.remap_spurs(table),
1095            Expr::Field(f) => f.remap_spurs(table),
1096            Expr::MethodCall(m) => m.remap_spurs(table),
1097            Expr::IntrinsicCall(i) => i.remap_spurs(table),
1098            Expr::ArrayLit(a) => a.remap_spurs(table),
1099            Expr::Index(i) => i.remap_spurs(table),
1100            Expr::Path(p) => p.remap_spurs(table),
1101            Expr::EnumStructLit(e) => e.remap_spurs(table),
1102            Expr::AssocFnCall(a) => a.remap_spurs(table),
1103            Expr::SelfExpr(_) => {}
1104            Expr::Comptime(c) => c.remap_spurs(table),
1105            Expr::ComptimeUnrollFor(c) => c.remap_spurs(table),
1106            Expr::Checked(c) => c.remap_spurs(table),
1107            Expr::TypeLit(t) => t.remap_spurs(table),
1108            Expr::Tuple(t) => t.remap_spurs(table),
1109            Expr::TupleIndex(t) => t.remap_spurs(table),
1110            Expr::Range(r) => r.remap_spurs(table),
1111            Expr::AnonFn(a) => a.remap_spurs(table),
1112            Expr::Error(_) => {}
1113        }
1114    }
1115}
1116
1117impl RemapSpurs for Statement {
1118    fn remap_spurs(&mut self, table: &[Spur]) {
1119        match self {
1120            Statement::Let(l) => l.remap_spurs(table),
1121            Statement::Assign(a) => a.remap_spurs(table),
1122            Statement::Expr(e) => e.remap_spurs(table),
1123        }
1124    }
1125}
1126
1127impl RemapSpurs for LetStatement {
1128    fn remap_spurs(&mut self, table: &[Spur]) {
1129        let LetStatement {
1130            directives,
1131            is_mut,
1132            pattern,
1133            ty,
1134            init,
1135            span,
1136        } = self;
1137        directives.remap_spurs(table);
1138        is_mut.remap_spurs(table);
1139        pattern.remap_spurs(table);
1140        ty.remap_spurs(table);
1141        init.remap_spurs(table);
1142        span.remap_spurs(table);
1143    }
1144}
1145
1146impl RemapSpurs for AssignStatement {
1147    fn remap_spurs(&mut self, table: &[Spur]) {
1148        let AssignStatement {
1149            target,
1150            value,
1151            span,
1152        } = self;
1153        target.remap_spurs(table);
1154        value.remap_spurs(table);
1155        span.remap_spurs(table);
1156    }
1157}
1158
1159impl RemapSpurs for AssignTarget {
1160    fn remap_spurs(&mut self, table: &[Spur]) {
1161        match self {
1162            AssignTarget::Var(i) => i.remap_spurs(table),
1163            AssignTarget::Field(f) => f.remap_spurs(table),
1164            AssignTarget::Index(i) => i.remap_spurs(table),
1165        }
1166    }
1167}
1168
1169// =====================================================================
1170// RIR impl — RIR is tabular (Vec<Inst> + extra: Vec<u32>), and most Spur
1171// values live inside opaque instruction payloads. Real walker requires
1172// accessor methods on Rir that aren't currently exposed (instructions
1173// and extra are private). Phase 2b wiring lands the accessors and the
1174// real walker together.
1175// =====================================================================
1176
1177impl RemapSpurs for Rir {
1178    fn remap_spurs(&mut self, _table: &[Spur]) {
1179        debug_assert!(
1180            false,
1181            "RemapSpurs::Rir not yet implemented — Phase 2b stub. \
1182             Cache pipeline currently regenerates RIR from cached AST \
1183             rather than serializing RIR, so this walker should not \
1184             be reached."
1185        );
1186    }
1187}
1188
1189#[cfg(test)]
1190mod tests {
1191    use super::*;
1192    use gruel_util::Span;
1193
1194    #[test]
1195    fn ident_remap_substitutes_spur() {
1196        let table = vec![
1197            Spur::try_from_usize(7).unwrap(),
1198            Spur::try_from_usize(8).unwrap(),
1199        ];
1200        let mut id = Ident {
1201            name: Spur::try_from_usize(1).unwrap(),
1202            span: Span::default(),
1203        };
1204        id.remap_spurs(&table);
1205        assert_eq!(id.name, Spur::try_from_usize(8).unwrap());
1206    }
1207
1208    #[test]
1209    fn vec_of_idents_remaps_each() {
1210        let table = vec![
1211            Spur::try_from_usize(100).unwrap(),
1212            Spur::try_from_usize(200).unwrap(),
1213        ];
1214        let mut idents = vec![
1215            Ident {
1216                name: Spur::try_from_usize(0).unwrap(),
1217                span: Span::default(),
1218            },
1219            Ident {
1220                name: Spur::try_from_usize(1).unwrap(),
1221                span: Span::default(),
1222            },
1223        ];
1224        idents.remap_spurs(&table);
1225        assert_eq!(idents[0].name, Spur::try_from_usize(100).unwrap());
1226        assert_eq!(idents[1].name, Spur::try_from_usize(200).unwrap());
1227    }
1228
1229    #[test]
1230    fn empty_ast_remap_is_noop() {
1231        let mut ast = Ast {
1232            module_doc: None,
1233            items: Vec::new(),
1234        };
1235        ast.remap_spurs(&[]);
1236        assert!(ast.items.is_empty());
1237    }
1238
1239    #[test]
1240    fn nested_expr_remap_substitutes_idents_at_every_level() {
1241        use gruel_parser::ast::{
1242            BinaryExpr, BinaryOp, BlockExpr, CallArg, CallExpr, Function, IntLit, Param, ParamMode,
1243            Statement, TypeExpr, Visibility,
1244        };
1245        use smallvec::smallvec;
1246
1247        // Hand-build a small AST to exercise the deep walker:
1248        //
1249        //   fn add(a: i32, b: i32) -> i32 {
1250        //       sum(a, b)
1251        //   }
1252        //
1253        // Spurs: 0=add, 1=a, 2=i32, 3=b, 4=sum
1254        let s = |i: usize| Spur::try_from_usize(i).unwrap();
1255        let id = |sp: Spur| Ident {
1256            name: sp,
1257            span: Span::default(),
1258        };
1259
1260        let body_call = Expr::Call(CallExpr {
1261            name: id(s(4)),
1262            args: vec![
1263                CallArg {
1264                    mode: ArgMode::Normal,
1265                    expr: Expr::Ident(id(s(1))),
1266                    span: Span::default(),
1267                },
1268                CallArg {
1269                    mode: ArgMode::Normal,
1270                    expr: Expr::Ident(id(s(3))),
1271                    span: Span::default(),
1272                },
1273            ],
1274            span: Span::default(),
1275        });
1276
1277        let body = Box::new(Expr::Block(BlockExpr {
1278            statements: vec![Statement::Expr(Expr::Binary(BinaryExpr {
1279                left: Box::new(Expr::Ident(id(s(1)))),
1280                op: BinaryOp::Add,
1281                right: Box::new(Expr::Int(IntLit {
1282                    value: 1,
1283                    span: Span::default(),
1284                })),
1285                span: Span::default(),
1286            }))],
1287            expr: Box::new(body_call),
1288            span: Span::default(),
1289        }));
1290
1291        let mut ast = Ast {
1292            module_doc: None,
1293            items: vec![Item::Function(Function {
1294                doc: None,
1295                directives: smallvec![],
1296                visibility: Visibility::Public,
1297                is_unchecked: false,
1298                name: id(s(0)),
1299                params: vec![
1300                    Param {
1301                        is_comptime: false,
1302                        mode: ParamMode::Normal,
1303                        name: id(s(1)),
1304                        ty: TypeExpr::Named(id(s(2))),
1305                        span: Span::default(),
1306                    },
1307                    Param {
1308                        is_comptime: false,
1309                        mode: ParamMode::Normal,
1310                        name: id(s(3)),
1311                        ty: TypeExpr::Named(id(s(2))),
1312                        span: Span::default(),
1313                    },
1314                ],
1315                return_type: Some(TypeExpr::Named(id(s(2)))),
1316                body: Expr::Block(BlockExpr {
1317                    statements: Vec::new(),
1318                    expr: body,
1319                    span: Span::default(),
1320                }),
1321                span: Span::default(),
1322            })],
1323        };
1324
1325        // Remap: shift every Spur by +10 (so 0→10, 1→11, 2→12, 3→13, 4→14).
1326        let table: Vec<Spur> = (0..5).map(|i| s(i + 10)).collect();
1327        ast.remap_spurs(&table);
1328
1329        // Walk the resulting AST and check that every Ident.name is in
1330        // the new range (10..=14).
1331        fn collect_idents(e: &Expr, out: &mut Vec<Spur>) {
1332            match e {
1333                Expr::Ident(i) => out.push(i.name),
1334                Expr::Binary(b) => {
1335                    collect_idents(&b.left, out);
1336                    collect_idents(&b.right, out);
1337                }
1338                Expr::Call(c) => {
1339                    out.push(c.name.name);
1340                    for a in &c.args {
1341                        collect_idents(&a.expr, out);
1342                    }
1343                }
1344                Expr::Block(b) => {
1345                    for s in &b.statements {
1346                        if let Statement::Expr(e) = s {
1347                            collect_idents(e, out);
1348                        }
1349                    }
1350                    collect_idents(&b.expr, out);
1351                }
1352                _ => {}
1353            }
1354        }
1355
1356        let Item::Function(f) = &ast.items[0] else {
1357            panic!()
1358        };
1359        assert_eq!(f.name.name, s(10));
1360        assert_eq!(f.params[0].name.name, s(11));
1361        assert_eq!(f.params[1].name.name, s(13));
1362        let mut idents = Vec::new();
1363        collect_idents(&f.body, &mut idents);
1364        // We expect to see Spurs 11, 14, 11, 13 (from the body Binary + Call)
1365        // — i.e. all in the remapped 10+ range.
1366        assert!(
1367            idents.iter().all(|sp| sp.into_usize() >= 10),
1368            "expected every Spur to be in remapped range, got {:?}",
1369            idents
1370        );
1371    }
1372}