read-only-derive 0.1.1

Proc-macro support crate for read-only
Documentation
# read-only-derive

Internal proc-macro support crate for [`read-only`](https://github.com/attackgoat/read-only).

This crate implements the `#[embed]` attribute macro that powers the safe,
composition-based readonly pattern exported by the public `read-only` crate.

It is intended primarily for maintainers, contributors, and anyone trying to
understand how the macro parses Rust syntax trees and rewrites them into the
generated readonly view type.

## Relationship to `read-only`

- The root `read-only` crate is the public user-facing crate.
- `read-only-derive` is the proc-macro implementation crate.
- `read_only::embed` is re-exported from this crate.
- `read_only::cast` does **not** live here; it is re-exported from
  [`readonly::make`]https://docs.rs/readonly/latest/readonly/attr.make.html.

This split follows the common two-crate Rust pattern used by crates such as
[`serde`](https://crates.io/crates/serde) / `serde_derive`.

## What `#[embed]` Does

At a high level, the macro takes a named-field struct and splits it into two
parts:

1. readonly fields marked with `#[readonly]`
2. all remaining fields

It then generates:

- a `ReadOnly{Type}` struct containing the readonly fields
- a rewritten original struct that embeds `read_only: ReadOnly{Type}`
- a `Deref<Target = ReadOnly{Type}>` implementation for the rewritten original
  struct

This means consumers get direct readonly access syntax through deref, while the
implementation remains composition-based rather than cast-based.

## Example

Input:

```rust
use read_only::embed;

#[embed]
pub struct Device {
    #[readonly]
    pub handle: u64,
    #[readonly]
    pub label: String,
    pub mutable: i32,
}
```

Expansion shape:

```rust
pub struct ReadOnlyDevice {
    pub handle: u64,
    pub label: String,
}

pub struct Device {
    read_only: ReadOnlyDevice,
    pub mutable: i32,
}

impl core::ops::Deref for Device {
    type Target = ReadOnlyDevice;

    fn deref(&self) -> &Self::Target {
        &self.read_only
    }
}
```

## Parsing and Rewrite Rules

The macro operates on a parsed [`syn::DeriveInput`](https://docs.rs/syn/latest/syn/struct.DeriveInput.html).

### Accepted input

Supported:

- named-field structs
- generics
- where-clauses
- field-level `#[readonly]`
- field types containing `Self`

Rejected:

- enums
- unions
- tuple structs
- unit structs
- empty named structs
- structs with no `#[readonly]` fields
- structs with a field already named `read_only`

### Rewrite algorithm

The implementation in `src/expand.rs` follows roughly this sequence:

```mermaid
flowchart TD
    A[Parse DeriveInput] --> B{Named struct?}
    B -- no --> E1[Emit compile error]
    B -- yes --> C{Has at least one field?}
    C -- no --> E2[Emit compile error]
    C -- yes --> D[Scan fields for #[readonly]]
    D --> F{Any readonly fields?}
    F -- no --> E3[Emit compile error]
    F -- yes --> G{Field named read_only already exists?}
    G -- yes --> E4[Emit compile error]
    G -- no --> H[Split fields into readonly and remaining]
    H --> I[Rewrite readonly field types containing Self]
    I --> J[Generate ReadOnlyType struct]
    J --> K[Generate outer struct with read_only field]
    K --> L[Generate Deref impl]
```

### Field handling rules

| Field kind | Result |
| --- | --- |
| `#[readonly] pub field: T` | moved into `ReadOnly*` and remains public there |
| `#[readonly] field: T` | moved into `ReadOnly*` and promoted to outer struct visibility |
| non-`#[readonly]` field | stays on outer struct |
| field type using `Self` | rewritten to the concrete outer type in readonly fields |

## Why `Self` Replacement Exists

If a readonly field type refers to `Self`, that `Self` cannot remain unchanged
inside the generated `ReadOnly*` struct because the generated struct is a
different type.

Example input:

```rust
#[embed]
pub struct Node {
    #[readonly]
    pub next: Option<Box<Self>>,
    pub value: i32,
}
```

If left untouched, `Self` inside the generated readonly struct would refer to
`ReadOnlyNode`, not `Node`, which changes semantics.

The macro therefore rewrites readonly field types from:

```rust
Option<Box<Self>>
```

to:

```rust
Option<Box<Node>>
```

This is implemented through a small [`VisitMut`](https://docs.rs/syn/latest/syn/visit_mut/index.html)
visitor that walks the type syntax tree and replaces path occurrences of
`Self`.

## Error Handling Model

The macro uses two kinds of errors:

1. hard structural errors that stop expansion immediately
2. collected attribute-shape errors that are appended as compile errors

Hard structural errors include:

- wrong item kind
- wrong field style
- empty struct
- missing readonly fields
- conflicting `read_only` field name

Collected attribute errors currently come from malformed `#[readonly]` usage,
for example:

```rust
#[readonly(oops)]
```

The macro strips the attribute and preserves the diagnostic by storing the
parser error and appending its `to_compile_error()` output to the final token
stream.

## Why the Code Is Written This Way

The implementation may look a little unusual compared with some proc-macro
crates that aggressively factor parsing into many helpers.

That is intentional.

The current style aims to keep the most important structural validation and
rewrite steps in a single visible control flow inside `embed()`:

- validate input shape
- strip and classify readonly fields
- split field ownership
- rewrite `Self`
- emit expansion

This has a few benefits:

- maintainers can read the expansion logic top-to-bottom without jumping across
  many files
- structural error paths stay close to the logic that needs them
- macro compile tests map more directly to the expansion steps

It also ended up being helpful for coverage work. Some of the code shape was
chosen to keep validation paths explicit and testable rather than hiding them
behind layered helper abstractions that would leave more unreachable or
hard-to-attribute coverage artifacts in proc-macro expansion code.

That does **not** mean the code is coverage-driven first; it means we preferred
an implementation style that is both readable and easy to verify with compile
tests.

## Testing Strategy

This crate is exercised through the root workspace test suite:

- `tests/macro/pass/*.rs`: passing macro expansion cases
- `tests/macro/fail/*.rs`: compile-fail macro expansion cases with checked
  stderr snapshots
- `tests/miri.rs`: runtime coverage of the unsafe cast-based path in the root
  crate (`cast`), included because the public crate exposes both patterns

Useful commands:

```sh
cargo test
cargo test --test compile
cargo +nightly miri test --test miri
```

## Relevant Source Files

- `src/lib.rs`: proc-macro entrypoint for `#[embed]`
- `src/expand.rs`: parsing, validation, tree rewriting, and token generation
- `../tests/macro/`: compile-time macro test suite

## Related Reading

- [`syn` documentation]https://docs.rs/syn/latest/syn/
- [`quote` documentation]https://docs.rs/quote/latest/quote/
- [`proc_macro2` documentation]https://docs.rs/proc-macro2/latest/proc_macro2/
- [`readonly` crate docs]https://docs.rs/readonly/latest/readonly/

## Publishing Note

This crate is intended to be published before the root `read-only` crate,
because the public crate depends on it as a versioned proc-macro dependency.