Skip to main content

shape_value/
value.rs

1//! Surviving typed value types after the strict-typing bulldozer.
2//!
3//! Most of this module's content (VMArray, Upvalue, HostCallable, PrintResult,
4//! PrintSpan) was deleted along with the v1 ValueWord representation. What
5//! remains are the pure-data filter / vtable types that don't reference any
6//! dynamic-word machinery.
7
8use smallvec::SmallVec;
9use std::collections::HashMap;
10
11/// Comparison operator for filter expressions.
12#[derive(Debug, Clone, PartialEq)]
13pub enum FilterOp {
14    Eq,
15    Neq,
16    Gt,
17    Gte,
18    Lt,
19    Lte,
20}
21
22/// A literal value in a filter expression (for SQL generation).
23#[derive(Debug, Clone, PartialEq)]
24pub enum FilterLiteral {
25    Int(i64),
26    Float(f64),
27    String(String),
28    Bool(bool),
29    Null,
30}
31
32/// Filter expression tree for SQL pushdown.
33///
34/// Built from comparisons and logical operations. Represents typed
35/// column-vs-literal predicates suitable for pushdown to a SQL backend.
36#[derive(Debug, Clone, PartialEq)]
37pub enum FilterNode {
38    /// Column compared to a literal value.
39    Compare {
40        column: String,
41        op: FilterOp,
42        value: FilterLiteral,
43    },
44    /// Logical AND of two filter nodes.
45    And(Box<FilterNode>, Box<FilterNode>),
46    /// Logical OR of two filter nodes.
47    Or(Box<FilterNode>, Box<FilterNode>),
48    /// Logical NOT of a filter node.
49    Not(Box<FilterNode>),
50}
51
52/// Virtual method table for trait objects.
53///
54/// Maps method names to entries describing how each method dispatches
55/// through a `dyn Trait` receiver. Built at vtable-construction time
56/// (compile-time per `(impl Trait for Type)` pair) and shared via
57/// `Arc<VTable>` from the `TraitObjectStorage` fat-pointer carrier.
58///
59/// ADR-006 §2.7.24 Q25.C.5 — extended shape (W17-trait-object-storage,
60/// 2026-05-11): the legacy 2-variant `VTableEntry { FunctionId, Closure }`
61/// is widened to 6 variants (`Direct` / `Closure` / `BoxedReturn` /
62/// `SelfArg` / `Generic` / `Compound`) to encode the per-method
63/// rewriting that `Erase_T` performs on the method signature when the
64/// trait is used as `dyn T`. Per-(impl, method) thunks are emitted at
65/// vtable-construction time; the `thunk_id` fields below name them.
66#[derive(Debug, Clone)]
67pub struct VTable {
68    /// Trait names this vtable implements. Supports multi-trait
69    /// inheritance — when an impl spans `T: A + B + C`, all three trait
70    /// names appear here in order so the §Q25.C.2 vtable-identity check
71    /// can compare across the inheritance chain.
72    pub trait_names: Vec<String>,
73    /// Concrete-type discriminator for the underlying boxed value.
74    /// Enables the §Q25.C.2 `Self`-arg runtime check (`Arc::ptr_eq` on
75    /// vtables is a tighter equality, but `concrete_type_id` is needed
76    /// for the cross-vtable comparison error message and IC-stabilization
77    /// key in §Q25.C.6).
78    pub concrete_type_id: u32,
79    /// Map from method name to dispatch entry.
80    pub methods: HashMap<String, VTableEntry>,
81}
82
83/// How a single trait method dispatches through `dyn T`.
84///
85/// Six variants cover the cross-product of (no rewriting / Self-in-return /
86/// Self-in-arg / method-generic) per ADR-006 §2.7.24 Q25.C.5. The plain
87/// (function_id) and (function_id + type_id) entries preserve the pre-§2.7.24
88/// vtable shapes so existing emit-tier wiring continues to compile.
89///
90/// **Thunks vs function ids**: variants other than `Direct` / `Closure`
91/// carry a `thunk_id` rather than a raw function id. The compiler-side
92/// vtable-construction tier generates one thunk per `(impl, method)` pair
93/// whose Erase_T-rewritten signature differs from the underlying impl
94/// method; the thunk does the auto-boxing on return / vtable-identity
95/// check / TypeInfo dispatch / etc., then tail-calls the impl method.
96#[derive(Debug, Clone)]
97pub enum VTableEntry {
98    /// Direct call — no `Erase_T` rewriting needed (no `Self` in
99    /// non-receiver position, no method-generic parameters). Dispatch
100    /// is a simple function-id call. Preserves the pre-§2.7.24
101    /// `VTableEntry::FunctionId(u16)` shape (renamed `function_id`
102    /// field for forward consistency).
103    Direct { function_id: u16 },
104
105    /// Pre-existing closure entry (W7 closure trait impls).
106    ///
107    /// VTable closure entries carry `(function_id, type_id)`; dispatch
108    /// allocates a fresh `OwnedClosureBlock` per call via the program's
109    /// `closure_function_layouts` registry so the call convention sees
110    /// the same raw `TypedClosureHeader` shape that `op_make_closure`
111    /// emits.
112    Closure { function_id: u32, type_id: u32 },
113
114    /// `Self` (or `Self::A`) appears in return position. The thunk
115    /// wraps the impl's concrete return value back into a `dyn T`
116    /// carrier at each `wrap_targets` path before returning.
117    BoxedReturn {
118        thunk_id: u16,
119        /// One entry per place the impl's signature names `Self` /
120        /// `Self::A` inside its (possibly structural) return type.
121        /// E.g. for `fn try_clone(&self) -> Result<Self, Error>`,
122        /// `wrap_targets = [WrapTarget { path: [0], wrap_as_trait_id }]`.
123        wrap_targets: SmallVec<[WrapTarget; 2]>,
124    },
125
126    /// `Self` appears in argument position. The thunk checks
127    /// vtable-identity (per §Q25.C.2) between `self`'s vtable and each
128    /// `Self`-typed argument's vtable before forwarding to the impl
129    /// method.
130    SelfArg {
131        thunk_id: u16,
132        /// Argument indices (0-based, excluding the receiver) at which
133        /// the impl's signature names `Self` directly. Each gets one
134        /// `Arc::ptr_eq` check on its vtable before dispatch.
135        self_arg_positions: SmallVec<[u8; 4]>,
136    },
137
138    /// Method has type parameters (`fn method<G: Bound>(&self, g: G)`).
139    /// The thunk consumes `type_param_count` `&TypeInfo` parameters
140    /// alongside the regular arguments per §Q25.C.3 and dispatches on
141    /// `concrete_type_id` for each.
142    Generic {
143        thunk_id: u16,
144        type_param_count: u8,
145    },
146
147    /// Combination of `BoxedReturn` / `SelfArg` / `Generic`. The thunk
148    /// dispatches per `flags` bit set.
149    Compound {
150        thunk_id: u16,
151        flags: VTableEntryFlags,
152        wrap_targets: SmallVec<[WrapTarget; 2]>,
153        self_arg_positions: SmallVec<[u8; 4]>,
154        type_param_count: u8,
155    },
156}
157
158/// One auto-boxing site inside an `Erase_T`-rewritten return type.
159///
160/// `path` walks the generic-argument tree from the outer return type:
161/// - `Self` in return position → `path = []` (the whole return)
162/// - `Result<Self, Error>` → `path = [0]` (the Ok arm)
163/// - `(Self, Self)` (tuple) → two entries, `path = [0]` and `path = [1]`
164/// - `HashMap<int, Self>` → `path = [1]` (the value type)
165/// - `Option<Result<Self, Error>>` → `path = [0, 0]` (the inner Ok arm)
166///
167/// `wrap_as_trait_id` is the trait the boxed value should advertise
168/// itself under — usually the receiver trait's id, but for `Self::A`
169/// (associated-type return with bound `Bound`) the bound's trait id
170/// per §Q25.C.1 row 4.
171#[derive(Debug, Clone)]
172pub struct WrapTarget {
173    /// Argument-index path into the structural return type.
174    pub path: SmallVec<[u8; 4]>,
175    /// Trait the wrapped value's vtable should be registered against.
176    pub wrap_as_trait_id: u32,
177}
178
179/// Bitfield for `VTableEntry::Compound` — which of the three rewriting
180/// shapes apply to this method.
181///
182/// Plain `u8` bitflags (not the `bitflags` crate) to keep the dep tree
183/// small. Helpers: `is_boxed_return()`, `is_self_arg()`, `is_generic()`.
184#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
185#[repr(transparent)]
186pub struct VTableEntryFlags(pub u8);
187
188impl VTableEntryFlags {
189    pub const BOXED_RETURN: u8 = 0b0000_0001;
190    pub const SELF_ARG: u8 = 0b0000_0010;
191    pub const GENERIC: u8 = 0b0000_0100;
192
193    #[inline]
194    pub const fn empty() -> Self {
195        Self(0)
196    }
197
198    #[inline]
199    pub const fn from_bits(bits: u8) -> Self {
200        Self(bits)
201    }
202
203    #[inline]
204    pub const fn bits(self) -> u8 {
205        self.0
206    }
207
208    #[inline]
209    pub const fn is_boxed_return(self) -> bool {
210        self.0 & Self::BOXED_RETURN != 0
211    }
212
213    #[inline]
214    pub const fn is_self_arg(self) -> bool {
215        self.0 & Self::SELF_ARG != 0
216    }
217
218    #[inline]
219    pub const fn is_generic(self) -> bool {
220        self.0 & Self::GENERIC != 0
221    }
222
223    #[inline]
224    pub fn set(&mut self, flag: u8) {
225        self.0 |= flag;
226    }
227}
228
229/// Runtime type information for method-generic parameters (§Q25.C.3).
230///
231/// Threaded through `VTableEntry::Generic` / `Compound` thunks so a
232/// `fn method<G: Bound>(&self, g: G)` invocation through `dyn T`
233/// dispatches operations on `g` correctly. `concrete_type_id` is the
234/// IC-stabilization key per §Q25.C.6.
235#[derive(Debug, Clone)]
236pub struct TypeInfo {
237    /// Concrete-type discriminator — matches `VTable::concrete_type_id`
238    /// when the generic argument is itself a trait-object.
239    pub concrete_type_id: u32,
240    /// If the generic parameter has a trait bound (`G: Bound`), this is
241    /// the bound's vtable for the concrete type. `None` when the
242    /// parameter is unbounded.
243    pub vtable_for_bound: Option<std::sync::Arc<VTable>>,
244    /// Size and alignment of the concrete type, in bytes.
245    pub size_align: (u32, u32),
246}
247
248// ── Erase_T substitution + thunk-construction descriptors ───────────────────
249//
250// ADR-006 §2.7.24 Q25.C.1 — universal-dyn auto-boxing rule. `ErasureType`
251// is the storage-tier mirror of the compiler's `Type` enum, narrowed to
252// the shapes `Erase_T` operates on. Emission-tier code (W17-trait-object-
253// emission) constructs `ErasureType` values from its richer Type
254// representation, runs `Erase_T::rewrite`, and reads off
255// `ThunkSignature` to know what shape of thunk to emit per
256// `(impl, method)` pair.
257
258/// Storage-tier projection of the method-signature types `Erase_T`
259/// operates on. Mirrors the row table in ADR-006 §2.7.24 Q25.C.1:
260///
261/// | Input `τ` | `Erase_T(τ)` |
262/// |---|---|
263/// | `Self` | `dyn T` |
264/// | `&Self` / `&mut Self` | `&dyn T` / `&mut dyn T` |
265/// | `Self::A` w/ bound | `dyn Bound` |
266/// | `Self::A` w/o bound | **ETO-001 compile error** |
267/// | `G<τ₁, ...>` w/ erasure-safe G | recurse |
268/// | method-generic G | `KindedSlot` + `TypeInfo` |
269/// | concrete / builtin | unchanged |
270#[derive(Debug, Clone, PartialEq, Eq)]
271pub enum ErasureType {
272    /// `Self` — boxes into `dyn T` (the trait being erased).
273    SelfType,
274    /// `&Self` (immutable) — boxes into `&dyn T`.
275    SelfRef,
276    /// `&mut Self` — boxes into `&mut dyn T`.
277    SelfRefMut,
278    /// `Self::A` — projection of a Self-associated type. If `bound_trait_id`
279    /// is `Some`, the assoc type's bound trait id (the type erases to
280    /// `dyn Bound`); if `None`, this is an `ETO-001` compile error
281    /// (associated type without a trait bound cannot be erased).
282    SelfAssoc {
283        assoc_name: String,
284        bound_trait_id: Option<u32>,
285    },
286    /// An erasure-safe generic constructor (Option/Result/Vec/Box/Arc/
287    /// HashMap/HashSet/tuple/user-#[erasure_safe]) with type-argument
288    /// list to recurse into. `name` is the constructor's user-visible
289    /// name; the emission tier maps this back to its own Type ctor.
290    Generic {
291        name: String,
292        args: Vec<ErasureType>,
293    },
294    /// `&G<...>` / `&mut G<...>` — reference to a generic. Reference
295    /// itself is not auto-boxed; recurses into the payload.
296    Reference {
297        mutable: bool,
298        inner: Box<ErasureType>,
299    },
300    /// Method-generic parameter (`fn foo<G: Bound>(...)`). At dispatch
301    /// time the call site supplies a `KindedSlot` payload + a
302    /// `&TypeInfo` per generic — see §Q25.C.3.
303    MethodGeneric { name: String },
304    /// A concrete or builtin type (int, string, user-defined struct,
305    /// closure type, etc.). Carries an opaque token so the emission
306    /// tier can map back to its richer representation. `Erase_T`
307    /// leaves these unchanged.
308    Concrete { type_token: u32 },
309}
310
311impl ErasureType {
312    /// Apply the `Erase_T(τ)` substitution per ADR-006 §2.7.24 Q25.C.1.
313    /// Returns the rewritten type, plus a `wrap_targets` accumulator
314    /// describing every `Self` / `Self::A` site reached (used by the
315    /// emission tier to populate `VTableEntry::BoxedReturn::wrap_targets`).
316    ///
317    /// The `trait_id` argument is the surrounding trait's id — used
318    /// for `Self` → `dyn T` (T being the surrounding trait) and as
319    /// the fallback `wrap_as_trait_id` for assoc-type erasures.
320    ///
321    /// Returns `Err` with the ETO error code on unbounded `Self::A`
322    /// per §Q25.C.1 row 5.
323    pub fn rewrite(&self, trait_id: u32) -> Result<RewriteResult, ErasureError> {
324        let mut wrap_targets = SmallVec::new();
325        let out = Self::rewrite_inner(self, trait_id, &mut wrap_targets, &mut SmallVec::new())?;
326        Ok(RewriteResult {
327            erased: out,
328            wrap_targets,
329        })
330    }
331
332    fn rewrite_inner(
333        ty: &ErasureType,
334        trait_id: u32,
335        wrap_targets: &mut SmallVec<[WrapTarget; 2]>,
336        path: &mut SmallVec<[u8; 4]>,
337    ) -> Result<ErasureType, ErasureError> {
338        match ty {
339            ErasureType::SelfType => {
340                wrap_targets.push(WrapTarget {
341                    path: path.clone(),
342                    wrap_as_trait_id: trait_id,
343                });
344                // Self at the outermost position erases to `dyn T`
345                // (encoded as a Concrete with the trait-id token —
346                // emission tier reads `wrap_targets` for the actual
347                // boxing-site information, the type itself becomes a
348                // `dyn T` carrier).
349                Ok(ErasureType::Concrete { type_token: trait_id })
350            }
351            ErasureType::SelfRef => {
352                wrap_targets.push(WrapTarget {
353                    path: path.clone(),
354                    wrap_as_trait_id: trait_id,
355                });
356                Ok(ErasureType::Reference {
357                    mutable: false,
358                    inner: Box::new(ErasureType::Concrete { type_token: trait_id }),
359                })
360            }
361            ErasureType::SelfRefMut => {
362                wrap_targets.push(WrapTarget {
363                    path: path.clone(),
364                    wrap_as_trait_id: trait_id,
365                });
366                Ok(ErasureType::Reference {
367                    mutable: true,
368                    inner: Box::new(ErasureType::Concrete { type_token: trait_id }),
369                })
370            }
371            ErasureType::SelfAssoc { assoc_name, bound_trait_id } => match bound_trait_id {
372                Some(bound) => {
373                    wrap_targets.push(WrapTarget {
374                        path: path.clone(),
375                        wrap_as_trait_id: *bound,
376                    });
377                    Ok(ErasureType::Concrete { type_token: *bound })
378                }
379                None => Err(ErasureError::Eto001UnboundedAssoc {
380                    assoc_name: assoc_name.clone(),
381                }),
382            },
383            ErasureType::Generic { name, args } => {
384                let mut new_args = Vec::with_capacity(args.len());
385                for (i, arg) in args.iter().enumerate() {
386                    path.push(i as u8);
387                    new_args.push(Self::rewrite_inner(arg, trait_id, wrap_targets, path)?);
388                    path.pop();
389                }
390                Ok(ErasureType::Generic {
391                    name: name.clone(),
392                    args: new_args,
393                })
394            }
395            ErasureType::Reference { mutable, inner } => {
396                // References themselves are unchanged; recurse into
397                // payload without pushing a path step (the reference
398                // is a transparent wrapper for `Erase_T` purposes).
399                let new_inner =
400                    Self::rewrite_inner(inner.as_ref(), trait_id, wrap_targets, path)?;
401                Ok(ErasureType::Reference {
402                    mutable: *mutable,
403                    inner: Box::new(new_inner),
404                })
405            }
406            ErasureType::MethodGeneric { .. } | ErasureType::Concrete { .. } => Ok(ty.clone()),
407        }
408    }
409}
410
411/// Result of `Erase_T::rewrite` — the erased type plus the wrap-target
412/// list for the emission-tier thunk.
413#[derive(Debug, Clone)]
414pub struct RewriteResult {
415    pub erased: ErasureType,
416    pub wrap_targets: SmallVec<[WrapTarget; 2]>,
417}
418
419/// Errors `Erase_T` can surface per §Q25.C.1 / §Q25.C.4 ETO error codes.
420#[derive(Debug, Clone, PartialEq, Eq)]
421pub enum ErasureError {
422    /// ETO-001: `Self::A` with no trait bound cannot be erased.
423    Eto001UnboundedAssoc { assoc_name: String },
424    /// ETO-002: method is `#[static_only]` and cannot be called through
425    /// `dyn T` (the emission tier checks this at the call site before
426    /// even building a `dyn` coercion).
427    Eto002StaticOnly { method_name: String },
428}
429
430/// Per-(impl, method) thunk descriptor — the data the emission tier
431/// needs to generate a thunk function for one method of one impl.
432///
433/// The emission tier walks each impl's method list, runs `Erase_T` on
434/// each method's signature, and either emits a `VTableEntry::Direct`
435/// (no rewriting needed) or a thunk + the corresponding
436/// `VTableEntry::{BoxedReturn, SelfArg, Generic, Compound}` per
437/// §Q25.C.5.
438///
439/// **Per-(impl, method) cardinality.** One thunk per pair, NOT one per
440/// trait — different impls of the same trait method may need
441/// different thunks because the concrete return type differs. (E.g.
442/// `impl Animal for Dog { fn clone_me(&self) -> Dog }` vs
443/// `impl Animal for Cat { fn clone_me(&self) -> Cat }` — each clones
444/// a different concrete type and boxes it under the same trait id.)
445#[derive(Debug, Clone)]
446pub struct ThunkSignature {
447    /// Impl that owns the method (concrete-type id from the impl's
448    /// matching `VTable::concrete_type_id`).
449    pub impl_type_id: u32,
450    /// Trait the method belongs to (`VTable::trait_names` first entry,
451    /// or the surrounding trait for multi-impl cases).
452    pub trait_id: u32,
453    /// Method name as declared in the trait.
454    pub method_name: String,
455    /// Erasure flags — which of (BoxedReturn / SelfArg / Generic)
456    /// apply. Empty → emission tier emits `VTableEntry::Direct` and
457    /// no thunk. Single bit → emission emits the corresponding
458    /// single-shape `VTableEntry::*` variant. Multiple bits →
459    /// `VTableEntry::Compound`.
460    pub flags: VTableEntryFlags,
461    /// Wrap-targets if `BOXED_RETURN` is set (empty otherwise).
462    pub wrap_targets: SmallVec<[WrapTarget; 2]>,
463    /// Self-arg positions if `SELF_ARG` is set (empty otherwise).
464    pub self_arg_positions: SmallVec<[u8; 4]>,
465    /// Number of method-generic parameters if `GENERIC` is set
466    /// (zero otherwise).
467    pub type_param_count: u8,
468}
469
470impl ThunkSignature {
471    /// Build a thunk signature from per-arg / per-return erasure
472    /// results. The emission tier calls this once per (impl, method)
473    /// pair after running `Erase_T` on each component of the
474    /// signature.
475    ///
476    /// - `return_wrap_targets` — `wrap_targets` from running `Erase_T`
477    ///   on the method's return type. Non-empty ⇒ `BOXED_RETURN`.
478    /// - `self_arg_positions` — indices (0-based, excluding receiver)
479    ///   of arguments declared as `Self` in the trait signature.
480    ///   Non-empty ⇒ `SELF_ARG`.
481    /// - `type_param_count` — number of `<G>` method-generic
482    ///   parameters. Non-zero ⇒ `GENERIC`.
483    pub fn build(
484        impl_type_id: u32,
485        trait_id: u32,
486        method_name: String,
487        return_wrap_targets: SmallVec<[WrapTarget; 2]>,
488        self_arg_positions: SmallVec<[u8; 4]>,
489        type_param_count: u8,
490    ) -> Self {
491        let mut flags = VTableEntryFlags::empty();
492        if !return_wrap_targets.is_empty() {
493            flags.set(VTableEntryFlags::BOXED_RETURN);
494        }
495        if !self_arg_positions.is_empty() {
496            flags.set(VTableEntryFlags::SELF_ARG);
497        }
498        if type_param_count > 0 {
499            flags.set(VTableEntryFlags::GENERIC);
500        }
501        Self {
502            impl_type_id,
503            trait_id,
504            method_name,
505            flags,
506            wrap_targets: return_wrap_targets,
507            self_arg_positions,
508            type_param_count,
509        }
510    }
511
512    /// Whether this method needs no thunk at all (Direct dispatch).
513    pub fn is_direct(&self) -> bool {
514        self.flags.bits() == 0
515    }
516
517    /// Build the corresponding `VTableEntry` once the emission tier
518    /// has assigned `function_id` (for Direct) / `thunk_id`
519    /// (otherwise). `Direct` entries get `function_id`; Compound /
520    /// shape entries get the thunk id and the emission-tier-
521    /// allocated thunk function id is stored elsewhere.
522    pub fn to_vtable_entry(&self, function_or_thunk_id: u16) -> VTableEntry {
523        let bits = self.flags.bits();
524        if bits == 0 {
525            return VTableEntry::Direct {
526                function_id: function_or_thunk_id,
527            };
528        }
529        let one_bit_set = bits.count_ones() == 1;
530        if one_bit_set {
531            if self.flags.is_boxed_return() {
532                return VTableEntry::BoxedReturn {
533                    thunk_id: function_or_thunk_id,
534                    wrap_targets: self.wrap_targets.clone(),
535                };
536            }
537            if self.flags.is_self_arg() {
538                return VTableEntry::SelfArg {
539                    thunk_id: function_or_thunk_id,
540                    self_arg_positions: self.self_arg_positions.clone(),
541                };
542            }
543            if self.flags.is_generic() {
544                return VTableEntry::Generic {
545                    thunk_id: function_or_thunk_id,
546                    type_param_count: self.type_param_count,
547                };
548            }
549        }
550        VTableEntry::Compound {
551            thunk_id: function_or_thunk_id,
552            flags: self.flags,
553            wrap_targets: self.wrap_targets.clone(),
554            self_arg_positions: self.self_arg_positions.clone(),
555            type_param_count: self.type_param_count,
556        }
557    }
558}
559
560#[cfg(test)]
561mod erase_t_tests {
562    //! W17-trait-object-storage (ADR-006 §2.7.24 / Q25.C, 2026-05-11):
563    //! pin the `Erase_T` substitution + `ThunkSignature::build` shape
564    //! contracts. These are storage-tier compiler-side rewriting
565    //! tests; the emission tier consumes these to drive thunk
566    //! generation.
567    use super::*;
568
569    fn concrete(token: u32) -> ErasureType {
570        ErasureType::Concrete { type_token: token }
571    }
572
573    #[test]
574    fn erase_self_at_top_level_pushes_wrap_target_with_empty_path() {
575        // `fn clone(&self) -> Self` → return type `Self`
576        // Erase_T → `dyn T`, wrap_targets = [{ path: [], wrap_as: T }]
577        let r = ErasureType::SelfType.rewrite(42).unwrap();
578        assert_eq!(r.wrap_targets.len(), 1);
579        assert_eq!(r.wrap_targets[0].path.as_slice(), &[] as &[u8]);
580        assert_eq!(r.wrap_targets[0].wrap_as_trait_id, 42);
581    }
582
583    #[test]
584    fn erase_result_of_self_pushes_path_zero() {
585        // `fn try_clone(&self) -> Result<Self, Error>`
586        // → wrap_targets = [{ path: [0], wrap_as: T }]
587        let ty = ErasureType::Generic {
588            name: "Result".to_string(),
589            args: vec![ErasureType::SelfType, concrete(99)],
590        };
591        let r = ty.rewrite(7).unwrap();
592        assert_eq!(r.wrap_targets.len(), 1);
593        assert_eq!(r.wrap_targets[0].path.as_slice(), &[0u8] as &[u8]);
594        assert_eq!(r.wrap_targets[0].wrap_as_trait_id, 7);
595    }
596
597    #[test]
598    fn erase_tuple_of_self_self_pushes_two_targets() {
599        // `fn split(self) -> (Self, Self)`
600        // → wrap_targets = [{ path: [0] }, { path: [1] }]
601        let ty = ErasureType::Generic {
602            name: "tuple".to_string(),
603            args: vec![ErasureType::SelfType, ErasureType::SelfType],
604        };
605        let r = ty.rewrite(11).unwrap();
606        assert_eq!(r.wrap_targets.len(), 2);
607        assert_eq!(r.wrap_targets[0].path.as_slice(), &[0u8] as &[u8]);
608        assert_eq!(r.wrap_targets[1].path.as_slice(), &[1u8] as &[u8]);
609    }
610
611    #[test]
612    fn erase_nested_option_result_self_pushes_deep_path() {
613        // `fn deep(&self) -> Option<Result<Self, E>>`
614        // → wrap_targets = [{ path: [0, 0] }]
615        let ty = ErasureType::Generic {
616            name: "Option".to_string(),
617            args: vec![ErasureType::Generic {
618                name: "Result".to_string(),
619                args: vec![ErasureType::SelfType, concrete(50)],
620            }],
621        };
622        let r = ty.rewrite(33).unwrap();
623        assert_eq!(r.wrap_targets.len(), 1);
624        assert_eq!(r.wrap_targets[0].path.as_slice(), &[0u8, 0u8] as &[u8]);
625    }
626
627    #[test]
628    fn erase_unbounded_assoc_returns_eto_001() {
629        let ty = ErasureType::SelfAssoc {
630            assoc_name: "Item".to_string(),
631            bound_trait_id: None,
632        };
633        let err = ty.rewrite(1).unwrap_err();
634        assert!(matches!(err, ErasureError::Eto001UnboundedAssoc { .. }));
635    }
636
637    #[test]
638    fn erase_bounded_assoc_erases_to_bound_trait() {
639        let ty = ErasureType::SelfAssoc {
640            assoc_name: "Iter".to_string(),
641            bound_trait_id: Some(77),
642        };
643        let r = ty.rewrite(1).unwrap();
644        assert_eq!(r.wrap_targets.len(), 1);
645        assert_eq!(r.wrap_targets[0].wrap_as_trait_id, 77);
646    }
647
648    #[test]
649    fn erase_concrete_type_is_identity_no_wrap_targets() {
650        let r = concrete(42).rewrite(1).unwrap();
651        assert_eq!(r.wrap_targets.len(), 0);
652        assert_eq!(r.erased, concrete(42));
653    }
654
655    #[test]
656    fn thunk_signature_direct_when_no_rewriting() {
657        let sig = ThunkSignature::build(
658            1,
659            2,
660            "name".to_string(),
661            SmallVec::new(),
662            SmallVec::new(),
663            0,
664        );
665        assert!(sig.is_direct());
666        match sig.to_vtable_entry(7) {
667            VTableEntry::Direct { function_id } => assert_eq!(function_id, 7),
668            _ => panic!("expected Direct"),
669        }
670    }
671
672    #[test]
673    fn thunk_signature_boxed_return_only() {
674        let mut wts: SmallVec<[WrapTarget; 2]> = SmallVec::new();
675        wts.push(WrapTarget {
676            path: SmallVec::new(),
677            wrap_as_trait_id: 1,
678        });
679        let sig = ThunkSignature::build(
680            1,
681            2,
682            "clone".to_string(),
683            wts,
684            SmallVec::new(),
685            0,
686        );
687        assert!(!sig.is_direct());
688        match sig.to_vtable_entry(9) {
689            VTableEntry::BoxedReturn { thunk_id, wrap_targets } => {
690                assert_eq!(thunk_id, 9);
691                assert_eq!(wrap_targets.len(), 1);
692            }
693            _ => panic!("expected BoxedReturn"),
694        }
695    }
696
697    #[test]
698    fn thunk_signature_compound_when_two_flags() {
699        // Self in return AND a method generic — Compound.
700        let mut wts: SmallVec<[WrapTarget; 2]> = SmallVec::new();
701        wts.push(WrapTarget {
702            path: SmallVec::new(),
703            wrap_as_trait_id: 5,
704        });
705        let sig = ThunkSignature::build(
706            1,
707            5,
708            "compound".to_string(),
709            wts,
710            SmallVec::new(),
711            1,
712        );
713        match sig.to_vtable_entry(11) {
714            VTableEntry::Compound { thunk_id, flags, type_param_count, .. } => {
715                assert_eq!(thunk_id, 11);
716                assert!(flags.is_boxed_return());
717                assert!(flags.is_generic());
718                assert!(!flags.is_self_arg());
719                assert_eq!(type_param_count, 1);
720            }
721            _ => panic!("expected Compound"),
722        }
723    }
724}