extern-trait 0.4.2

Opaque types for traits using static dispatch
Documentation
# VTable Refactor Plan

## Goal

Replace multiple per-method exported symbols with a single VTable symbol per trait.

### Current Design

- Each method: `#[unsafe(export_name = "...")] fn method(...)` (via `emit_export` in `decl/mod.rs`)
- `drop` and `typeid`: separate symbols (via `expand_drop_impl` / `expand_cast_impl`)
- N+2 symbols per trait (N methods + drop + typeid)

### New Design

- Single `#[unsafe(export_name = "...")] static VT: __TraitVTable`
- VTable contains all method pointers + typeid + drop

## Benefits

- Reduced linker symbol pollution
- Single linker error when trait not implemented (vs N+2 errors)
- Potentially smaller binary and faster compilation

## Notes on LTO

With LTO enabled, the entire VTable struct is eliminated and function calls are inlined
directly — verified by testing. Both dead code elimination and indirect call overhead are
non-issues under LTO.

This crate is designed for cross-crate interfaces where LTO is the expected configuration.
Even the current per-method symbol design has call overhead without LTO, so LTO is
effectively a requirement regardless of dispatch strategy.

## Critical Design Notes

### `#[repr(C)]` Required

The VTable struct is defined **twice** — once on the proxy side (trait definition) and once
on the impl side (macro_rules expansion). The two definitions have **different field types**
for ref/ptr Self (proxy uses `ProxyType`, impl uses `$ty`), but they share the same linker
symbol.

Their memory layouts **must** be identical. Default Rust struct layout is unspecified and may
differ between compilation units. Both definitions **must** use `#[repr(C)]` to guarantee
deterministic field ordering and alignment.

Layout equivalence is guaranteed by:
- `#[repr(C)]` on both structs ensures identical field ordering
- By-value Self: proxy uses `ProxyType` which is `#[repr(transparent)]` over `Repr`;
  impl uses `Repr` directly — ABI identical
- Ref/ptr Self: both sides are pointer types — same size and alignment
- Non-Self types: identical on both sides

### `safe static` for VTable Symbol

The proxy side declares the VTable as an extern static. Following the existing pattern for
`TYPEID` (which already uses `safe static`), the VTable should also be declared `safe static`:

```rust
unsafe extern "Rust" {
    #[link_name = #vtable_sym]
    safe static VT: __TraitVTable;
}
```

This allows `(VT.field)(args)` without wrapping every access in an `unsafe` block.
Safety invariant: the impl side guarantees the exported static has the correct type and layout
(enforced by the macro_rules expansion and `#[repr(C)]`).

### Self Type on Proxy Side

By-value `Self` on the proxy side uses `ProxyType` directly (same as the current per-method
design — `emit_method` already substitutes `Self` → `ProxyType` via `SelfKind::Value.to_type()`).

This means for a method like `fn new(num: i32) -> Self`, the proxy-side VTable field type is
`fn(i32) -> HelloProxy`, and the trait impl can return the call result directly:

```rust
fn new(num: i32) -> Self {
    (VT.new)(num)    // returns HelloProxy, which IS Self
}
```

No `Self(...)` wrapping needed — `HelloProxy` is already `Self` in this context.

On the impl side, the same field uses `Repr` (since the concrete type is erased), with
`Repr::from_value` / `Repr::into_value` conversions in the closure body.

## Implementation

### 1. `impl/src/decl/symbol.rs`

**Current state:**

```rust
pub struct Symbol {
    extern_trait: String,
    package: String,
    version: String,
    crate_name: String,
    package_disambiguator: u64,
    trait_name: String,
    local_disambiguator: u64,
    name: String,           // per-method suffix, set via .with_name()
}
```

**Changes:**

- Delete `name: String` field
- Delete `with_name()` method

**Reason:** Only one symbol per trait now (vtable), no need for per-method name customization.

### 2. `impl/src/decl/supertraits.rs`

**No changes needed.**

- `collect_supertraits()` already returns `Vec<SupertraitInfo>` with methods
- `SupertraitInfo` already provides `methods: Vec<VerifiedSignature>`

### 3. `impl/src/decl/types.rs`

**No changes needed.**

- `to_type()` method on `MaybeSelf` already handles Self type substitution
- `VerifiedSignature` already separates inputs/output as `Vec<MaybeSelf>` / `Option<MaybeSelf>`

### 4. `impl/src/decl/mod.rs`

**Current state:**

- `ExpandCtx` holds: `extern_trait: Path`, `proxy: Proxy`, `input: ItemTrait`, `sym: Symbol`, `macro_items: TokenStream`
- `emit_method()` — generates proxy-side impl method (link_name extern call)
- `emit_export()` — generates impl-side exported function (export_name)
- `expand_trait_impl()`, `expand_supertrait_impls()`, `expand_drop_impl()`, `expand_cast_impl()`, `expand_macro_rules()`, `expand()`

