|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +`cgp-macro-core` is the pure-logic core of the CGP proc-macro suite. **Always invoke the `/cgp` |
| 6 | +skill before working here** — every type in this crate exists to generate one of the CGP constructs |
| 7 | +the skill describes (`#[cgp_component]`, `#[cgp_impl]`, `#[cgp_fn]`, `delegate_components!`, |
| 8 | +`check_components!`, `#[derive(HasField)]`, …). You cannot correctly change the *output* of a macro |
| 9 | +without knowing the *expansion* it is supposed to produce, and that expansion is documented by |
| 10 | +`/cgp`. The workspace-level [../../../CLAUDE.md](../../../CLAUDE.md) covers build/test/lint commands |
| 11 | +and the crate hierarchy. |
| 12 | + |
| 13 | +## What this crate is (and isn't) |
| 14 | + |
| 15 | +- It contains **no `#[proc_macro]` entrypoints** and does **not depend on `proc-macro`** — only |
| 16 | + `syn` (with `full`, `extra-traits`, `visit`, `visit-mut`), `quote`, `proc-macro2`, `itertools`. |
| 17 | + Everything operates on `proc_macro2::TokenStream` and `syn` AST. |
| 18 | +- The proc-macro pipeline is **`cgp-macro` (entrypoints) → `cgp-macro-lib` (per-macro glue) → |
| 19 | + `cgp-macro-core` (this crate, all the logic)**. A `cgp-macro-lib` function is thin: `syn::parse2` |
| 20 | + the attr/item, build a `cgp_macro_core::types::<macro>::Item*`, run its transform pipeline, and |
| 21 | + wrap the result in `quote!`. See `cgp-macro-lib/src/cgp_component.rs` for the canonical shape. |
| 22 | +- Because it is `proc-macro`-free, its parsers and codegen are **unit-testable as plain functions**. |
| 23 | + Tests live in the sibling `crates/tests/cgp-macro-tests` crate (parser corner cases like |
| 24 | + `IdentWithTypeArgs`, plus expansion **snapshots** via the `snapshot_*` macros). Prefer adding |
| 25 | + coverage there over ad-hoc verification. |
| 26 | + |
| 27 | +## Module map |
| 28 | + |
| 29 | +- **[src/types/](src/types/)** — the bulk of the crate. One submodule per user-facing macro |
| 30 | + (`cgp_component`, `cgp_impl`, `cgp_provider`, `cgp_fn`, `cgp_getter`, `cgp_auto_getter`, |
| 31 | + `cgp_type`, `cgp_data`, `delegate_component`, `check_components`, |
| 32 | + `delegate_and_check_components`, `namespace`, `product`, `sum`), plus shared building-block types: |
| 33 | + `attributes/` (parsing of `#[uses]`, `#[use_type]`, `#[use_provider]`, `#[derive_delegate]`, |
| 34 | + `#[default_impl]`), `generics/`, `field/`, `getter/`, `implicits/`, `ident/`, `path/`, `keyword`. |
| 35 | +- **[src/functions/](src/functions/)** — free helper functions: identifier case conversion |
| 36 | + (`camel_case`/`snake_case`), `parse_internal`, generics merging, delegated-impl synthesis, |
| 37 | + field/getter/implicit-argument parsing, `strip`. |
| 38 | +- **[src/visitors/](src/visitors/)** — `syn` `VisitMut` passes that rewrite ASTs. `replace_self` |
| 39 | + (receiver/type/value) is the heart of `#[cgp_impl]`: it rewrites `self`/`Self` into the explicit |
| 40 | + `context`/`Context`. Also `replace_provider`, `remove_self_path`, `self_assoc_type`, and |
| 41 | + `substitute_abstract_type` (the `#[use_type]` machinery). **Do AST rewriting with these visitors, |
| 42 | + never with string manipulation.** |
| 43 | +- **[src/exports.rs](src/exports.rs)** — see "hygiene" below. |
| 44 | +- **[src/traits/](src/traits/)** — small internal traits (`ToType`, `IsKeyword`, bound helpers). |
| 45 | +- **[src/macros/](src/macros/)** — internal `macro_rules!`: `parse_internal!`, `export_construct(s)!`, |
| 46 | + `define_keyword!`. |
| 47 | +- **[src/vendor.rs](src/vendor.rs)** — re-exports `quote::quote` so exported `macro_rules!` can use |
| 48 | + it from downstream crates. |
| 49 | + |
| 50 | +## Conventions you must follow |
| 51 | + |
| 52 | +**1. Construct = a type implementing `Parse` and/or `ToTokens`.** Input is parsed by implementing |
| 53 | +`syn::parse::Parse`; code is emitted by implementing `quote::ToTokens`. Add new syntax by writing a |
| 54 | +type with these impls rather than hand-rolling token munging. |
| 55 | + |
| 56 | +**2. Build AST nodes with `parse_internal!`, not by hand.** `parse_internal!(#some_tokens ...)` |
| 57 | +(in [src/functions/parse_internal.rs](src/functions/parse_internal.rs)) quasi-quotes tokens and |
| 58 | +parses them into the target `syn` type, attaching a descriptive error (including the offending |
| 59 | +tokens with the `::cgp::macro_prelude::` prefix stripped) on failure. It expands to a `?` |
| 60 | +expression, so call it inside a `syn::Result`-returning function. |
| 61 | + |
| 62 | +**3. Reference CGP items through `exports.rs`, never by hardcoded path.** Each name in |
| 63 | +[src/exports.rs](src/exports.rs) (`IsProviderFor`, `DelegateComponent`, `UseField`, `HasField`, …) |
| 64 | +is a zero-sized marker struct whose `ToTokens` emits the fully-qualified path |
| 65 | +`::cgp::macro_prelude::<Name>`. Generated code interpolates these markers (e.g. |
| 66 | +`use crate::exports::IsProviderFor;` then `#is_provider_for`) so the expansion is hygienic and the |
| 67 | +user only needs `cgp` in scope. **To emit a new CGP item, add it to `export_constructs!` in |
| 68 | +`exports.rs` and interpolate the marker** — don't write `::cgp::...` path literals inline. |
| 69 | + |
| 70 | +**4. Codegen is a staged transform pipeline.** Each macro type moves through explicit stages rather |
| 71 | +than emitting in one pass. The reference shape is `cgp_component`: |
| 72 | +`ItemCgpComponent { args, item_trait }` → `.preprocess()` → `PreprocessedCgpComponent` → |
| 73 | +`.eval()` → `Evaluated…` → `.to_items()` → `Vec<syn::Item>` (driven by |
| 74 | +`cgp-macro-lib/src/cgp_component.rs` as `item.preprocess()?.eval()?.to_items()?`). Other macros use |
| 75 | +their own stage names — look for `item.rs`, `preprocessed.rs`/`lowered.rs`, `evaluated/`, and |
| 76 | +`to_*` methods within each `types/<macro>/` directory. When extending a macro, slot your change |
| 77 | +into the existing stage rather than collapsing the pipeline. |
| 78 | + |
| 79 | +**5. Custom keywords go through `define_keyword!` + `IsKeyword`** (see |
| 80 | +[src/macros/keyword.rs](src/macros/keyword.rs) and `types/keyword*.rs`). |
0 commit comments