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}