**Major restructuring:**

#### 4.1 New type: `MethodInfo`

```rust
struct MethodInfo {
    sig: VerifiedSignature,
    /// None for trait's own methods, Some(path) for supertrait methods
    supertrait_path: Option<Path>,
}

impl MethodInfo {
    fn field_name(&self) -> Ident {
        match &self.supertrait_path {
            None => self.sig.ident.clone(),
            Some(path) => {
                let last = path.segments.last().unwrap();
                format_ident!("{}_{}", last.ident, self.sig.ident)
            }
        }
    }
}
```

#### 4.2 Replace `emit_method()` + `emit_export()`

Both are removed. In their place:

- `generate_vtable_struct()` — generates `#[repr(C)] struct __TraitVTable { ... }` with:
  - `typeid: ConstTypeId`
  - `drop: unsafe fn(*mut ProxyType)`
  - One field per method (trait + supertrait), fn pointer type
  - Takes a type parameter to control Self substitution:
    - Proxy side: `Self``ProxyType`
    - Impl side: by-value `Self``Repr`, ref/ptr `Self``$ty`

- `generate_proxy_trait_impl()` — calls through VTable fields:
  ```rust
  fn method(&self, arg: T) -> R {
      (VT.method)(self, arg)
  }
  ```

- `generate_impl_vtable_init()` — generates the static VTable initializer:
  ```rust
  static VT: __TraitVTable = __TraitVTable {
      typeid: ConstTypeId::of::<$ty>(),
      drop: |this| unsafe { core::ptr::drop_in_place(this) },
      method: |args| { <$ty as $trait>::method(converted_args) },
  };
  ```

#### 4.3 Reflow `expand()`

1. Collect all methods: trait methods + supertrait methods → `Vec<MethodInfo>`
2. Generate proxy-side VTable struct (`#[repr(C)]`)
3. Generate extern static declaration (`safe static VT`)
4. Generate trait impl for proxy (calls `(VT.field)(args)`)
5. Generate supertrait impls (calls through VTable, same pattern)
6. Generate Drop impl (`unsafe { (VT.drop)(self as *mut Self) }`)
7. Generate cast methods (`VT.typeid` instead of separate extern static)
8. Generate macro_rules with impl-side VTable struct + static initializer

#### 4.4 VTable Field Type Mapping

**Proxy-side struct:**

| Self kind in signature | VTable field param type | VTable field return type |
| ---------------------- | ----------------------- | ----------------------- |
| `Self` (by value)      | `ProxyType`             | `ProxyType`             |
| `&Self`                | `&ProxyType`            | N/A (never returned)    |
| `&mut Self`            | `&mut ProxyType`        | N/A                     |
| `*const Self`          | `*const ProxyType`      | `*const ProxyType`      |
| `*mut Self`            | `*mut ProxyType`        | `*mut ProxyType`        |
| Non-Self               | unchanged               | unchanged               |

All Self variants use `ProxyType` on the proxy side, consistent with the current per-method
design (`emit_method` uses `input.to_type(proxy.clone())`).

**Impl-side struct:**

| Self kind in signature | VTable field param type | VTable field return type |
| ---------------------- | ----------------------- | ----------------------- |
| `Self` (by value)      | `Repr`                  | `Repr`                  |
| `&Self`                | `&$ty`                  | N/A                     |
| `&mut Self`            | `&mut $ty`              | N/A                     |
| `*const Self`          | `*const $ty`            | `*const $ty`            |
| `*mut Self`            | `*mut $ty`              | `*mut $ty`              |
| Non-Self               | unchanged               | unchanged               |

**Layout equivalence**: `ProxyType` is `#[repr(transparent)]` over `Repr`, so proxy-side
`ProxyType` and impl-side `Repr` have identical ABI. Ref/ptr types are all pointer-sized.
Combined with `#[repr(C)]`, the two struct definitions are layout-compatible.

#### 4.5 Impl-side Field Initialization (call wrappers)

| Self kind                                        | Argument conversion                       |
| ------------------------------------------------ | ----------------------------------------- |
| `Self` (by value) param                          | `unsafe { Repr::into_value::<$ty>(arg) }` |
| `&Self`, `&mut Self`, `*const Self`, `*mut Self`  | No conversion needed (pointer-compatible) |

| Self kind                | Return conversion                       |
| ------------------------ | --------------------------------------- |
| `Self` (by value) return | `unsafe { Repr::from_value(result) }`   |
| Non-Self                 | No conversion                           |

### 5. `impl/src/imp.rs`

