diff --git a/docs/errors/checks/verbose-cascade.md b/docs/errors/checks/verbose-cascade.md index 8fe64434..2056dcba 100644 --- a/docs/errors/checks/verbose-cascade.md +++ b/docs/errors/checks/verbose-cascade.md @@ -46,7 +46,7 @@ The root cause is **present and repeated** — the same `HasField>` spine reads `Symbol!("height")` here. Narrow it to the construct in question rather than reading a whole crate — naming a provider trait gives you every impl of it, which is exactly "what did this component generate": + +```sh +cargo cgp expand --lib --item AreaCalculator # a trait: its definition and every impl of it +cargo cgp expand --lib --item contexts::MockApp # a type: its HasField impls and its wiring entries +``` + +Reading the generated `where` clauses and the exact key types is often enough to spot the defect — a bound on the wrong type, a path with a segment too many, a parameter that should not be there. Where `cargo cgp expand` is unavailable, plain `cargo expand` shows the same expansion without the resugaring; and in a project set up with the CGP test utilities, the `snapshot_*!` helpers in `cgp-macro-test-util` (`snapshot_cgp_impl!`, `snapshot_delegate_components!`, `snapshot_cgp_namespace!`, …) additionally emit the real generated code into the module *and* pretty-print it into an inline snapshot you can review and keep. Comparing two expansions side by side localizes a difference precisely. When one form works and another does not, expand both and diff the output; the delta is the bug. This is how a `#[default_impl]` defect can be pinned: the namespace-registration impl it emitted carried a `where` clause that the equivalent body entry did not, and seeing the two expansions next to each other made the leaked clause obvious. Capturing the expansion as a snapshot doubles as a permanent regression test, so the effort is not throwaway. diff --git a/docs/reference/README.md b/docs/reference/README.md index 6bf0b2c5..cd0830d1 100644 --- a/docs/reference/README.md +++ b/docs/reference/README.md @@ -66,7 +66,7 @@ This index is the catalog of constructs. When you add, remove, or rename a const One document here describes a tool rather than a construct, and it is the exception to the per-construct rule above: it has no subdirectory and no Syntax/Expansion shape. It is registered here because a reader looking up how to *read* a CGP error belongs in the reference. -- [`cargo-cgp`](cargo-cgp.md) — CGP's first-class error toolchain: the cargo subcommand that rewrites CGP compiler errors into a readable, root-cause-first form, how to install and run it, and how its `[CGP-Exxx]` output maps to the [error catalog](../errors/README.md). Recommend it for building, checking, and debugging CGP code. +- [`cargo-cgp`](cargo-cgp.md) — CGP's first-class error toolchain: the cargo subcommand that rewrites CGP compiler errors into a readable, root-cause-first form, how to install and run it, how its `[CGP-Exxx]` output maps to the [error catalog](../errors/README.md), and its companion `expand` command, which shows the ordinary Rust a target's CGP macros generate with the type-level constructs resugared. Recommend it for building, checking, and debugging CGP code. ## Component definition macros — [macros/](macros/) diff --git a/docs/reference/cargo-cgp.md b/docs/reference/cargo-cgp.md index 98f66fb0..11682756 100644 --- a/docs/reference/cargo-cgp.md +++ b/docs/reference/cargo-cgp.md @@ -1,8 +1,8 @@ # cargo-cgp — the CGP error toolchain -`cargo-cgp` is CGP's first-class toolchain: a cargo subcommand that stands in for `cargo check` and rewrites CGP's compiler errors into a compact, root-cause-first form. It is the **recommended way to build and check CGP code and to read a CGP compile error**, and an agent working in this repository should reach for it before plain `cargo check` when diagnosing a wiring failure. This document is enough to install and use it without leaving `cgp`; the tool itself lives in a separate repository, [github.com/contextgeneric/cargo-cgp](https://github.com/contextgeneric/cargo-cgp), and its own docs are the exhaustive reference. +`cargo-cgp` is CGP's first-class toolchain: a cargo subcommand that stands in for `cargo check` and rewrites CGP's compiler errors into a compact, root-cause-first form, and that also expands a target's macros with CGP's type-level constructs made legible. It is the **recommended way to build and check CGP code and to read a CGP compile error**, and an agent working in this repository should reach for it before plain `cargo check` when diagnosing a wiring failure. This document is enough to install and use it without leaving `cgp`; the tool itself lives in a separate repository, [github.com/contextgeneric/cargo-cgp](https://github.com/contextgeneric/cargo-cgp), and its own docs are the exhaustive reference. -This is a *tooling* reference, not a CGP construct — it is the one document under `reference/` that describes an external tool rather than a macro, trait, or provider. The [error catalog](../errors/README.md) is its companion: the catalog documents each error *class* and how `cargo-cgp` presents it, while this document covers running the tool. +This is a *tooling* reference, not a CGP construct — it is the one document under `reference/` that describes an external tool rather than a macro, trait, or provider. The [error catalog](../errors/README.md) is its companion: the catalog documents each error *class* and how `cargo-cgp` presents it, while this document covers running the tool — both its `check` and its [`expand`](#expanding-the-generated-code) command. ## Why it exists @@ -49,7 +49,7 @@ cargo cgp check # like `cargo check`, with CGP errors reshaped cargo cgp check --workspace # every argument after `check` is forwarded to `cargo check` ``` -`check` is the only command cargo-cgp adds to your build workflow — it re-compiles your code under the pinned nightly purely to produce readable diagnostics. There is no `cargo cgp build`, `run`, or `test`; run those with plain cargo (besides `check`, only `setup` and `update` exist, and they merely provision the tool). **cargo-cgp is optional, and its one advantage is readable errors during development.** CGP itself is an ordinary library that compiles on any stable Rust ≥ 1.89, so `cargo check`, `cargo build`, `cargo run`, and `cargo test` all work on a CGP project unchanged. Reach for `cargo cgp check` when you hit — or expect — a wiring error and want it readable; use plain `cargo check` when you do not; and always build, run, and test with ordinary cargo. +`check` is the command you reach for day to day; its companion [`expand`](#expanding-the-generated-code) shows the Rust your macros generate. Neither changes how you build: both re-compile your code under the pinned nightly purely to show you something, and there is no `cargo cgp build`, `run`, or `test`; run those with plain cargo (besides these two, only `setup` and `update` exist, and they merely provision the tool). **cargo-cgp is optional, and its advantage is readable errors — and readable expansions — during development.** CGP itself is an ordinary library that compiles on any stable Rust ≥ 1.89, so `cargo check`, `cargo build`, `cargo run`, and `cargo test` all work on a CGP project unchanged. Reach for `cargo cgp check` when you hit — or expect — a wiring error and want it readable; use plain `cargo check` when you do not; and always build, run, and test with ordinary cargo. Read its output as ordinary rustc/cargo diagnostics with the recognized CGP errors rewritten. When the tool rewrites an error into a known class it stamps a short code in brackets and leads with the cause: @@ -74,6 +74,65 @@ To use it as **Rust Analyzer's** on-save check backend so the reshaped errors ap Never apply this to a user's Rust Analyzer configuration on your own initiative — present it as an option and edit their editor settings only when they explicitly ask. +## Expanding the generated code + +`cargo cgp expand` prints the crate as the compiler sees it after macro expansion, which answers the question a CGP error cannot: *what did that macro actually generate?* Reach for it when a wiring failure stops making sense from the message alone — the failure is always "the emitted impls do not resolve," and you cannot reason about which impl is wrong until you read it — or simply to confirm what a construct produces before trusting your memory of it. + +What makes it worth using over plain `cargo expand` is that CGP's type-level constructs come back **resugared**: a field tag reads `Symbol!("width")` rather than the raw `Symbol<5, Chars<'w', …>>` spine the compiler prints, a handler pipeline reads `Product![StepOne, StepTwo]`, and a namespace key reads `Path!(@app.GreeterComponent)`. Everything else is a full expansion — `#[derive(Debug)]` and `println!` appear in their generated form too — so what you get is the whole program with the CGP parts legible. Expanding the [area-calculation](../examples/area-calculation.md) example's `rectangle_area` function shows the shape: + +```sh +cargo cgp expand --lib --item RectangleArea +``` + +```rust +pub trait RectangleArea { + fn rectangle_area(&self) -> f64; +} +impl<__Context__> RectangleArea for __Context__ +where + Self: HasField + + HasField, +{ + fn rectangle_area(&self) -> f64 { + let width: f64 = self + .get_field(::core::marker::PhantomData::) + .clone(); + let height: f64 = self + .get_field(::core::marker::PhantomData::) + .clone(); + width * height + } +} +``` + +Reading that answers two questions at once: what the trait `#[cgp_fn]` generated looks like, and what the `#[implicit]` arguments turned into — a `HasField` bound per argument and a `get_field` call that clones the value. + +**It expands exactly one target**, so a package with both a library and a binary needs `--lib` or `--bin NAME`; without one, cargo declines with *"extra arguments to `rustc` can only be passed to one target"*. Everything else is forwarded to `cargo rustc`, so `-p`, `--features`, and the rest work as usual, and the expansion goes to stdout so it pipes and redirects: + +```sh +cargo cgp expand --lib # the whole library target +cargo cgp expand --bin server # one binary +cargo cgp expand --lib > expanded.rs # stdout, so redirect or pipe freely +``` + +**A whole crate's expansion is long, so `--item ` narrows it to one part.** The path names a module or item inside the crate being expanded, written as a `::`-separated path with an optional leading `crate::` (`self::` and a bare `::` work too), and what it selects depends on what it names: + +```sh +cargo cgp expand --lib --item contexts # a module: its contents +cargo cgp expand --lib --item contexts::MockApp # a type: its declaration and every impl for it +cargo cgp expand --lib --item AreaCalculator # a trait: its definition and every impl of it +``` + +The **trait** form is the one to reach for on CGP code, because a component's generated items are almost all impls and an impl has no name of its own. Naming a component's *provider* trait (`--item AreaCalculator`) gives you the provider trait definition, the delegation blanket impl every wired context resolves through, the [`UseContext`](providers/use_context.md) and [`RedirectLookup`](providers/redirect_lookup.md) impls, and each provider's own impl of it — the whole set of things that can answer "which provider serves this component, and under what bounds?" Naming the *consumer* trait (`--item CanCalculateArea`) is the smaller companion: the consumer trait and the one blanket impl that routes it to the provider trait. + +The **type** form is what you want for a context. Naming a context (`--item Rectangle`) gives its struct, the `HasField`/`HasFieldMut` impls its [`#[derive(HasField)]`](derives/derive_has_field.md) generated, and its `DelegateComponent` wiring entries with the real key and provider types — which is how you check that a wiring table produced the keys you intended rather than inferring them from the table's syntax. + +If a path matches nothing you get an error saying so, naming the path, rather than a silent whole-crate expansion. + +Two limits are worth knowing. `expand` is **not a check**: it stops as soon as the macros are expanded, so type-checking never runs and it reports nothing about wiring — a malformed macro invocation still fails, since that happens during expansion, but a missing field does not. And the output is meant to be *read* rather than compiled: the `cgp::macro_prelude::` qualifier the macros emit is stripped for legibility, and an `open` statement's per-key wiring entry keeps its raw `PathCons<…>` key, since its tail is a generic parameter that no `Path!` spelling covers. + +`expand` is newer than the v0.1.0-alpha release, so a `cargo install cargo-cgp` from crates.io does not yet carry it. Until the next release, get it by dropping the tag from the Nix reference (`nix run github:contextgeneric/cargo-cgp -- expand --lib`, which tracks `main`) or by [building from a checkout](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/reference/installation.md#installing-from-source). + ## When cargo-cgp is not available If the tool is not installed and cannot be run, fall back to reading the raw compiler output directly. Every class in the [error catalog](../errors/README.md) documents that raw diagnostic — its code, shape, and whether the root cause is present — as the fallback for exactly this case, and the [error-extraction sub-skill](https://github.com/contextgeneric/cgp/blob/main/docs/skills/cgp/references/error-extraction.md) is the technique for reducing a raw cascade to its cause by hand. The key difference to remember: without `cargo-cgp`'s next-gen solver, the [hidden classes](../errors/hidden/unsatisfied-dependency.md) genuinely omit the cause, so promote them with a `check_components!` at the wiring site to make it appear. @@ -86,7 +145,7 @@ This documentation is written for **`cgp` v0.8.0** and **`cargo-cgp` v0.1.0-alph All in the `cargo-cgp` repository (link as GitHub URLs, read from `../cargo-cgp` locally when checked out): -- [Usage](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/reference/usage.md) — running the check, reading output, editor integration. +- [Usage](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/reference/usage.md) — running the check, reading output, expanding a target, editor integration. - [Installation](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/reference/installation.md) — every install/update/uninstall path. - [Error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md) — what each `[CGP-Exxx]` means. - [Troubleshooting](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/reference/troubleshooting.md) — when the tool itself will not run. diff --git a/docs/skills/cgp/SKILL.md b/docs/skills/cgp/SKILL.md index 812b870b..d0f9841e 100644 --- a/docs/skills/cgp/SKILL.md +++ b/docs/skills/cgp/SKILL.md @@ -45,9 +45,11 @@ traits and impls, so the reliable way to understand any construct is to know the to. This primer shows those expansions where they matter and keeps them out of the way where they do not. -## Tooling: use cargo-cgp for readable errors +## Tooling: use cargo-cgp for readable errors and expansions -**Before debugging any CGP compile error, use `cargo-cgp` if it is available.** `cargo-cgp` is CGP's +**Before debugging any CGP compile error, use `cargo-cgp` if it is available.** It has two commands +you will want: `check`, which reshapes the errors, and +[`expand`](#reading-what-a-macro-generated-cargo-cgp-expand), which shows the code the macros generated. `cargo-cgp` is CGP's first-class toolchain — a cargo subcommand that stands in for `cargo check` and rewrites CGP's compiler errors into a compact, root-cause-first form, tagging each rewritten message with a `[CGP-Exxx]` code. A raw CGP error is often a wall of generated types with the real cause buried or @@ -84,10 +86,69 @@ untouched. Full instructions: `[CGP-Exxx]` tag, leading with the root cause over a `cargo tree`-style dependency chain; the codes are catalogued at . -**Scope:** cargo-cgp is optional and adds only `check` — there is no `cargo cgp build`/`run`/`test`, -so build, run, and test the project with plain cargo. CGP itself compiles on any **stable Rust ≥ -1.89**, so plain `cargo check` works on a CGP project too; reach for `cargo cgp check` specifically -when you hit or expect a wiring error and want it made readable. +### Reading what a macro generated: `cargo cgp expand` + +**When a CGP error stops making sense, read the code the macros produced rather than reasoning about +what you think they produce.** A wiring failure is always "the emitted impls do not resolve," and the +impls are generated, so the fastest way past a confusing diagnostic is often to look at them. +`cargo cgp expand` prints the crate after macro expansion, with CGP's type-level constructs +**resugared** — a field tag reads `Symbol!("width")`, not the raw `Symbol<5, Chars<'w', …>>` +spine the compiler prints; a pipeline reads `Product![StepOne, StepTwo]`; a namespace key reads +`Path!(@app.GreeterComponent)` — so the generated code is legible in the same vocabulary as the +source. + +Reach for it in four situations, each one where the answer is in the generated code rather than in the +message: + +- **A diagnostic names a type you did not write** (`IsProviderFor<…>`, a `PathCons<…>` key, a + `__Context__` parameter) and you need to see the impl it came from. +- **A wiring table does not resolve** and you want the real `DelegateComponent` keys and `Delegate` + values it produced, rather than inferring them from the table's syntax. +- **You are unsure what a construct emits** — the exact `where` clauses of a provider impl, whether a + getter's blanket impl requires the field you think it does. Reading the expansion beats guessing, and + beats trusting a remembered expansion. +- **Two forms differ and only one compiles.** Expand both and diff; the delta is the bug. + +Run it on one target, narrowed to what you care about: + +```sh +cargo cgp expand --lib # the whole library target +cargo cgp expand --lib --item contexts::MockApp # one module, type, or trait +``` + +**Target selection is required when a package has several targets** — pass `--lib` or `--bin NAME`, or +cargo declines with "extra arguments to `rustc` can only be passed to one target". Other arguments +(`-p`, `--features`) forward to `cargo rustc`, and the expansion goes to stdout, so redirect it to a +file when it is long and read that instead of flooding your context. + +**`--item ` is what makes the output manageable**, and which of three rules applies depends on +what the path names. The path is `::`-separated, may carry a leading `crate::`, and names something +inside the crate being expanded: + +- a **module** → its contents (`--item contexts`); +- a **type** → its declaration and every impl written *for* it. For a context that is its struct, the + `HasField`/`HasFieldMut` impls the derive generated, and its `DelegateComponent` wiring entries with + the real key and provider types (`--item Rectangle`); +- a **trait** → its definition and every impl *of* it, which is usually the rule you want. Naming a + component's *provider* trait (`--item AreaCalculator`) gives the provider trait, the delegation + blanket impl, the `UseContext` and `RedirectLookup` impls, and each wired provider's impl; naming the + *consumer* trait (`--item CanCalculateArea`) gives just the consumer trait and its routing blanket. + +Two limits matter when you read the output. **`expand` is not a check**: it stops once the macros are +expanded, so it reports nothing about wiring — use `cargo cgp check` for that, and expect `expand` to +succeed even on a crate that does not type-check (which is exactly what makes it useful mid-debugging). +And the output is for reading, not compiling: the `cgp::macro_prelude::` qualifier is stripped, and an +`open` statement's per-key entry keeps its raw `PathCons<…>` key. + +One availability note: `expand` is newer than cargo-cgp v0.1.0-alpha, so a crates.io install does not +carry it yet. Until the next release it comes from the Nix flake without a tag +(`nix run github:contextgeneric/cargo-cgp -- expand --lib`) or from a source checkout; see +. + +**Scope:** cargo-cgp is optional and adds only `check` and `expand` — there is no +`cargo cgp build`/`run`/`test`, so build, run, and test the project with plain cargo. CGP itself +compiles on any **stable Rust ≥ 1.89**, so plain `cargo check` works on a CGP project too; reach for +`cargo cgp check` specifically when you hit or expect a wiring error and want it made readable. `cargo-cgp` can also be Rust Analyzer's on-save check backend, via `rust-analyzer.check.overrideCommand` set to diff --git a/docs/skills/cgp/references/error-extraction.md b/docs/skills/cgp/references/error-extraction.md index 83d6b1f2..4f11d7a8 100644 --- a/docs/skills/cgp/references/error-extraction.md +++ b/docs/skills/cgp/references/error-extraction.md @@ -42,7 +42,15 @@ cargo test -p --test 2>&1 | tee /tmp/cgp-error.txt Two details recur, both plain `rustc` behavior rather than anything CGP-specific. When a type in the error is elided as `...`, the compiler writes its full form to a file named in a final note (`the full name for the type has been written to '….long-type-….txt'`); that elided middle is frequently the one segment that reveals *which* context or path the error is about, so read that file when the cause hinges on a long type. And watch for the **near-contradiction** shape — "the trait `X` is not implemented for `T`" immediately followed by a `help:` note that `X` *is* implemented for `T` — which means an impl exists but a nested bound it carries does not hold, or two candidates are ambiguous; trust the error, not the `help:`. -When the cause hinges on *what the macro emitted* rather than on the message itself, stop reading the error and read the expansion. `cargo expand` prints the macro-expanded source in any project; in a project set up with the CGP test utilities, the `snapshot_*!` helpers from `cgp-macro-test-util` additionally pin an expansion as a reviewable snapshot. The [macro-grammar](macro-grammar.md) skill covers how to read the expanded impls once you have them. +When the cause hinges on *what the macro emitted* rather than on the message itself, stop reading the error and read the expansion. Reach for `cargo cgp expand` first: it prints the crate after macro expansion with CGP's type-level constructs resugared, so a field tag that the error rendered as a `Chars` spine reads `Symbol!("height")` in the expansion — the generated code is often easier to read than the diagnostic about it. Narrow it to the part you care about, since a whole crate's expansion is long and a filtered one keeps your context clear: + +```sh +cargo cgp expand --lib --item AreaCalculator # a trait: every impl of it — what a component generated +cargo cgp expand --lib --item contexts::MockApp # a type: its HasField impls and its wiring entries +cargo cgp expand --lib > /tmp/expanded.rs # or redirect the whole thing and grep it +``` + +Pass `--lib` or `--bin NAME` when the package has several targets, or cargo refuses to run. Plain `cargo expand` is the fallback where `cargo cgp expand` is unavailable — it shows the same expansion without the resugaring — and in a project set up with the CGP test utilities the `snapshot_*!` helpers from `cgp-macro-test-util` additionally pin an expansion as a reviewable snapshot. The [macro-grammar](macro-grammar.md) skill covers how to read the expanded impls once you have them. ### Reduce to the compact summary diff --git a/docs/skills/cgp/references/type-level-primitives.md b/docs/skills/cgp/references/type-level-primitives.md index 7204c4cb..f742494f 100644 --- a/docs/skills/cgp/references/type-level-primitives.md +++ b/docs/skills/cgp/references/type-level-primitives.md @@ -6,7 +6,7 @@ The handful of zero-sized types and type macros that CGP folds strings, numbers, CGP keys nearly everything by *type*, not by value. A getter looks up a field by a tag type; a [wiring](wiring.md) table selects a [provider](components.md) by a [component](components.md) key type; a [namespace](namespaces.md) re-routes a lookup along a path type. For that to work, things that are normally values — a field name string, a tuple position, a list of fields, a lifetime — have to be encoded as types the compiler can compare and dispatch on. The primitives in this reference are those encodings. Each is a familiar value-level idea lifted into a type: a string becomes a type-level character list, a number becomes a const-generic marker, a list becomes a recursive cons cell, a sum becomes a recursive branch. -These types are almost never written by hand. They are produced by macros (`Symbol!`, `Product!`, `Sum!`, `Path!`) or emitted by derives, and a reader mostly meets them when *decoding a type the compiler prints* — in an error message, a `cargo expand` dump, or a hover. This reference is a decoder ring: skim it to read off what a long nested type means. The prose below always uses the readable `Cons`/`Nil`/`Symbol!` forms, which are exactly what the compiler prints — no abbreviations or aliases are substituted for these types. +These types are almost never written by hand. They are produced by macros (`Symbol!`, `Product!`, `Sum!`, `Path!`) or emitted by derives, and a reader mostly meets them when *decoding a type the compiler prints* — in an error message, a macro-expansion dump, or a hover. (`cargo cgp expand` resugars them back to `Symbol!`/`Product!`/`Path!`, so a raw spine there means the resugaring declined; an error message or a plain `cargo expand` shows them as they are.) This reference is a decoder ring: skim it to read off what a long nested type means. The prose below always uses the readable `Cons`/`Nil`/`Symbol!` forms, which are exactly what the compiler prints — no abbreviations or aliases are substituted for these types. Assume `use cgp::prelude::*;` throughout.