Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Understanding CGP first

This repository **is** the implementation of Context-Generic Programming (CGP). Before reading or
writing any CGP code here, **always invoke the `/cgp` skill** to load the fundamentals (consumer vs.
provider traits, `#[cgp_component]`/`#[cgp_impl]`/`#[cgp_fn]`, `delegate_components!`, `HasField`,
`UseDelegate`, check traits, etc.). Re-invoke it whenever you navigate into unfamiliar parts of the
codebase — the macros and core traits here are the ground truth that the skill describes, so the two
should always be read together.

The canonical export surface for users is `cgp::prelude` — see
[crates/main/cgp/src/prelude.rs](crates/main/cgp/src/prelude.rs), which re-exports
`cgp_core::prelude` + `cgp_extra::prelude`. When unsure what a name resolves to, start from the
prelude re-exports in [crates/main/cgp-core/src/prelude.rs](crates/main/cgp-core/src/prelude.rs).

## Commands

This is a Cargo workspace (edition 2024, resolver 3). Toolchain is pinned to **1.93** via
[rust-toolchain.toml](rust-toolchain.toml). Nearly every crate is `#![no_std]` — keep new code
`no_std`-compatible (use `core`/`alloc`, gate `std`/`alloc` usage behind features as existing crates
do).

- **Format** (requires nightly — `.rustfmt.toml` uses unstable `group_imports`/`imports_granularity`):
`cargo +nightly fmt --all` (check: `cargo +nightly fmt --all -- --check`)
- **Lint:** `cargo clippy --all-features --all-targets -- -D warnings`
and `cargo clippy --no-default-features --all-targets -- -D warnings`
- **Test** (uses `cargo-nextest`): `cargo nextest run --all-features --no-fail-fast --workspace`
- **Single test crate / test:** `cargo nextest run -p cgp-tests` or target one file with the
standard test harness, e.g. `cargo test -p cgp-tests --test component`
- Many "tests" are **compile-time wiring checks** (`check_components!` /
`delegate_and_check_components!`) and **macro-expansion snapshots** — for these, a successful
`cargo build`/`cargo test` compilation *is* the passing test. A wiring mistake surfaces as a
compile error, not a runtime failure.

## Architecture: layered micro-crates

Crates are organized so that low-level primitives have no knowledge of the high-level facade. Work
inward (core/macros) when changing fundamentals, outward (main) only to adjust the public surface.

- **`crates/macros/`** — the proc-macro pipeline. `cgp-macro` is a thin `#[proc_macro]` entrypoint
that forwards to `cgp-macro-lib` (one module per macro), which in turn builds on
**`cgp-macro-core`** — this is where the real parsing, AST types, and codegen live (see
`cgp-macro-core/src/{types,functions,visitors,macros}/`). When a macro misbehaves, the logic to
fix is almost always in `cgp-macro-core`, not the entrypoint crate. `cgp-async-macro` provides
`#[async_trait]`; `cgp-extra-macro{,-lib}` host the extra-feature macros.

- **`crates/core/`** — the foundational runtime traits the macros expand into:
- `cgp-component` — the wiring machinery: `DelegateComponent`, `IsProviderFor`,
`CanUseComponent`, `UseContext`, `UseDelegate`, `UseField`, `WithProvider`, etc.
- `cgp-type` — abstract types: `HasType`, `TypeProvider`, `UseType`.
- `cgp-field` — `HasField` and extensible data: `Cons`/`Nil`, `Symbol`, `Index`, `Field`,
builders/extractors for records and variants.
- `cgp-error` — `HasErrorType`, `CanRaiseError`, `CanWrapError`.
- `cgp-base-types` — the lowest-level type-level primitives (`Symbol`/`Chars`/`Cons`/`Nil`/path).

- **`crates/extra/`** — higher-level building blocks layered on core: `cgp-handler`,
`cgp-dispatch`, `cgp-monad`, `cgp-run`, `cgp-runtime`, `cgp-field-extra`, `cgp-error-extra`.

- **`crates/main/`** — facade crates that only re-export. `cgp` is the crate users depend on
(`cgp = core + extra`, exposing `cgp::prelude`). `cgp-core`/`cgp-extra`/`cgp-base`/
`cgp-base-extra` are intermediate bundles. Changes here are almost always just re-export plumbing.