**No changes needed.** The impl side just invokes the macro_rules generated by the decl side.
The macro_rules content changes (VTable struct + static init instead of per-method exports),
but `imp.rs` itself only does:

```rust
#trait_!(#trait_: #ty);
```

## Generated Code Example

### Trait Definition Side

```rust
pub trait Hello {
    fn new(num: i32) -> Self;
    fn hello(&self);
}

#[repr(transparent)]
pub struct HelloProxy(::extern_trait::Repr);

const _: () = {
    #[repr(C)]
    struct __HelloVTable {
        typeid: ::extern_trait::__private::ConstTypeId,
        drop: unsafe fn(*mut HelloProxy),
        new: fn(i32) -> HelloProxy,
        hello: fn(&HelloProxy),
    }

    unsafe extern "Rust" {
        #[link_name = "Symbol { ... trait_name: \"Hello\" ... }"]
        safe static VT: __HelloVTable;
    }

    impl Hello for HelloProxy {
        fn new(num: i32) -> Self {
            (VT.new)(num)
        }
        fn hello(&self) {
            (VT.hello)(self)
        }
    }

    impl Drop for HelloProxy {
        fn drop(&mut self) {
            unsafe { (VT.drop)(self) }
        }
    }

    impl HelloProxy {
        fn assert_type_is_impl<T: Hello>() {
            let typeid = ::extern_trait::__private::ConstTypeId::of::<T>();
            assert!(typeid == VT.typeid, "...");
        }

        pub fn from_impl<T: Hello>(value: T) -> Self {
            Self::assert_type_is_impl::<T>();
            Self(unsafe { ::extern_trait::Repr::from_value(value) })
        }

        pub fn into_impl<T: Hello>(self) -> T {
            Self::assert_type_is_impl::<T>();
            unsafe {
                ::extern_trait::Repr::into_value(
                    ::extern_trait::Repr::from_value(self)
                )
            }
        }

        pub fn downcast_ref<T: Hello>(&self) -> &T {
            Self::assert_type_is_impl::<T>();
            unsafe { &*(self as *const Self as *const T) }
        }

        pub fn downcast_mut<T: Hello>(&mut self) -> &mut T {
            Self::assert_type_is_impl::<T>();
            unsafe { &mut *(self as *mut Self as *mut T) }
        }
    }
};

#[doc(hidden)]
#[macro_export]
macro_rules! __extern_trait_Hello {
    ($trait:path: $ty:ty) => {
        const _: () = {
            #[repr(C)]
            struct __HelloVTable {
                typeid: ::extern_trait::__private::ConstTypeId,
                drop: unsafe fn(*mut $ty),
                new: fn(i32) -> ::extern_trait::Repr,
                hello: fn(&$ty),
            }

            #[unsafe(export_name = "Symbol { ... trait_name: \"Hello\" ... }")]
            static VT: __HelloVTable = __HelloVTable {
                typeid: ::extern_trait::__private::ConstTypeId::of::<$ty>(),
                drop: |this: *mut $ty| unsafe { ::core::ptr::drop_in_place(this) },
                new: |num: i32| {
                    let __result = <$ty as $trait>::new(num);
                    unsafe { ::extern_trait::Repr::from_value(__result) }
                },
                hello: |this: &$ty| <$ty as $trait>::hello(this),
            };
        };
    };
}

#[doc(hidden)]
#[allow(unused_imports)]
pub use __extern_trait_Hello as Hello;
```

### With Supertraits

```rust
#[extern_trait(ResourceProxy)]
trait Resource: Send + Sync + Clone + Debug {
    fn new() -> Self;
}

// Proxy-side VTable struct:
#[repr(C)]
struct __ResourceVTable {
    typeid: ConstTypeId,
    drop: unsafe fn(*mut ResourceProxy),
    new: fn() -> ResourceProxy,
    Clone_clone: fn(&ResourceProxy) -> ResourceProxy,
    Debug_fmt: fn(&ResourceProxy, &mut core::fmt::Formatter<'_>) -> core::fmt::Result,
}

// impl Clone for ResourceProxy:
//     fn clone(&self) -> Self { (VT.Clone_clone)(self) }

// impl Debug for ResourceProxy:
//     fn fmt(&self, f: &mut Formatter<'_>) -> Result { (VT.Debug_fmt)(self, f) }

// unsafe impl Send for ResourceProxy {}
// unsafe impl Sync for ResourceProxy {}
```

## Test Plan

1. Run existing tests: `cargo test` — all must pass without modification
2. Check generated code with `cargo expand` (if available)
3. Verify single symbol per trait in object files: `nm` or `objdump`
4. Verify `#[repr(C)]` is present on both VTable struct definitions
5. Add a UI test for mismatched VTable (if possible)