# Hacking on `future_form` > "In nova fert animus mutatas dicere formas corpora." > > ("I intend to speak of forms changed into new bodies.") > > — Ovid, _Metamorphoses_ ## Nix Flake The project uses a Nix flake for reproducible development. Enter the dev shell: ```sh nix develop ``` This gives you: - **Rust 1.90.0** (pinned via `rust-overlay`) - **Cross-compilation targets**: macOS (aarch64/x86_64), Linux musl (aarch64/x86_64), Wasm, thumbv6m (embedded) - **Cargo tools**: `cargo-expand`, `cargo-deny`, `cargo-outdated`, `cargo-udeps`, `cargo-sort`, `cargo-component` - **Formatters**: `nixpkgs-fmt`, `alejandra`, `taplo` ### Commands On shell entry, a `menu` displays available commands: | Command | Description | |----------|------------------------------| | `test` | Run tests with `cargo-watch` | | `lint` | Run clippy | | `fmt` | Format code | | `build` | Build the project | | `doc` | Generate docs | | `watch` | Watch for changes | | `audit` | Security audit dependencies | | `semver` | Check semver compatibility | | `ci` | Run full CI checks | ### Debugging Macros `cargo-expand` is included for inspecting macro output: ```sh cargo expand --package future_form --test macro_tests ``` ## Project Structure ``` future_form/ ├── future_form/ # Core library │ ├── src/lib.rs # FutureForm, Sendable, Local, FromFuture │ └── tests/ │ ├── macro_tests.rs # Unit tests for the macro │ └── ui/ # Compile-fail tests (trybuild) ├── future_form_macros/ # Proc macro crate │ └── src/lib.rs # #[future_form] attribute macro └── Cargo.toml # Workspace configuration ``` ## Building ```sh cargo build ``` ## Testing ```sh # Run all tests cargo test # Run only macro unit tests cargo test --test macro_tests # Run only compile-fail UI tests cargo test --test compile_fail # Update UI test expected output after intentional changes TRYBUILD=overwrite cargo test --test compile_fail ``` ## How the Macro Works ``` ┌─────────────────────────────────┐ │ #[future_form(Sendable, Local)]│ │ impl Trait │ │ for Type { ... } │ └───────────────┬─────────────────┘ │ ┌─────────────────┴─────────────────┐ │ syn::parse │ │ Extract K parameter │ └─────────────────┬─────────────────┘ │ ┌─────────────────────────┼─────────────────────────┐ │ │ │ ▼ ▼ ▼ ┌─────────────────────────┐ ┌─────────────────────────┐ ┌─────────────────────┐ │ For each variant in │ │ Clone the impl block │ │ VisitMut walks AST │ │ (Sendable, Local, ...) │ │ and transform: │ │ replacing K only │ │ │ │ │ │ in type positions │ └─────────────────────────┘ └─────────────────────────┘ └─────────────────────┘ │ │ │ └─────────────────────────┼─────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ │ │ impl Trait for Type { ... } │ │ impl Trait for Type { ... } │ │ impl Trait for Type { ... } │ │ │ └─────────────────────────────────────────────────────┘ ``` The `#[future_form(Sendable, Local)]` macro transforms a generic impl: ```rust #[future_form(Sendable, Local)] impl Trait for Type { fn method(&self) -> K::Future<'_, T> { K::from_future(async { ... }) } } ``` Into concrete impls for each variant: ```rust impl Trait for Type { fn method(&self) -> BoxFuture<'_, T> { Sendable::from_future(async { ... }) } } impl Trait for Type { fn method(&self) -> LocalBoxFuture<'_, T> { Local::from_future(async { ... }) } } ``` Key transformations: 1. Replace `K` type parameter with concrete type (`Sendable`/`Local`) 2. Replace `K::Future<'a, T>` with `BoxFuture<'a, T>`/`LocalBoxFuture<'a, T>` 3. Replace `K::from_future(...)` with `Sendable::from_future(...)`/`Local::from_future(...)` 4. Merge variant-specific `where` clauses into the impl The macro uses `syn::visit_mut::VisitMut` to walk the AST and replace identifiers only in type positions, avoiding mangling identifiers that contain the type parameter as a substring (e.g., `WorksOK` when `K` is the parameter). ## Adding a New Test **Unit test** — Add to `future_form/tests/macro_tests.rs`: ```rust mod my_feature { use super::*; trait MyTrait { ... } #[future_form(Sendable, Local)] impl MyTrait for MyType { ... } #[test] fn test_my_feature_sendable() { ... } #[test] fn test_my_feature_local() { ... } } ``` **Compile-fail test** — Add two files to `future_form/tests/ui/`: 1. `my_error.rs` — Code that should fail to compile 2. `my_error.stderr` — Expected compiler output (generate with `TRYBUILD=overwrite`) ## Release Checklist 1. Update version in workspace `Cargo.toml` 2. Run `cargo test` 3. Run `cargo publish --dry-run -p future_form_macros` 4. Run `cargo publish --dry-run -p future_form` 5. Commit and tag 6. `cargo publish -p future_form_macros` (first — main crate depends on it) 7. `cargo publish -p future_form`