- **`crates/standalone/error/`** — pluggable error backends implementing the `cgp-error` traits:
`cgp-error-anyhow`, `cgp-error-eyre`, `cgp-error-std`. These are opt-in and not part of the
default `cgp` facade.

- **`crates/tests/`** — `cgp-tests` exercises real wiring and the user-facing macros end-to-end;
`cgp-macro-tests` covers parser corner cases plus **expansion snapshots** via the `snapshot_*`
proc macros in `cgp-macro-test-util{,-lib}` (which pretty-print generated code with
`prettyplease`). When you change macro codegen, expect snapshot output to change — update and
review the expanded code, since it is the contract users see.

## Conventions specific to this repo

- All versions are kept in lockstep at the workspace level (currently **0.7.0**); inter-crate
dependencies are declared once in the root [Cargo.toml](Cargo.toml) `[workspace.dependencies]`
and referenced with `{ workspace = true }`. Add new crates to the `members` list and the
workspace dependency table together.
- The crate split is deliberate (coherence-friendly micro-crates). When adding functionality,
place it in the lowest layer that makes sense and re-export upward through the facade crates,
rather than adding cross-layer dependencies that skip the hierarchy.
- See [CHANGELOG.md](CHANGELOG.md) for the evolution of macro syntax — it is the most reliable
record of which macro forms are current vs. removed (e.g. `#[cgp_context]` was removed,
`ProvideType` → `TypeProvider`).
80 changes: 80 additions & 0 deletions crates/macros/cgp-macro-core/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

`cgp-macro-core` is the pure-logic core of the CGP proc-macro suite. **Always invoke the `/cgp`
skill before working here** — every type in this crate exists to generate one of the CGP constructs
the skill describes (`#[cgp_component]`, `#[cgp_impl]`, `#[cgp_fn]`, `delegate_components!`,
`check_components!`, `#[derive(HasField)]`, …). You cannot correctly change the *output* of a macro
without knowing the *expansion* it is supposed to produce, and that expansion is documented by
`/cgp`. The workspace-level [../../../CLAUDE.md](../../../CLAUDE.md) covers build/test/lint commands
and the crate hierarchy.

## What this crate is (and isn't)

- It contains **no `#[proc_macro]` entrypoints** and does **not depend on `proc-macro`** — only
`syn` (with `full`, `extra-traits`, `visit`, `visit-mut`), `quote`, `proc-macro2`, `itertools`.
Everything operates on `proc_macro2::TokenStream` and `syn` AST.
- The proc-macro pipeline is **`cgp-macro` (entrypoints) → `cgp-macro-lib` (per-macro glue) →
`cgp-macro-core` (this crate, all the logic)**. A `cgp-macro-lib` function is thin: `syn::parse2`
the attr/item, build a `cgp_macro_core::types::<macro>::Item*`, run its transform pipeline, and
wrap the result in `quote!`. See `cgp-macro-lib/src/cgp_component.rs` for the canonical shape.
- Because it is `proc-macro`-free, its parsers and codegen are **unit-testable as plain functions**.
Tests live in the sibling `crates/tests/cgp-macro-tests` crate (parser corner cases like
`IdentWithTypeArgs`, plus expansion **snapshots** via the `snapshot_*` macros). Prefer adding
coverage there over ad-hoc verification.

## Module map

- **[src/types/](src/types/)** — the bulk of the crate. One submodule per user-facing macro
(`cgp_component`, `cgp_impl`, `cgp_provider`, `cgp_fn`, `cgp_getter`, `cgp_auto_getter`,
`cgp_type`, `cgp_data`, `delegate_component`, `check_components`,
`delegate_and_check_components`, `namespace`, `product`, `sum`), plus shared building-block types:
`attributes/` (parsing of `#[uses]`, `#[use_type]`, `#[use_provider]`, `#[derive_delegate]`,
`#[default_impl]`), `generics/`, `field/`, `getter/`, `implicits/`, `ident/`, `path/`, `keyword`.
- **[src/functions/](src/functions/)** — free helper functions: identifier case conversion
(`camel_case`/`snake_case`), `parse_internal`, generics merging, delegated-impl synthesis,
field/getter/implicit-argument parsing, `strip`.
- **[src/visitors/](src/visitors/)** — `syn` `VisitMut` passes that rewrite ASTs. `replace_self`
(receiver/type/value) is the heart of `#[cgp_impl]`: it rewrites `self`/`Self` into the explicit
`context`/`Context`. Also `replace_provider`, `remove_self_path`, `self_assoc_type`, and
`substitute_abstract_type` (the `#[use_type]` machinery). **Do AST rewriting with these visitors,
never with string manipulation.**
- **[src/exports.rs](src/exports.rs)** — see "hygiene" below.
- **[src/traits/](src/traits/)** — small internal traits (`ToType`, `IsKeyword`, bound helpers).
- **[src/macros/](src/macros/)** — internal `macro_rules!`: `parse_internal!`, `export_construct(s)!`,
`define_keyword!`.
- **[src/vendor.rs](src/vendor.rs)** — re-exports `quote::quote` so exported `macro_rules!` can use
it from downstream crates.

## Conventions you must follow

**1. Construct = a type implementing `Parse` and/or `ToTokens`.** Input is parsed by implementing
`syn::parse::Parse`; code is emitted by implementing `quote::ToTokens`. Add new syntax by writing a
type with these impls rather than hand-rolling token munging.

**2. Build AST nodes with `parse_internal!`, not by hand.** `parse_internal!(#some_tokens ...)`
(in [src/functions/parse_internal.rs](src/functions/parse_internal.rs)) quasi-quotes tokens and
parses them into the target `syn` type, attaching a descriptive error (including the offending
tokens with the `::cgp::macro_prelude::` prefix stripped) on failure. It expands to a `?`
expression, so call it inside a `syn::Result`-returning function.

**3. Reference CGP items through `exports.rs`, never by hardcoded path.** Each name in
[src/exports.rs](src/exports.rs) (`IsProviderFor`, `DelegateComponent`, `UseField`, `HasField`, …)
is a zero-sized marker struct whose `ToTokens` emits the fully-qualified path
`::cgp::macro_prelude::<Name>`. Generated code interpolates these markers (e.g.
`use crate::exports::IsProviderFor;` then `#is_provider_for`) so the expansion is hygienic and the
user only needs `cgp` in scope. **To emit a new CGP item, add it to `export_constructs!` in
`exports.rs` and interpolate the marker** — don't write `::cgp::...` path literals inline.

**4. Codegen is a staged transform pipeline.** Each macro type moves through explicit stages rather
than emitting in one pass. The reference shape is `cgp_component`:
`ItemCgpComponent { args, item_trait }` → `.preprocess()` → `PreprocessedCgpComponent` →
`.eval()` → `Evaluated…` → `.to_items()` → `Vec<syn::Item>` (driven by
`cgp-macro-lib/src/cgp_component.rs` as `item.preprocess()?.eval()?.to_items()?`). Other macros use
their own stage names — look for `item.rs`, `preprocessed.rs`/`lowered.rs`, `evaluated/`, and
`to_*` methods within each `types/<macro>/` directory. When extending a macro, slot your change
into the existing stage rather than collapsing the pipeline.

**5. Custom keywords go through `define_keyword!` + `IsKeyword`** (see
[src/macros/keyword.rs](src/macros/keyword.rs) and `types/keyword*.rs`).
2 changes: 1 addition & 1 deletion crates/main/cgp-core/src/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub use cgp_field::traits::{
pub use cgp_field::types::{Chars, Cons, Either, Field, Index, Life, Nil, Symbol, Void};
pub use cgp_macro::{
BuildField, CgpData, CgpRecord, CgpVariant, ExtractField, FromVariant, HasField, HasFields,
Product, Sum, Symbol, cgp_auto_getter, cgp_component, cgp_fn, cgp_getter, cgp_impl,
Path, Product, Sum, Symbol, cgp_auto_getter, cgp_component, cgp_fn, cgp_getter, cgp_impl,
cgp_namespace, cgp_new_provider, cgp_provider, cgp_type, check_components,
delegate_and_check_components, delegate_components, product,
};
Expand Down
Loading
Loading