From 71825bdd75c640efb015e22e9ac7e5022c5b990b Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Tue, 21 Jul 2026 00:19:50 +0200 Subject: [PATCH] Adopt cargo-cgp as CGP's first-class error toolchain - Remove the cgp-compile-fail-tests suite and cgp-test-crate-a/-b (migrated to cargo-cgp UI fixtures); drop the rust-src pin that only that suite needed. - Error catalog: link the cargo-cgp UI fixtures (GitHub URLs), add a "How cargo-cgp presents it" section per class, keep the raw diagnostics as a fallback, and add error_codes/cargo-cgp-codes.md. - Add docs/reference/cargo-cgp.md; update the CGP skill with must-have install/use guidance, version-check behaviour, and the "don't touch RA settings" caveat; lead the debugging guide with cargo-cgp (hand techniques as fallback). - Communication strategy: position cargo-cgp as removing the unreadable-errors adoption obstacle (honesty-calibrated; alpha conceded). - AGENTS.md: add the cross-project discovery/GitHub-link/sync rules and redirect the fixture-capture workflow to cargo-cgp. - README: recommend installing cargo-cgp when getting started, and clarify it is optional and that CGP builds on stable Rust. Co-Authored-By: Claude Opus 4.8 (1M context) --- AGENTS.md | 57 ++- Cargo.lock | 199 ----------- Cargo.toml | 5 - README.md | 13 + crates/tests/AGENTS.md | 161 +++------ crates/tests/README.md | 46 +-- .../tests/cgp-compile-fail-tests/Cargo.toml | 18 - crates/tests/cgp-compile-fail-tests/README.md | 78 ---- .../tests/cgp-compile-fail-tests/src/lib.rs | 6 - .../cgp_auto_getter/option_slice.rs | 22 -- .../cgp_auto_getter/option_slice.stderr | 30 -- .../cgp_component/duplicate_component_name.rs | 30 -- .../duplicate_component_name.stderr | 10 - .../use_type_foreign_unsatisfied.rs | 33 -- .../use_type_foreign_unsatisfied.stderr | 83 ----- .../cgp_fn/use_type_cyclic_context.rs | 37 -- .../cgp_fn/use_type_cyclic_context.stderr | 11 - .../cgp_fn/use_type_nested_unsatisfied.rs | 53 --- .../cgp_fn/use_type_nested_unsatisfied.stderr | 171 --------- .../cgp_fn/use_type_unknown_assoc.rs | 30 -- .../cgp_fn/use_type_unknown_assoc.stderr | 13 - .../cgp_impl/duplicate_default_impl.rs | 42 --- .../cgp_impl/duplicate_default_impl.stderr | 8 - .../cgp_impl/duplicate_provider_name.rs | 35 -- .../cgp_impl/duplicate_provider_name.stderr | 36 -- .../default_impl_foreign_component.rs | 38 -- .../default_impl_foreign_component.stderr | 9 - .../default_impl_foreign_prefix_path.rs | 36 -- .../default_impl_foreign_prefix_path.stderr | 9 - .../cgp_namespace/duplicate_path_key.rs | 26 -- .../cgp_namespace/duplicate_path_key.stderr | 7 - .../cgp_namespace/for_loop_bare_key.rs | 55 --- .../cgp_namespace/for_loop_bare_key.stderr | 17 - .../cgp_namespace/inheritance_cycle.rs | 29 -- .../cgp_namespace/inheritance_cycle.stderr | 53 --- .../inherited_override_conflict.rs | 59 ---- .../inherited_override_conflict.stderr | 7 - .../cgp_namespace/override_registered_path.rs | 61 ---- .../override_registered_path.stderr | 20 -- .../cgp_namespace/reopen_foreign_namespace.rs | 34 -- .../reopen_foreign_namespace.stderr | 13 - .../cgp_namespace/two_namespaces_joined.rs | 57 --- .../two_namespaces_joined.stderr | 15 - .../cgp_namespace/unregistered_prefix_path.rs | 52 --- .../unregistered_prefix_path.stderr | 43 --- .../check_components/dependency_cascade.rs | 90 ----- .../dependency_cascade.stderr | 333 ------------------ .../generic_context_ordinary_bound.rs | 54 --- .../generic_context_ordinary_bound.stderr | 35 -- .../higher_order_inner_dependency.rs | 79 ----- .../higher_order_inner_dependency.stderr | 75 ---- .../higher_order_outer_dependency.rs | 77 ---- .../higher_order_outer_dependency.stderr | 38 -- .../check_components/missing_dependency.rs | 59 ---- .../missing_dependency.stderr | 38 -- .../missing_has_field_derive.rs | 62 ---- .../missing_has_field_derive.stderr | 37 -- .../ordinary_bound_unsatisfied.rs | 63 ---- .../ordinary_bound_unsatisfied.stderr | 35 -- .../delegate_components/duplicate_key.rs | 42 --- .../delegate_components/duplicate_key.stderr | 17 - .../duplicate_key_same_block.rs | 42 --- .../duplicate_key_same_block.stderr | 15 - .../delegate_components/duplicate_open_key.rs | 39 -- .../duplicate_open_key.stderr | 19 - .../delegate_components/duplicate_path_key.rs | 30 -- .../duplicate_path_key.stderr | 17 - .../delegate_components/missing_dependency.rs | 51 --- .../missing_dependency.stderr | 41 --- .../ordinary_bound_unsatisfied.rs | 55 --- .../ordinary_bound_unsatisfied.stderr | 44 --- .../overlapping_generic.rs | 39 -- .../overlapping_generic.stderr | 17 - .../unconstrained_generic.rs | 43 --- .../unconstrained_generic.stderr | 5 - .../delegate_components/use_context_cycle.rs | 44 --- .../use_context_cycle.stderr | 36 -- .../tests/compile_fail_tests.rs | 27 -- crates/tests/cgp-test-crate-a/Cargo.toml | 13 - crates/tests/cgp-test-crate-a/src/lib.rs | 63 ---- crates/tests/cgp-test-crate-b/Cargo.toml | 14 - crates/tests/cgp-test-crate-b/src/lib.rs | 147 -------- docs/AGENTS.md | 2 +- docs/README.md | 2 +- docs/communication-strategy/README.md | 2 + .../attention-and-engagement.md | 4 +- .../communication-strategy/problems-solved.md | 20 +- .../communication-strategy/reader-profiles.md | 4 +- docs/communication-strategy/selling-points.md | 16 +- docs/communication-strategy/skepticism.md | 8 +- .../technical-barriers.md | 6 +- docs/communication-strategy/vocabulary.md | 2 + docs/errors/AGENTS.md | 21 +- docs/errors/README.md | 14 +- docs/errors/checks/check-trait-failure.md | 18 +- .../checks/higher-order-provider-layer.md | 14 +- docs/errors/checks/ordinary-trait-bound.md | 18 +- .../checks/unregistered-namespace-path.md | 17 +- docs/errors/checks/verbose-cascade.md | 15 +- docs/errors/error_codes/README.md | 6 +- docs/errors/error_codes/cargo-cgp-codes.md | 50 +++ docs/errors/hidden/unsatisfied-dependency.md | 16 +- .../lowering/ill-formed-generated-type.md | 14 +- .../lowering/unresolved-imported-type.md | 18 +- docs/errors/wiring/conflicting-wiring.md | 37 +- .../wiring/namespace-forwarding-conflict.md | 16 +- .../wiring/namespace-inheritance-cycle.md | 16 +- .../wiring/namespace-override-conflict.md | 18 +- docs/errors/wiring/orphan-rule.md | 20 +- docs/errors/wiring/unconstrained-generic.md | 14 +- docs/errors/wiring/wiring-cycle.md | 17 +- docs/guides/README.md | 4 +- docs/guides/debugging.md | 9 +- docs/implementation/AGENTS.md | 8 +- .../asts/attributes/default_impl.md | 2 +- .../asts/attributes/use_type.md | 8 +- .../entrypoints/cgp_auto_getter.md | 2 +- .../entrypoints/cgp_component.md | 4 +- docs/implementation/entrypoints/cgp_impl.md | 6 +- .../entrypoints/cgp_namespace.md | 29 +- .../entrypoints/check_components.md | 6 +- .../entrypoints/delegate_components.md | 20 +- docs/reference/README.md | 6 + docs/reference/cargo-cgp.md | 92 +++++ docs/skills/cgp/SKILL.md | 76 +++- .../skills/cgp/references/error-extraction.md | 2 + rust-toolchain.toml | 9 - 127 files changed, 649 insertions(+), 3861 deletions(-) delete mode 100644 crates/tests/cgp-compile-fail-tests/Cargo.toml delete mode 100644 crates/tests/cgp-compile-fail-tests/README.md delete mode 100644 crates/tests/cgp-compile-fail-tests/src/lib.rs delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_auto_getter/option_slice.rs delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_auto_getter/option_slice.stderr delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/duplicate_component_name.rs delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/duplicate_component_name.stderr delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/use_type_foreign_unsatisfied.rs delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/use_type_foreign_unsatisfied.stderr delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_cyclic_context.rs delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_cyclic_context.stderr delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_nested_unsatisfied.stderr delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_unknown_assoc.rs delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_unknown_assoc.stderr delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_impl/duplicate_default_impl.rs delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_impl/duplicate_default_impl.stderr delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_impl/duplicate_provider_name.rs delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_impl/duplicate_provider_name.stderr delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_component.rs delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_component.stderr delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_prefix_path.rs delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_prefix_path.stderr delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/duplicate_path_key.rs delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/duplicate_path_key.stderr delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/for_loop_bare_key.rs delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/for_loop_bare_key.stderr delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/inheritance_cycle.rs delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/inheritance_cycle.stderr delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/inherited_override_conflict.rs delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/inherited_override_conflict.stderr delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/override_registered_path.rs delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/override_registered_path.stderr delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/reopen_foreign_namespace.rs delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/reopen_foreign_namespace.stderr delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/two_namespaces_joined.rs delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/two_namespaces_joined.stderr delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/unregistered_prefix_path.rs delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/unregistered_prefix_path.stderr delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/dependency_cascade.rs delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/dependency_cascade.stderr delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/generic_context_ordinary_bound.rs delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/generic_context_ordinary_bound.stderr delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/higher_order_inner_dependency.rs delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/higher_order_inner_dependency.stderr delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/higher_order_outer_dependency.rs delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/higher_order_outer_dependency.stderr delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_dependency.rs delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_dependency.stderr delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_has_field_derive.rs delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_has_field_derive.stderr delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/ordinary_bound_unsatisfied.rs delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/ordinary_bound_unsatisfied.stderr delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_key.rs delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_key.stderr delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_key_same_block.rs delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_key_same_block.stderr delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_open_key.rs delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_open_key.stderr delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_path_key.rs delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_path_key.stderr delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/missing_dependency.rs delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/missing_dependency.stderr delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/ordinary_bound_unsatisfied.rs delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/ordinary_bound_unsatisfied.stderr delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/overlapping_generic.rs delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/overlapping_generic.stderr delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/unconstrained_generic.rs delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/unconstrained_generic.stderr delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/use_context_cycle.rs delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/use_context_cycle.stderr delete mode 100644 crates/tests/cgp-compile-fail-tests/tests/compile_fail_tests.rs delete mode 100644 crates/tests/cgp-test-crate-a/Cargo.toml delete mode 100644 crates/tests/cgp-test-crate-a/src/lib.rs delete mode 100644 crates/tests/cgp-test-crate-b/Cargo.toml delete mode 100644 crates/tests/cgp-test-crate-b/src/lib.rs create mode 100644 docs/errors/error_codes/cargo-cgp-codes.md create mode 100644 docs/reference/cargo-cgp.md diff --git a/AGENTS.md b/AGENTS.md index 7873cea5..7d5cc68c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -18,6 +18,43 @@ Only the library crates are published. The five crates under [crates/tests/](cra marked `publish = false`, so `cargo publish --workspace` skips them and publishes just the real CGP crates. +## The two projects: cgp and cargo-cgp + +`cgp` and `cargo-cgp` are developed together but live in separate Git repositories, so a task in one +routinely reads — and sometimes updates — the other. [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp) +is CGP's **first-class toolchain**: a cargo subcommand that rewrites the compiler's diagnostics into +readable, root-cause-first CGP errors. It is the recommended way to build and check CGP code and to +read a CGP compile error, so recommend it wherever CGP errors are discussed, and prefer it over raw +`cargo check` when diagnosing a wiring failure in this repository. + +**Finding `cargo-cgp`.** Look first in the parent directory, at `../cargo-cgp`. When that sibling +checkout is present, use it — it is the fastest reference and reflects any uncommitted work. When it +is absent, fall back to the official repository at +[github.com/contextgeneric/cargo-cgp](https://github.com/contextgeneric/cargo-cgp), fetching the file +you need from there. (The mirror image applies to an agent working in `cargo-cgp` and looking for +`cgp` at `../cgp`.) + +**Writing cross-project links.** A *link* from a committed file here to a document in `cargo-cgp` — a +Markdown link or a URL — is **always written as a GitHub URL** +(`https://github.com/contextgeneric/cargo-cgp/blob/main/`), never as a relative `../cargo-cgp/...` +link, so the document resolves for a reader who has only this repository checked out. When *reading* +such a link yourself, prefer the local `../cargo-cgp` checkout if it is available and fetch the GitHub +URL only when it is not. (A bare mention of the checkout's location, like the path `../cargo-cgp`, is +a filesystem reference rather than a link and stays relative.) + +**Keeping the two in sync.** When a change here affects `cargo-cgp` — most often when a construct +change alters a compiler diagnostic that `cargo-cgp` has a UI fixture for — and `cargo-cgp` is checked +out at `../cargo-cgp`, update it in the same change, following `cargo-cgp`'s own conventions (its +`AGENTS.md`). When `../cargo-cgp` is not checked out, state plainly what needs updating there so it is +not silently left to drift. The post-codegen compile-fail cases this repository used to keep in +`cgp-compile-fail-tests` now live as `cargo-cgp` UI fixtures, and the [error catalog](docs/errors/README.md) +links each error class to the fixture that backs it. + +**The code dependency stays one-way.** No `cgp` *crate* may depend on a `cargo-cgp` crate: +`cargo-cgp` reads `cgp`, never the reverse. Documentation is the deliberate exception — this +repository's docs and skill reference `cargo-cgp` as the recommended toolchain — so it is the *code* +graph that stays acyclic, not the documentation. + ## Orient before any task This repository **is** the implementation of Context-Generic Programming (CGP), and its behavior is @@ -59,13 +96,14 @@ do). - **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`. - This runs the whole suite, including the `trybuild` compile-fail fixtures in - `cgp-compile-fail-tests` (an ordinary integration test, so nextest picks it up). + This runs the whole suite: `cgp-tests` (behavior + expansion snapshots) and `cgp-macro-tests` + (macro internals, rejection cases, invalid-expansion snapshots). - **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` -- **Compile-fail fixtures:** `cargo nextest run -p cgp-compile-fail-tests` runs the `trybuild` - cases that check macro *expansions* fail to compile with a pinned `.stderr`; regenerate the - snapshots with `TRYBUILD=overwrite cargo test -p cgp-compile-fail-tests`. +- **Post-codegen compile failures** (input a macro accepts but whose expansion fails to compile) are + no longer tested here: they are UI fixtures in [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp), + the first-class error toolchain, and cataloged in the [error catalog](docs/errors/README.md). See + [crates/tests/AGENTS.md](crates/tests/AGENTS.md) for the workflow. - 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 @@ -210,10 +248,11 @@ far more leniently than Rust accepts, validate against CGP's restricted argument Walk the shapes the expansion can take across the whole input space and confirm none produces invalid Rust — no conflicting `impl` blocks from a cartesian expansion, no unbound or doubly-declared generic, no empty expansion that checks nothing, no clash on a generated identifier. When an expansion fails to -compile, capture it as a `trybuild` fixture in `cgp-compile-fail-tests` — `problematic/` when the -macro should have rejected it, `acceptable/` when CGP defers the failure to the compiler — and -document it in the macro's implementation document, per [crates/tests/AGENTS.md](crates/tests/AGENTS.md) -and [docs/implementation/AGENTS.md](docs/implementation/AGENTS.md). +compile, capture it as a [`cargo-cgp` UI fixture](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/README.md) +(post-codegen compile-fail cases live there now, filed by the quality of the output the tool produces) +and catalog the class in the [error catalog](docs/errors/README.md); when the failure is a CGP *defect* +the macro should have rejected, also record it under the macro's `## Known issues`, per +[crates/tests/AGENTS.md](crates/tests/AGENTS.md) and [docs/implementation/AGENTS.md](docs/implementation/AGENTS.md). #### Generics diff --git a/Cargo.lock b/Cargo.lock index d2312dbc..6df59d02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -58,15 +58,6 @@ dependencies = [ name = "cgp-base-types" version = "0.8.0-alpha" -[[package]] -name = "cgp-compile-fail-tests" -version = "0.8.0-alpha" -dependencies = [ - "cgp", - "cgp-test-crate-a", - "trybuild", -] - [[package]] name = "cgp-component" version = "0.8.0-alpha" @@ -278,21 +269,6 @@ dependencies = [ "cgp-core", ] -[[package]] -name = "cgp-test-crate-a" -version = "0.8.0-alpha" -dependencies = [ - "cgp", -] - -[[package]] -name = "cgp-test-crate-b" -version = "0.8.0-alpha" -dependencies = [ - "cgp", - "cgp-test-crate-a", -] - [[package]] name = "cgp-tests" version = "0.8.0-alpha" @@ -339,12 +315,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - [[package]] name = "errno" version = "0.3.14" @@ -471,34 +441,12 @@ dependencies = [ "r-efi", ] -[[package]] -name = "glob" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" - -[[package]] -name = "hashbrown" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" - [[package]] name = "indenter" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" -[[package]] -name = "indexmap" -version = "2.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" -dependencies = [ - "equivalent", - "hashbrown", -] - [[package]] name = "insta" version = "1.48.0" @@ -520,12 +468,6 @@ dependencies = [ "either", ] -[[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - [[package]] name = "libc" version = "0.2.186" @@ -609,57 +551,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.150" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_spanned" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" -dependencies = [ - "serde_core", -] - [[package]] name = "similar" version = "2.7.0" @@ -683,12 +574,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "target-triple" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "591ef38edfb78ca4771ee32cf494cb8771944bee237a9b91fc9c1424ac4b777b" - [[package]] name = "tempfile" version = "3.27.0" @@ -702,84 +587,12 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "toml" -version = "1.1.2+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" -dependencies = [ - "indexmap", - "serde_core", - "serde_spanned", - "toml_datetime", - "toml_parser", - "toml_writer", - "winnow", -] - -[[package]] -name = "toml_datetime" -version = "1.1.1+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_parser" -version = "1.1.2+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" -dependencies = [ - "winnow", -] - -[[package]] -name = "toml_writer" -version = "1.1.1+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" - -[[package]] -name = "trybuild" -version = "1.0.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0710d4dfbeae4f9c390baa784c49858a7468fa433f3fe5d0ec5ebef651cf59f9" -dependencies = [ - "glob", - "serde", - "serde_derive", - "serde_json", - "target-triple", - "termcolor", - "toml", -] - [[package]] name = "unicode-ident" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" -[[package]] -name = "winapi-util" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" -dependencies = [ - "windows-sys", -] - [[package]] name = "windows-link" version = "0.2.1" @@ -794,15 +607,3 @@ checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", ] - -[[package]] -name = "winnow" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" - -[[package]] -name = "zmij" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 6e9efbbb..4cd043ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,9 +38,6 @@ members = [ "crates/tests/cgp-tests", "crates/tests/cgp-macro-tests", - "crates/tests/cgp-compile-fail-tests", - "crates/tests/cgp-test-crate-a", - "crates/tests/cgp-test-crate-b", ] [workspace.package] @@ -78,8 +75,6 @@ cgp-macro-core = { version = "0.8.0-alpha", path = "./crates/macros cgp-macro-lib = { version = "0.8.0-alpha", path = "./crates/macros/cgp-macro-lib" } cgp-macro-test-util = { version = "0.8.0-alpha", path = "./crates/macros/cgp-macro-test-util" } -cgp-test-crate-a = { version = "0.8.0-alpha", path = "./crates/tests/cgp-test-crate-a" } -cgp-test-crate-b = { version = "0.8.0-alpha", path = "./crates/tests/cgp-test-crate-b" } cgp-macro-test-util-lib = { version = "0.8.0-alpha", path = "./crates/macros/cgp-macro-test-util-lib" } cgp-extra-macro = { version = "0.8.0-alpha", path = "./crates/macros/cgp-extra-macro" } cgp-extra-macro-lib = { version = "0.8.0-alpha", path = "./crates/macros/cgp-extra-macro-lib" } diff --git a/README.md b/README.md index 0e61f54e..918799c3 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,19 @@ CGP requires **Rust 1.89+** and runs on the **stable** toolchain — no nightly, > ``` > Once v0.8.0 is released, `cargo add cgp` will install it. +### Install cargo-cgp for readable errors + +CGP compiles to ordinary Rust, so a wiring mistake surfaces as a compiler error about generated types — often a wall of them with the real cause buried. **When you start with CGP, install [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp) alongside it.** cargo-cgp is CGP's error toolchain: a drop-in for `cargo check` that rewrites those errors into a compact, root-cause-first form, leading with the field or wiring that actually failed. It is the single biggest quality-of-life improvement for writing CGP, so treat it as part of your getting-started setup. + +```bash +cargo install cargo-cgp # the front-end (installs on any toolchain) +cargo cgp setup # provisions the compiler + driver it needs +``` + +Then run `cargo cgp check` wherever you would run `cargo check`; it forces the pinned nightly that `cargo cgp setup` installed only for its own check, so your project keeps its toolchain untouched. (Prefer Nix? `nix run github:contextgeneric/cargo-cgp/v0.1.0-alpha -- check` runs it without installing.) cargo-cgp is an early pre-release that already makes the common wiring errors readable and is steadily covering more; see its [README](https://github.com/contextgeneric/cargo-cgp) for the full guide. + +cargo-cgp is optional and adds only a diagnostic `check`: keep building, running, and testing with ordinary cargo — CGP is a plain library needing only stable Rust (1.89+) — and reach for `cargo cgp check` when you want a wiring error made readable. Plain `cargo check` still works too, and is fine when you are not expecting a compile error. + ## When to use CGP — and when not CGP is a tool with a boundary, and it earns its cost only past that boundary. The rule of thumb is to **reach for CGP when a capability needs more than one implementation and the choice belongs to the context — not before.** Concretely: diff --git a/crates/tests/AGENTS.md b/crates/tests/AGENTS.md index 4155824e..d27a62b4 100644 --- a/crates/tests/AGENTS.md +++ b/crates/tests/AGENTS.md @@ -5,7 +5,7 @@ moving, or refactoring any test here. Invoke the `/cgp` skill first — every te in this tree is CGP code, and the skill is the authoritative source for CGP semantics and vocabulary. -The test suite has four jobs, split across crates: +The test suite has two jobs, split across crates: - **`cgp-tests`** is the main suite: realistic example code that must **compile and run**. A passing test is often just successful compilation, because much of CGP @@ -15,16 +15,14 @@ The test suite has four jobs, split across crates: functions in `cgp-macro-core` directly (parsers, AST types), and is the home for **inputs a macro rejects** (via `assert_macro_rejects`) and for **pinning the exact invalid tokens** a macro emits (`invalid_expansion` string snapshots). -- **`cgp-compile-fail-tests`** holds the **`trybuild` compile-fail tests**: input a - macro *accepts* but whose *expansion* then fails to compile. Each case is a - standalone `.rs` fixture whose expected compiler output is pinned in a committed - `.stderr` file, and the fixtures are split into **acceptable** and **problematic** - failures (see "Adding a failure case" below). Because the driver is an ordinary - integration test, `cargo nextest` runs it like any other test. -- **`cgp-test-crate-a` / `cgp-test-crate-b`** are auxiliary packages for - **cross-crate** behavior: whether a downstream crate can extend a namespace or - provide a provider for a component defined elsewhere, under Rust's coherence and - orphan rules. + +**Post-codegen compile failures are tested in `cargo-cgp`, not here.** The cases where a +macro *accepts* input but its *expansion* then fails to compile — and the cross-crate +coherence and orphan-rule fixtures that once lived in `cgp-test-crate-a`/`-b` — were +migrated to `cargo-cgp`'s UI test suite, which pins the readable errors the tool renders +for each class. `cargo-cgp` is CGP's first-class error toolchain, so those diagnostics +belong where the tool that improves them lives; see "Adding a failure case" for the new +workflow. ## Organize by concept, not by construct @@ -128,93 +126,41 @@ which is enough to pin a rejection and gives a precise check of the macro's own diagnostic. This is the right tool for a structural error the macro is expected to catch, and such a case does **not** also need a compile-fail test. -**Input a macro accepts whose expansion fails to compile — a `trybuild` fixture in -`cgp-compile-fail-tests`.** Reserve these tests for input a CGP macro *accepts* but -whose *expansion* then fails to type- or borrow-check — the failure lands on the -emitted Rust, not inside the macro. This is the tool for a documented bug or known -limitation, and for the cases a macro cannot reject because it lacks the whole-program -view the check needs. Each case is a standalone `.rs` fixture (a complete program with -`fn main`) under the crate's `tests/` tree; `trybuild` compiles each as its own crate -and compares the compiler's output against a committed sibling `.stderr` file, so the -test passes only when compilation fails *with the pinned diagnostic*. The `.stderr` -snapshot is what proves *which* element causes the failure and *why* — it names the -exact error code and span — so it replaces the companion ```` ```rust ```` block the -old doctest form paired with each probe. Because the toolchain is pinned -([rust-toolchain.toml](../../rust-toolchain.toml)), the pinned diagnostics are stable -across runs. - -**Split every fixture into acceptable and problematic.** A compile failure carries one -of two opposite meanings, and the directory it lives in records which: - -- **`tests/acceptable/`** — the failure is one CGP **intentionally delegates to the - Rust compiler**. CGP is working as designed: it cannot see the whole program, so it - lowers the input faithfully and lets `rustc` reject it. Two separate - `delegate_components!` blocks that delegate the same key, generic - `delegate_components!` entries that expand to overlapping impls, and a lazily-wired - provider whose impl-side dependency the context does not satisfy all belong here. The - pinned `.stderr` documents that the diagnostic a user sees is the compiler doing its - job, and it is the diagnostic they should expect. -- **`tests/problematic/`** — the failure is a **CGP defect**: input a macro should have - rejected with a spanned error, or that a macro expanded into invalid Rust. The pinned - `.stderr` captures the confusing downstream error a user currently hits. Every - problematic fixture must be cross-linked (in its header comment) to the `## Known - issues` section of the owning macro's implementation document, and its `.stderr` - should improve — ideally becoming a clean macro-time rejection — when the defect is - fixed. When it does, regenerate the snapshot and, if the failure moves into the macro - itself, migrate the case to an `assert_macro_rejects` test in `cgp-macro-tests`. - -Under each category directory, group fixtures into one subdirectory per **owning -macro** — the macro whose expansion produces the failure and whose implementation -document documents it (`acceptable/delegate_components/`, or a `problematic//` -when a defect is pinned). This is the one place the suite groups by construct rather -than by concept, and deliberately so: a compile-fail case is defined by *which -macro's expansion* fails, and each cross-links to that macro's per-entrypoint -implementation document, so the fixture tree mirrors [docs/implementation/entrypoints/](../../docs/implementation/entrypoints). -Within a subdirectory, write one fixture file per case, named for the failure mode it -probes (`duplicate_key.rs`, `missing_dependency.rs`), and open each with a comment -stating what it exercises and why it must not compile — exactly as the main suite -requires. **Keep each fixture subdirectory small — no more than about a dozen cases — -and split into further nested subdirectories when one grows past that**; the driver's -`**` glob picks up the new level with no registration. The driver [tests/compile_fail_tests.rs](cgp-compile-fail-tests/tests/compile_fail_tests.rs) -globs both trees with `**`, so the two `t.compile_fail(...)` calls pick up a new -fixture with no per-file registration. A single `trybuild::TestCases` runs both globs -— do not split them across two `#[test]` functions, which would race on the shared -build directory. - -Run the suite with `cargo test -p cgp-compile-fail-tests` or `cargo nextest run -p -cgp-compile-fail-tests`; both work because the driver is an ordinary integration test. -After adding a fixture, or when an intended change alters a diagnostic, regenerate the -golden output with `TRYBUILD=overwrite cargo test -p cgp-compile-fail-tests` and review -the diff before committing. - -**Pinning the exact invalid output** is a separate, rarer need: only when you must -*inspect* the wrong tokens a macro emits (not merely assert they fail to compile), -capture the expanded code as an `insta` inline string snapshot in the +**Input a macro accepts whose expansion fails to compile — a UI fixture in +`cargo-cgp`.** Reserve these for input a CGP macro *accepts* but whose *expansion* then +fails to type- or borrow-check — the failure lands on the emitted Rust, not inside the +macro. These **no longer live in this repository**: they are UI fixtures in `cargo-cgp`, +the tool that rewrites such errors into readable form, so the pinned snapshot is what a +user actually sees rather than the raw cascade. Add the fixture there — a standalone +`.rs` program with `fn main`, plus a `//@aux-build: ` directive when the case is +cross-crate — following +[cargo-cgp's UI-test guide](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/README.md); +its harness records both `cargo-cgp`'s output (`.cgp.stderr`) and the raw compiler +baseline (`.rust.stderr`). cargo-cgp files a fixture by the **quality of the output** — +`acceptable/` when the tool already leads with the root cause, `usability/` when the +cause is present but buried, `ok/` for a clean compile — rather than by the +acceptable/problematic distinction this repository formerly used. Then catalog the class +and link the fixture (below). + +**Pinning the exact invalid output** is a separate, rarer need that *does* stay here: +only when you must *inspect* the wrong tokens a macro emits (not merely assert they fail +to compile), capture the expanded code as an `insta` inline string snapshot in the `invalid_expansion` target of `cgp-macro-tests` (the snapshot is a *string*, so it -compiles even though the code would not), with a comment explaining **why** the -output is wrong and **what the correct output should be**. - -Post-codegen compile-fail cases are additionally cataloged in the [error catalog](../../docs/errors/README.md) -under `docs/errors/`, which is becoming the canonical, reader-facing documentation for -them (see [docs/errors/AGENTS.md](../../docs/errors/AGENTS.md)): a fixture cross-links to -the error *class* it exercises there, and the catalog indexes the fixture. This migration -proceeds class by class — until a class is migrated, its fixture continues to cross-link -to the owning macro's implementation document as described below. - -Every failure case must also be recorded in the owning macro's **implementation -document** under `docs/implementation/`, and *which section* holds it is what the -acceptable/problematic split decides. An **acceptable** fixture documents *intended* -behavior — a failure CGP deliberately defers to the compiler — so it belongs in that -document's `## Failure modes` section (a dedicated section, kept out of Known issues so -it is not mistaken for a bug), with a short code snippet of the failing input, and is -indexed from `## Tests`. A **problematic** fixture (and every `invalid_expansion` -snapshot) documents a defect, so describe it in `## Known issues` alongside the -construct's other bugs, again with a snippet, and index it from `## Tests`; when the -defect has a user-visible consequence, note it in the reference document's `## Known -issues` section too and cross-link the two. In both cases describe the behavior in the -document's own words without referring to the test, and put a link from the fixture's -header comment back to the implementation document — to its Failure modes section for an -acceptable case, its Known issues section for a problematic one. +compiles even though the code would not), with a comment explaining **why** the output +is wrong and **what the correct output should be**. + +**Where a post-codegen class is documented.** Such a class is cataloged in the +[error catalog](../../docs/errors/README.md) under `docs/errors/`, the canonical +reader-facing documentation for these errors (see +[docs/errors/AGENTS.md](../../docs/errors/AGENTS.md)): the class doc describes the raw +diagnostic *and* how cargo-cgp presents it, and links the backing `cargo-cgp` UI fixture +as a GitHub URL. When a construct change alters such a diagnostic, the cross-project +[sync rule](../../AGENTS.md) applies — update the `cargo-cgp` fixture and the class doc +here together when both repos are checked out. A macro's **implementation document** +still records the *rejection* cases it catches and its behavioral tests (its `## Known +issues` and `## Tests` sections); the accept-then-fail classes are documented in the +error catalog, and a macro's `## Failure modes` section links out to the catalog class +and its `cargo-cgp` fixture rather than to a local fixture. ## Keep the docs in sync @@ -236,27 +182,22 @@ document's Tests or Snapshots section in the same change. ``` cargo nextest run -p cgp-tests # the main suite cargo nextest run -p cgp-macro-tests # macro internals + rejection/invalid-expansion cases -cargo nextest run -p cgp-compile-fail-tests # trybuild compile-fail fixtures cargo nextest run --workspace # everything -TRYBUILD=overwrite cargo test -p cgp-compile-fail-tests # regenerate .stderr snapshots - cargo insta test -p cgp-tests --review # review snapshot diffs cargo insta test -p cgp-tests --accept # accept intended snapshot changes ``` -The `cgp-compile-fail-tests` fixtures run under both `cargo test` and `cargo nextest`, -because the `trybuild` driver is an ordinary integration test rather than a doctest. -Include the crate when verifying a change that touches a macro's accepted input or its -expansion, and regenerate its `.stderr` snapshots with `TRYBUILD=overwrite` whenever an -intended change alters a pinned diagnostic — then review the diff before committing. - A snapshot test that fails prints a diff of the generated code; accept it with -`cargo insta` only after confirming the change is intended. +`cargo insta` only after confirming the change is intended. When a change touches a +macro's accepted input or its *expansion*, also check the post-codegen behavior in +`cargo-cgp`'s UI suite (its fixtures compile CGP code through the tool), per the +cross-project [sync rule](../../AGENTS.md). ## Migration status The suite was reorganized from a by-construct layout to this by-concept layout. As -categories grow, keep splitting them per the rule above, and keep expanding failure -coverage in `cgp-macro-tests` and cross-crate coverage in the `cgp-test-crate-*` -packages — these were established with representative cases and are meant to grow. +categories grow, keep splitting them per the rule above, and keep expanding rejection +coverage in `cgp-macro-tests`. Post-codegen compile-fail coverage and cross-crate +coverage now grow in `cargo-cgp`'s UI suite rather than here (the former +`cgp-compile-fail-tests` and `cgp-test-crate-*` packages were migrated there). diff --git a/crates/tests/README.md b/crates/tests/README.md index ee27d91c..176162a1 100644 --- a/crates/tests/README.md +++ b/crates/tests/README.md @@ -10,7 +10,7 @@ This README is the map. ## The crates -The suite is split into four kinds of crate, each with a distinct job. +The suite is split into two kinds of crate, each with a distinct job. **`cgp-tests`** is the main suite: realistic example code that must compile and run. Because much of CGP is compile-time wiring, a test here often passes simply @@ -22,22 +22,16 @@ where the canonical macro-expansion snapshots live. refuses during expansion — and for pinning the invalid tokens a macro currently emits. -**`cgp-compile-fail-tests`** holds the **`trybuild` compile-fail tests**: input a -macro *accepts* but whose *expansion* then fails to compile. Each case is a -standalone `.rs` fixture with a committed `.stderr` snapshot of the expected -compiler output, split into two categories — **acceptable** failures that CGP -intentionally delegates to the Rust compiler (overlapping wiring, a lazily-wired -missing dependency) and **problematic** failures that are a CGP defect (input a -macro should have rejected, or an expansion that emits invalid Rust). See -[cgp-compile-fail-tests/README.md](cgp-compile-fail-tests/README.md) for what -belongs in each category, the fixture layout, and how to regenerate the -`.stderr` snapshots. - -**`cgp-test-crate-a`** and **`cgp-test-crate-b`** are auxiliary packages for -**cross-crate** behavior. Crate A defines components, a provider, and a namespaced -component; crate B (a downstream crate) wires them, supplies its own provider for a -foreign component, and participates in crate A's namespace — showing that CGP stays -within Rust's coherence and orphan rules across crate boundaries. +**Post-codegen compile failures live in `cargo-cgp`, not here.** The cases where a +macro *accepts* input but its *expansion* then fails to compile — and the +cross-crate coherence and orphan-rule fixtures that need a companion crate — were +migrated to `cargo-cgp`'s UI test suite, which snapshots the readable, root-cause-first +errors `cargo-cgp` renders for each class (its `.rust.stderr` still records the raw +compiler output as the "before"). `cargo-cgp` is CGP's first-class error toolchain, so +those diagnostics are pinned where the tool that improves them lives; the +[error catalog](../../docs/errors/README.md) links each class to the fixture that +backs it. See +[cargo-cgp's UI tests](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/README.md). ## How the tests are laid out @@ -60,15 +54,6 @@ accumulates too many cases to stay coherent, it is split into finer targets. for parser corner cases, and the failure-case targets `parser_rejections` and `invalid_expansion`. -`cgp-compile-fail-tests` is laid out for `trybuild` instead: a single driver -`tests/compile_fail_tests.rs` recursively glob-compiles every fixture under -`tests/acceptable/` and `tests/problematic/`, which are further grouped into one -subdirectory per owning macro (`acceptable/delegate_components/`, or a -`problematic//` when a defect is pinned). Each fixture is a self-contained -program paired with a committed `.stderr` snapshot of the compiler output it must -produce. Its own [README](cgp-compile-fail-tests/README.md) is the authoritative -guide to this crate. - ## Snapshots Many tests assert the exact code a macro generates, using the `snapshot_*!` macros @@ -83,16 +68,11 @@ everywhere else (see [AGENTS.md](AGENTS.md) for the ownership rules). ``` cargo nextest run -p cgp-tests # the main suite cargo nextest run -p cgp-macro-tests # macro internals + rejection cases -cargo nextest run -p cgp-compile-fail-tests # trybuild compile-fail fixtures -cargo nextest run --workspace # everything, including the aux crates +cargo nextest run --workspace # everything cargo insta test -p cgp-tests --review # review snapshot diffs interactively cargo insta test -p cgp-tests --accept # accept intended snapshot changes - -TRYBUILD=overwrite cargo test -p cgp-compile-fail-tests # regenerate .stderr snapshots ``` When a `snapshot_*!` test fails it prints a diff of the generated code; accept the -new output with `cargo insta` only after confirming the change is intended. When a -`trybuild` fixture fails it prints a diff against the committed `.stderr`; regenerate -it with `TRYBUILD=overwrite` only after confirming the diagnostic change is intended. +new output with `cargo insta` only after confirming the change is intended. diff --git a/crates/tests/cgp-compile-fail-tests/Cargo.toml b/crates/tests/cgp-compile-fail-tests/Cargo.toml deleted file mode 100644 index f6791abc..00000000 --- a/crates/tests/cgp-compile-fail-tests/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "cgp-compile-fail-tests" -version = "0.8.0-alpha" -publish = false -edition = { workspace = true } -license = { workspace = true } -repository = { workspace = true } -authors = { workspace = true } -rust-version = { workspace = true } -keywords = { workspace = true } -readme = "./README.md" - -[dependencies] -cgp = { workspace = true } -cgp-test-crate-a = { workspace = true } - -[dev-dependencies] -trybuild = { version = "1.0.117" } diff --git a/crates/tests/cgp-compile-fail-tests/README.md b/crates/tests/cgp-compile-fail-tests/README.md deleted file mode 100644 index 883db815..00000000 --- a/crates/tests/cgp-compile-fail-tests/README.md +++ /dev/null @@ -1,78 +0,0 @@ -# `cgp-compile-fail-tests` - -Compile-fail tests for the CGP macros, driven by -[`trybuild`](https://docs.rs/trybuild). - -Each test is a standalone `.rs` fixture under `tests/` that a CGP macro -**accepts** but whose **expansion** then fails to type- or borrow-check — the -failure lands on the emitted Rust, not inside the macro. `trybuild` compiles -each fixture as its own throwaway crate and compares the compiler's output -against a committed `.stderr` file, so a test passes only when compilation fails -*with the pinned diagnostic*. Because the driver is an ordinary integration test -(`tests/compile_fail_tests.rs`), `cargo test` and `cargo nextest run` both -execute it — unlike the `compile_fail` doctests this crate previously held, which -`cargo nextest` silently skipped. - -## What belongs here - -A `trybuild` fixture is reserved for input that a CGP macro **accepts** but whose -**expansion** fails to compile — the case a macro cannot reject because it lacks -the whole-program view the borrow/coherence check needs, or a documented bug -where the macro emits code it should not. Input that a macro **rejects** during -expansion (it returns `Err`) does not belong here; test it by driving the -entrypoint directly in `cgp-macro-tests` with the `assert_macro_rejects` helper, -which pins the macro's own diagnostic. - -## Two categories of failure - -The fixtures are split by *whose fault the failure is*, because the two -categories carry opposite messages about CGP's health. - -**`tests/acceptable/`** holds failures CGP **intentionally delegates to the Rust -compiler**. CGP is working as designed: it cannot see the whole program, so it -lowers the input faithfully and lets `rustc` reject it (overlapping -`delegate_components!` entries becoming conflicting impls, a lazily-wired -provider whose impl-side dependency the context does not meet, an ill-formed -per-entry generic the compiler rejects as unconstrained). The pinned `.stderr` -documents that the failure is the compiler doing its job, and its diagnostic is -the one a user should expect. Each fixture is cross-linked to the -`## Failure modes` section of the owning macro's implementation document. - -**`tests/problematic/`** holds failures that are a **CGP defect**: input a macro -should have rejected with a spanned error, or that a macro expanded into invalid -Rust. The pinned `.stderr` captures the confusing downstream error a user -currently hits; each fixture is cross-linked to the `## Known issues` section of -the owning macro's implementation document, and its `.stderr` should improve -(ideally become a clean macro-time rejection) when the defect is fixed. - -## Organization - -Under each category directory, fixtures are grouped into one subdirectory per -**owning macro** — the macro whose expansion produces the failure and whose -implementation document documents it (`acceptable/delegate_components/`, or a -`problematic//` when a defect is pinned). This mirrors the per-entrypoint -layout of the implementation docs, so a fixture and the document that indexes it -share a name. Within a subdirectory, write one fixture file per case, named for -the failure mode it probes (`duplicate_key.rs`, `missing_dependency.rs`), and -open each with a comment stating what it exercises and why it must not compile. -The driver `tests/compile_fail_tests.rs` globs both trees with `**`, so a new -fixture is picked up with no registration. - -## Regenerating the `.stderr` snapshots - -A fixture's committed `.stderr` is the golden output. After adding a fixture or -when an intended change alters a diagnostic, regenerate the snapshots with -`TRYBUILD=overwrite cargo test -p cgp-compile-fail-tests`, then review the diff -before committing — an unexpected change to an `acceptable/` diagnostic, or a -`problematic/` fixture that stops failing, is a signal worth reading closely. - -Regenerate with the pinned toolchain, because a snapshot can embed standard-library -source. When an error points into the standard library — `option_slice.stderr` is -the only current case — `rustc` prints the referenced source line (`pub enum -Option {`) if the `rust-src` component is installed and omits it otherwise, while -`trybuild` normalizes the path to `$RUST/...` either way. The difference is therefore -invisible in the path and shows up only as an added or missing snippet line, which -reads as spurious non-determinism between machines. The pinned toolchain declares -`rust-src` in [rust-toolchain.toml](../../../rust-toolchain.toml) precisely so this -renders the same everywhere; blessing under a toolchain that lacks the component -would silently strip the snippet and reintroduce the mismatch on CI. diff --git a/crates/tests/cgp-compile-fail-tests/src/lib.rs b/crates/tests/cgp-compile-fail-tests/src/lib.rs deleted file mode 100644 index 32127762..00000000 --- a/crates/tests/cgp-compile-fail-tests/src/lib.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! Compile-fail tests for the CGP macros, driven by `trybuild`. -//! -//! This crate carries no library code — the suite lives in the `trybuild` -//! fixtures under `tests/` and their driver `tests/compile_fail_tests.rs`. See -//! the crate [README](../README.md) for what belongs in each category and how to -//! regenerate the `.stderr` snapshots. diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_auto_getter/option_slice.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_auto_getter/option_slice.rs deleted file mode 100644 index 5eafc81c..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_auto_getter/option_slice.rs +++ /dev/null @@ -1,22 +0,0 @@ -//! Acceptable failure: a getter (or `#[implicit]` argument) typed `Option<&[T]>` -//! combines the `Option<&T>` and `&[T]` shorthands, a combination CGP does not -//! provide magic for. The shared `parse_field_type` lowers it literally by the -//! `Option<&T>` rule — reading an `Option` field where `T` is the slice `[u8]` -//! — so the generated `HasField` bound names the unsized `Option<[u8]>`, which -//! `rustc` rejects (`[u8]` has no statically known size). CGP is working as -//! designed: the `Option` and slice shorthands ease the common single-shape -//! cases, and an unsupported combination is deferred to the compiler rather than -//! given a bespoke rule. The same boundary holds for `#[cgp_getter]` and for a -//! `#[cgp_fn]` `#[implicit]` argument, since all three share `parse_field_type`. -//! -//! See docs/errors/lowering/ill-formed-generated-type.md; the parser detail is in -//! docs/implementation/entrypoints/cgp_auto_getter.md (Behavior and corner cases). - -use cgp::prelude::*; - -#[cgp_auto_getter] -pub trait HasItems { - fn items(&self) -> Option<&[u8]>; -} - -fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_auto_getter/option_slice.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_auto_getter/option_slice.stderr deleted file mode 100644 index 31e9aba3..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_auto_getter/option_slice.stderr +++ /dev/null @@ -1,30 +0,0 @@ -error[E0277]: the size for values of type `[u8]` cannot be known at compilation time - --> tests/acceptable/cgp_auto_getter/option_slice.rs:17:1 - | -17 | #[cgp_auto_getter] - | ^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time - | - = help: the trait `Sized` is not implemented for `[u8]` -note: required by an implicit `Sized` bound in `Option` - --> $RUST/core/src/option.rs - | - | pub enum Option { - | ^ required by the implicit `Sized` requirement on this type parameter in `Option` - = note: this error originates in the attribute macro `cgp_auto_getter` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0599]: the method `as_ref` exists for reference `&Option<[u8]>`, but its trait bounds were not satisfied - --> tests/acceptable/cgp_auto_getter/option_slice.rs:17:1 - | -17 | #[cgp_auto_getter] - | ^^^^^^^^^^^^^^^^^^ method cannot be called on `&Option<[u8]>` due to unsatisfied trait bounds - | - ::: $RUST/core/src/option.rs - | - | pub enum Option { - | ------------------ doesn't satisfy `Option<[u8]>: AsRef<_>` - | - = note: the following trait bounds were not satisfied: - `[u8]: Sized` - `Option<[u8]>: AsRef<_>` - which is required by `&Option<[u8]>: AsRef<_>` - = note: this error originates in the attribute macro `cgp_auto_getter` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/duplicate_component_name.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/duplicate_component_name.rs deleted file mode 100644 index 86382e50..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/duplicate_component_name.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! Acceptable failure: `#[cgp_component(Greeter)]` derives a marker -//! `pub struct GreeterComponent;`, and this module also declares its own -//! `GreeterComponent`, so the name is defined twice (E0428). `#[cgp_component]` -//! expands without any view of the rest of the module, so it emits the derived -//! marker faithfully and lets the compiler report the clash — exactly as two -//! hand-written definitions would. -//! -//! This fixture pins the span of the *derived* `#[cgp_component]` marker. The -//! E0428 "previous definition of the type `GreeterComponent` here" note falls on -//! the `Greeter` provider name the user wrote inside `#[cgp_component(…)]`, not on -//! the whole attribute, because the derived marker struct ident is emitted with -//! the provider identifier's own span (see -//! cgp-macro-core/src/types/cgp_component/args/component_args.rs). A regression -//! that stamped the marker with `Span::call_site()` would move that note onto the -//! whole `#[cgp_component(..)]` attribute — the leak the span fix removed so that -//! cross-crate go-to-definition on the marker resolves to the provider name alone. -//! -//! See docs/errors/wiring/conflicting-wiring.md. - -use cgp::prelude::*; - -#[cgp_component(Greeter)] -pub trait CanGreet { - fn greet(&self); -} - -// Collides with the `GreeterComponent` marker derived from `Greeter` above. -pub struct GreeterComponent; - -fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/duplicate_component_name.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/duplicate_component_name.stderr deleted file mode 100644 index e7ff36dc..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/duplicate_component_name.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error[E0428]: the name `GreeterComponent` is defined multiple times - --> tests/acceptable/cgp_component/duplicate_component_name.rs:28:1 - | -22 | #[cgp_component(Greeter)] - | ------- previous definition of the type `GreeterComponent` here -... -28 | pub struct GreeterComponent; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `GreeterComponent` redefined here - | - = note: `GreeterComponent` must be defined only once in the type namespace of this module diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/use_type_foreign_unsatisfied.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/use_type_foreign_unsatisfied.rs deleted file mode 100644 index 34e8c8fe..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/use_type_foreign_unsatisfied.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! Acceptable failure: a foreign `#[use_type(HasScalarType.Scalar in Types)]` import -//! adds `Types: HasScalarType` to the generated trait, so naming the component for -//! a `Types` that does not implement `HasScalarType` is rejected by the compiler. -//! -//! This is the constraint that used to be *silently dropped* — before the trait -//! carried the foreign bound, `NoScalar` would have slipped through here and only -//! failed much later (or not at all, if the abstract type went unused). CGP is now -//! working as designed: it emits the bound and defers the actual check to `rustc`, -//! which reports the missing `NoScalar: HasScalarType` at the use site. -//! -//! See docs/reference/attributes/use_type.md and docs/errors/checks/check-trait-failure.md. - -use cgp::prelude::*; - -#[cgp_type] -pub trait HasScalarType { - type Scalar; -} - -#[cgp_component(AreaCalculator)] -#[use_type(HasScalarType.Scalar in Types)] -pub trait CanCalculateArea { - fn area(&self) -> Scalar; -} - -// `NoScalar` deliberately does not implement `HasScalarType`. -pub struct NoScalar; - -// Asserting the component for `NoScalar` requires `NoScalar: HasScalarType`, which -// the foreign import now demands — so this fails to compile. -pub trait CheckMissingScalar: CanCalculateArea {} - -fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/use_type_foreign_unsatisfied.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/use_type_foreign_unsatisfied.stderr deleted file mode 100644 index 8dea52a6..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/use_type_foreign_unsatisfied.stderr +++ /dev/null @@ -1,83 +0,0 @@ -error[E0277]: the trait bound `NoScalar: HasScalarType` is not satisfied - --> tests/acceptable/cgp_component/use_type_foreign_unsatisfied.rs:31:31 - | -31 | pub trait CheckMissingScalar: CanCalculateArea {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ unsatisfied trait bound - | -help: the trait `DelegateComponent` is not implemented for `NoScalar` - --> tests/acceptable/cgp_component/use_type_foreign_unsatisfied.rs:27:1 - | -27 | pub struct NoScalar; - | ^^^^^^^^^^^^^^^^^^^ - = help: the following other types implement trait `DelegateComponent`: - `BuildAndMergeOutputs` implements `DelegateComponent` - `BuildAndMergeOutputs` implements `DelegateComponent` - `BuildAndMergeOutputs` implements `DelegateComponent` - `BuildAndMergeOutputs` implements `DelegateComponent` - `BuildAndMergeOutputs` implements `DelegateComponent` - `BuildAndMergeOutputs` implements `DelegateComponent` - `MatchFirstWithFieldHandlersInputs` implements `DelegateComponent<(Input, Args)>` - `MatchFirstWithFieldHandlersInputsMut` implements `DelegateComponent<(&'a mut Input, Args)>` - and $N others -note: required for `NoScalar` to implement `ScalarTypeProvider` - --> tests/acceptable/cgp_component/use_type_foreign_unsatisfied.rs:15:1 - | -15 | #[cgp_type] - | ^^^^^^^^^^^ -note: required for `NoScalar` to implement `HasScalarType` - --> tests/acceptable/cgp_component/use_type_foreign_unsatisfied.rs:15:1 - | -15 | #[cgp_type] - | ^^^^^^^^^^^ -16 | pub trait HasScalarType { - | ^^^^^^^^^^^^^ -note: required by a bound in `CanCalculateArea` - --> tests/acceptable/cgp_component/use_type_foreign_unsatisfied.rs:21:12 - | -21 | #[use_type(HasScalarType.Scalar in Types)] - | ^^^^^^^^^^^^^ required by this bound in `CanCalculateArea` -22 | pub trait CanCalculateArea { - | ---------------- required by a bound in this trait - = note: this error originates in the attribute macro `cgp_type` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: the trait bound `NoScalar: HasScalarType` is not satisfied - --> tests/acceptable/cgp_component/use_type_foreign_unsatisfied.rs:31:31 - | -31 | pub trait CheckMissingScalar: CanCalculateArea {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ unsatisfied trait bound - | -help: the trait `IsProviderFor` is not implemented for `NoScalar` - --> tests/acceptable/cgp_component/use_type_foreign_unsatisfied.rs:27:1 - | -27 | pub struct NoScalar; - | ^^^^^^^^^^^^^^^^^^^ - = help: the following other types implement trait `IsProviderFor`: - `BindErr` implements `IsProviderFor)>` - `BindErr` implements `IsProviderFor)>` - `BindOk` implements `IsProviderFor)>` - `BindOk` implements `IsProviderFor)>` - `BuildAndMerge` implements `IsProviderFor` - `BuildAndMerge` implements `IsProviderFor` - `BuildAndMerge` implements `IsProviderFor` - `BuildAndMergeOutputs` implements `IsProviderFor` - and $N others -note: required for `NoScalar` to implement `ScalarTypeProvider` - --> tests/acceptable/cgp_component/use_type_foreign_unsatisfied.rs:15:1 - | -15 | #[cgp_type] - | ^^^^^^^^^^^ -note: required for `NoScalar` to implement `HasScalarType` - --> tests/acceptable/cgp_component/use_type_foreign_unsatisfied.rs:15:1 - | -15 | #[cgp_type] - | ^^^^^^^^^^^ -16 | pub trait HasScalarType { - | ^^^^^^^^^^^^^ -note: required by a bound in `CanCalculateArea` - --> tests/acceptable/cgp_component/use_type_foreign_unsatisfied.rs:21:12 - | -21 | #[use_type(HasScalarType.Scalar in Types)] - | ^^^^^^^^^^^^^ required by this bound in `CanCalculateArea` -22 | pub trait CanCalculateArea { - | ---------------- required by a bound in this trait - = note: this error originates in the attribute macro `cgp_type` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_cyclic_context.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_cyclic_context.rs deleted file mode 100644 index 85e7a5e5..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_cyclic_context.rs +++ /dev/null @@ -1,37 +0,0 @@ -//! Acceptable failure: two nested `#[use_type]` imports whose `in Context` clauses -//! reference *each other*, so there is no valid order in which to ground them. -//! -//! `HasA.A in B` resolves `A` against `B`, and `HasB.B in A` resolves `B` against -//! `A` — a cycle. Grounding runs to a fixpoint and deliberately stops making -//! progress on a cycle rather than looping, so the context aliases are never -//! resolved and the rewrite leaves the bare `A` and `B` from the `in` clauses in -//! type position. CGP lowers the input faithfully and defers to the compiler, -//! which reports `E0425` "cannot find type" with the caret on the unresolved -//! context alias the user wrote in the attribute. -//! -//! An *acyclic* chain in any order (`HasC.C in B, HasB.B in A, HasA.A` written -//! back-to-front) grounds fine — see the passing `use_type_fn_reverse_order` -//! behavioral test. Only a genuine cycle, which has no valid ordering, fails. -//! -//! See docs/reference/attributes/use_type.md and -//! docs/errors/lowering/unresolved-imported-type.md. - -use cgp::prelude::*; - -#[cgp_type] -pub trait HasA { - type A; -} - -#[cgp_type] -pub trait HasB { - type B; -} - -#[cgp_fn] -#[use_type(HasA.A in B, HasB.B in A)] -pub fn deep(&self) -> A { - todo!() -} - -fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_cyclic_context.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_cyclic_context.stderr deleted file mode 100644 index fc3f994d..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_cyclic_context.stderr +++ /dev/null @@ -1,11 +0,0 @@ -error[E0425]: cannot find type `A` in this scope - --> tests/acceptable/cgp_fn/use_type_cyclic_context.rs:32:35 - | -32 | #[use_type(HasA.A in B, HasB.B in A)] - | ^ not found in this scope - -error[E0425]: cannot find type `B` in this scope - --> tests/acceptable/cgp_fn/use_type_cyclic_context.rs:32:22 - | -32 | #[use_type(HasA.A in B, HasB.B in A)] - | ^ not found in this scope diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs deleted file mode 100644 index 0794e242..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs +++ /dev/null @@ -1,53 +0,0 @@ -//! Acceptable failure: a *nested* foreign `#[use_type(HasTypes.Types, HasScalarType.Scalar in Types)]` -//! import adds the two-hop bound `::Types: HasScalarType` to the -//! generated trait, so a context whose `Types` associated type does not implement -//! `HasScalarType` is rejected — proof the transitively-grounded foreign bound is -//! enforced at depth, not just for a directly-named parameter. -//! -//! Before the foreign bound was carried onto the trait, this nested constraint was -//! silently dropped. CGP is now working as designed: it emits the grounded bound -//! and defers the check to `rustc`, which reports the missing `NoScalar: HasScalarType` -//! at the site that requires `App: GetScalar`. -//! -//! See docs/reference/attributes/use_type.md and docs/errors/checks/check-trait-failure.md. - -use cgp::prelude::*; - -#[cgp_type] -pub trait HasTypes { - type Types; -} - -#[cgp_type] -pub trait HasScalarType { - type Scalar; -} - -// The generated `GetScalar` trait becomes, after the rewrite: -// pub trait GetScalar: HasTypes -// where ::Types: HasScalarType -// { fn get_scalar(&self) -> <::Types as HasScalarType>::Scalar; } -#[cgp_fn] -#[use_type(HasTypes.Types, HasScalarType.Scalar in Types)] -pub fn get_scalar(&self) -> Scalar { - todo!() -} - -// `App::Types` is `NoScalar`, which deliberately does not implement `HasScalarType`. -pub struct NoScalar; - -pub struct App; - -impl HasTypes for App { - type Types = NoScalar; -} - -// Requires `App: GetScalar`, which the blanket impl reduces to -// `::Types: HasScalarType`, i.e. `NoScalar: HasScalarType`. -fn assert_app() -where - App: GetScalar, -{ -} - -fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_nested_unsatisfied.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_nested_unsatisfied.stderr deleted file mode 100644 index 0b95e975..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_nested_unsatisfied.stderr +++ /dev/null @@ -1,171 +0,0 @@ -error[E0277]: the trait bound `NoScalar: HasScalarType` is not satisfied - --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:49:10 - | -49 | App: GetScalar, - | ^^^^^^^^^ unsatisfied trait bound - | -help: the trait `DelegateComponent` is not implemented for `NoScalar` - --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:37:1 - | -37 | pub struct NoScalar; - | ^^^^^^^^^^^^^^^^^^^ - = help: the following other types implement trait `DelegateComponent`: - `BuildAndMergeOutputs` implements `DelegateComponent` - `BuildAndMergeOutputs` implements `DelegateComponent` - `BuildAndMergeOutputs` implements `DelegateComponent` - `BuildAndMergeOutputs` implements `DelegateComponent` - `BuildAndMergeOutputs` implements `DelegateComponent` - `BuildAndMergeOutputs` implements `DelegateComponent` - `MatchFirstWithFieldHandlersInputs` implements `DelegateComponent<(Input, Args)>` - `MatchFirstWithFieldHandlersInputsMut` implements `DelegateComponent<(&'a mut Input, Args)>` - and $N others -note: required for `NoScalar` to implement `ScalarTypeProvider` - --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:21:1 - | -21 | #[cgp_type] - | ^^^^^^^^^^^ -note: required for `NoScalar` to implement `HasScalarType` - --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:21:1 - | -21 | #[cgp_type] - | ^^^^^^^^^^^ -22 | pub trait HasScalarType { - | ^^^^^^^^^^^^^ -note: required by a bound in `GetScalar` - --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:31:28 - | -31 | #[use_type(HasTypes.Types, HasScalarType.Scalar in Types)] - | ^^^^^^^^^^^^^ required by this bound in `GetScalar` -32 | pub fn get_scalar(&self) -> Scalar { - | ---------- required by a bound in this trait - = note: this error originates in the attribute macro `cgp_type` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: the trait bound `NoScalar: HasScalarType` is not satisfied - --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:49:10 - | -49 | App: GetScalar, - | ^^^^^^^^^ unsatisfied trait bound - | -help: the trait `IsProviderFor` is not implemented for `NoScalar` - --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:37:1 - | -37 | pub struct NoScalar; - | ^^^^^^^^^^^^^^^^^^^ - = help: the following other types implement trait `IsProviderFor`: - `BindErr` implements `IsProviderFor)>` - `BindErr` implements `IsProviderFor)>` - `BindOk` implements `IsProviderFor)>` - `BindOk` implements `IsProviderFor)>` - `BuildAndMerge` implements `IsProviderFor` - `BuildAndMerge` implements `IsProviderFor` - `BuildAndMerge` implements `IsProviderFor` - `BuildAndMergeOutputs` implements `IsProviderFor` - and $N others -note: required for `NoScalar` to implement `ScalarTypeProvider` - --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:21:1 - | -21 | #[cgp_type] - | ^^^^^^^^^^^ -note: required for `NoScalar` to implement `HasScalarType` - --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:21:1 - | -21 | #[cgp_type] - | ^^^^^^^^^^^ -22 | pub trait HasScalarType { - | ^^^^^^^^^^^^^ -note: required by a bound in `GetScalar` - --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:31:28 - | -31 | #[use_type(HasTypes.Types, HasScalarType.Scalar in Types)] - | ^^^^^^^^^^^^^ required by this bound in `GetScalar` -32 | pub fn get_scalar(&self) -> Scalar { - | ---------- required by a bound in this trait - = note: this error originates in the attribute macro `cgp_type` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: the trait bound `NoScalar: DelegateComponent` is not satisfied - --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:49:5 - | -49 | App: GetScalar, - | ^^^^^^^^^^^^^^ unsatisfied trait bound - | -help: the trait `DelegateComponent` is not implemented for `NoScalar` - --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:37:1 - | -37 | pub struct NoScalar; - | ^^^^^^^^^^^^^^^^^^^ - = help: the following other types implement trait `DelegateComponent`: - `BuildAndMergeOutputs` implements `DelegateComponent` - `BuildAndMergeOutputs` implements `DelegateComponent` - `BuildAndMergeOutputs` implements `DelegateComponent` - `BuildAndMergeOutputs` implements `DelegateComponent` - `BuildAndMergeOutputs` implements `DelegateComponent` - `BuildAndMergeOutputs` implements `DelegateComponent` - `MatchFirstWithFieldHandlersInputs` implements `DelegateComponent<(Input, Args)>` - `MatchFirstWithFieldHandlersInputsMut` implements `DelegateComponent<(&'a mut Input, Args)>` - and $N others -note: required for `NoScalar` to implement `ScalarTypeProvider` - --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:21:1 - | -21 | #[cgp_type] - | ^^^^^^^^^^^ -note: required for `NoScalar` to implement `HasScalarType` - --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:21:1 - | -21 | #[cgp_type] - | ^^^^^^^^^^^ -22 | pub trait HasScalarType { - | ^^^^^^^^^^^^^ -note: required for `App` to implement `GetScalar` - --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:30:1 - | -30 | #[cgp_fn] - | ^^^^^^^^^ -31 | #[use_type(HasTypes.Types, HasScalarType.Scalar in Types)] - | ------------- unsatisfied trait bound introduced here -32 | pub fn get_scalar(&self) -> Scalar { - | ^^^^^^^^^^ - = note: this error originates in the attribute macro `cgp_type` which comes from the expansion of the attribute macro `cgp_fn` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: the trait bound `NoScalar: IsProviderFor` is not satisfied - --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:49:5 - | -49 | App: GetScalar, - | ^^^^^^^^^^^^^^ unsatisfied trait bound - | -help: the trait `IsProviderFor` is not implemented for `NoScalar` - --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:37:1 - | -37 | pub struct NoScalar; - | ^^^^^^^^^^^^^^^^^^^ - = help: the following other types implement trait `IsProviderFor`: - `BindErr` implements `IsProviderFor)>` - `BindErr` implements `IsProviderFor)>` - `BindOk` implements `IsProviderFor)>` - `BindOk` implements `IsProviderFor)>` - `BuildAndMerge` implements `IsProviderFor` - `BuildAndMerge` implements `IsProviderFor` - `BuildAndMerge` implements `IsProviderFor` - `BuildAndMergeOutputs` implements `IsProviderFor` - and $N others -note: required for `NoScalar` to implement `ScalarTypeProvider` - --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:21:1 - | -21 | #[cgp_type] - | ^^^^^^^^^^^ -note: required for `NoScalar` to implement `HasScalarType` - --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:21:1 - | -21 | #[cgp_type] - | ^^^^^^^^^^^ -22 | pub trait HasScalarType { - | ^^^^^^^^^^^^^ -note: required for `App` to implement `GetScalar` - --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:30:1 - | -30 | #[cgp_fn] - | ^^^^^^^^^ -31 | #[use_type(HasTypes.Types, HasScalarType.Scalar in Types)] - | ------------- unsatisfied trait bound introduced here -32 | pub fn get_scalar(&self) -> Scalar { - | ^^^^^^^^^^ - = note: this error originates in the attribute macro `cgp_type` which comes from the expansion of the attribute macro `cgp_fn` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_unknown_assoc.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_unknown_assoc.rs deleted file mode 100644 index c453a5d7..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_unknown_assoc.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! Acceptable failure: `#[use_type]` imports an associated type name the owning -//! trait does not declare, so the substituted `::WrongName` path -//! names an associated type that does not exist and the compiler rejects it. -//! -//! `HasScalarType` declares `Scalar`, but the import names `Scalr` (a typo), so the -//! bare `Scalr` in the signature is rewritten to `::Scalr`. -//! CGP cannot know the trait's associated types at expansion time — it performs a -//! textual rewrite — so it lowers the name faithfully and defers to the compiler, -//! which reports `E0576` "cannot find associated type `Scalr`". Because the -//! substitution preserves the *span* of the identifier the user wrote, the caret -//! lands on the `Scalr` in the signature, not on the macro attribute — so this -//! fixture also guards that span behavior. -//! -//! See docs/reference/attributes/use_type.md and -//! docs/errors/lowering/unresolved-imported-type.md. - -use cgp::prelude::*; - -#[cgp_type] -pub trait HasScalarType { - type Scalar; -} - -#[cgp_fn] -#[use_type(HasScalarType.Scalr)] -pub fn get_scalar(&self) -> Scalr { - todo!() -} - -fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_unknown_assoc.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_unknown_assoc.stderr deleted file mode 100644 index 0b804cae..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_unknown_assoc.stderr +++ /dev/null @@ -1,13 +0,0 @@ -error[E0576]: cannot find associated type `Scalr` in trait `HasScalarType` - --> tests/acceptable/cgp_fn/use_type_unknown_assoc.rs:26:29 - | -21 | type Scalar; - | ------------ similarly named associated type `Scalar` defined here -... -26 | pub fn get_scalar(&self) -> Scalr { - | ^^^^^ - | -help: an associated type with a similar name exists - | -26 | pub fn get_scalar(&self) -> Scalar { - | + diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_impl/duplicate_default_impl.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_impl/duplicate_default_impl.rs deleted file mode 100644 index 24f7ad61..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_impl/duplicate_default_impl.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! Acceptable failure: two `#[cgp_impl]` blocks each carrying a -//! `#[default_impl(String in DefaultImpls1<..>)]` for the same key emit two -//! conflicting `DefaultImpls1` impls for -//! `String`, which the Rust compiler rejects with the coherence error E0119. -//! `#[cgp_impl]` lowers each block independently and has no view of the other, -//! so it correctly defers to the compiler, exactly as two hand-written -//! overlapping impls would. -//! -//! The carets fall on the `String` key inside `#[default_impl(...)]` rather than -//! on the whole `#[cgp_impl]` attribute, because the synthesized default-impl is -//! re-spanned onto that key token (see -//! cgp-macro-core/src/types/attributes/default_impl/attribute.rs). A regression -//! that dropped the re-span would move the carets back onto the macro attribute. -//! -//! See docs/errors/wiring/conflicting-wiring.md. - -use cgp::core::component::DefaultImpls1; -use cgp::prelude::*; - -#[cgp_component(ShowImpl)] -pub trait Show { - fn show(&self, value: &T) -> String; -} - -#[cgp_impl(new ShowStringA)] -#[default_impl(String in DefaultImpls1)] -impl ShowImpl { - fn show(&self, value: &String) -> String { - value.clone() - } -} - -// Re-registering the same key as a per-type default emits a conflicting impl. -#[cgp_impl(new ShowStringB)] -#[default_impl(String in DefaultImpls1)] -impl ShowImpl { - fn show(&self, value: &String) -> String { - value.clone() - } -} - -fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_impl/duplicate_default_impl.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_impl/duplicate_default_impl.stderr deleted file mode 100644 index 3423df8b..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_impl/duplicate_default_impl.stderr +++ /dev/null @@ -1,8 +0,0 @@ -error[E0119]: conflicting implementations of trait `DefaultImpls1` for type `String` - --> tests/acceptable/cgp_impl/duplicate_default_impl.rs:35:16 - | -26 | #[default_impl(String in DefaultImpls1)] - | ------ first implementation here -... -35 | #[default_impl(String in DefaultImpls1)] - | ^^^^^^ conflicting implementation for `String` diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_impl/duplicate_provider_name.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_impl/duplicate_provider_name.rs deleted file mode 100644 index 2b114f37..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_impl/duplicate_provider_name.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! Acceptable failure: two `#[cgp_impl(new GreetHello)]` blocks each declare a -//! `pub struct GreetHello;`, so the name is defined twice (E0428) and the two -//! provider impls also conflict (E0119). `#[cgp_impl]` lowers each block -//! independently and has no view of the other, so it correctly defers both to -//! the compiler, exactly as two hand-written definitions would. -//! -//! The E0428 carets fall on the `GreetHello` name inside `#[cgp_impl(new …)]` -//! rather than on the whole attribute, because the synthesized provider struct is -//! emitted with the struct ident's span (see -//! cgp-macro-core/src/types/empty_struct.rs). A regression that stamped the -//! struct with `call_site` would move the carets back onto the macro attribute. -//! The E0119 carets fall on each provider `impl` block, since those impls are the -//! user's own `#[cgp_impl]` blocks rewritten in place. -//! -//! See docs/errors/wiring/conflicting-wiring.md. - -use cgp::prelude::*; - -#[cgp_component(Greeter)] -pub trait CanGreet { - fn greet(&self); -} - -#[cgp_impl(new GreetHello)] -impl Greeter { - fn greet(&self) {} -} - -// Re-declaring the same provider name emits a conflicting struct + impls. -#[cgp_impl(new GreetHello)] -impl Greeter { - fn greet(&self) {} -} - -fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_impl/duplicate_provider_name.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_impl/duplicate_provider_name.stderr deleted file mode 100644 index 9bf8417c..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_impl/duplicate_provider_name.stderr +++ /dev/null @@ -1,36 +0,0 @@ -error[E0428]: the name `GreetHello` is defined multiple times - --> tests/acceptable/cgp_impl/duplicate_provider_name.rs:30:16 - | -24 | #[cgp_impl(new GreetHello)] - | ---------- previous definition of the type `GreetHello` here -... -30 | #[cgp_impl(new GreetHello)] - | ^^^^^^^^^^ `GreetHello` redefined here - | - = note: `GreetHello` must be defined only once in the type namespace of this module - -error[E0119]: conflicting implementations of trait `Greeter<_>` for type `GreetHello` - --> tests/acceptable/cgp_impl/duplicate_provider_name.rs:31:1 - | -25 | / impl Greeter { -26 | | fn greet(&self) {} -27 | | } - | |_- first implementation here -... -31 | / impl Greeter { -32 | | fn greet(&self) {} -33 | | } - | |_^ conflicting implementation for `GreetHello` - -error[E0119]: conflicting implementations of trait `IsProviderFor` for type `GreetHello` - --> tests/acceptable/cgp_impl/duplicate_provider_name.rs:31:1 - | -25 | / impl Greeter { -26 | | fn greet(&self) {} -27 | | } - | |_- first implementation here -... -31 | / impl Greeter { -32 | | fn greet(&self) {} -33 | | } - | |_^ conflicting implementation for `GreetHello` diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_component.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_component.rs deleted file mode 100644 index 1d6d6cf3..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_component.rs +++ /dev/null @@ -1,38 +0,0 @@ -//! Acceptable failure: a downstream crate registers a `#[default_impl]` for a -//! foreign *unprefixed* component into a foreign namespace — an orphan violation -//! even though the component key is a bare marker rather than a prefix path. -//! -//! `GreeterComponent` (unprefixed) and `AppNamespace` both come from -//! `cgp-test-crate-a`, so `#[default_impl(GreeterComponent in AppNamespace)]` -//! expands to `impl<__Components__> AppNamespace<__Components__> for GreeterComponent`, -//! whose trait (`AppNamespace`) and self type (`GreeterComponent`) are both foreign -//! to this crate. Rust accepts a foreign-trait impl only when a local type covers -//! its type parameters, and here none does, so `__Components__` is an uncovered -//! type parameter and the orphan rule rejects it (`E0210`, caret on the -//! `#[cgp_impl]` attribute that generated the impl). Registering a per-component -//! default therefore needs the crate to own *either* the namespace or the component -//! key; owning neither, this crate cannot. -//! -//! This is the bare-marker sibling of default_impl_foreign_prefix_path.rs, whose -//! key is a foreign `PathCons<..>` path: both are the same orphan violation, and -//! this one shows the restriction is not specific to prefixed components. The -//! orphan-*safe* counterpart — a *local* component key registered into the foreign -//! `AppNamespace` — is exercised in `cgp-test-crate-b`. -//! -//! See docs/errors/wiring/orphan-rule.md. - -use cgp::prelude::*; -use cgp_test_crate_a::{AppNamespace, Greeter, GreeterComponent, HasName}; - -#[cgp_impl(new GreetPolitely)] -#[default_impl(GreeterComponent in AppNamespace)] -impl Greeter -where - Self: HasName, -{ - fn greet(&self) -> String { - format!("Good day, {}", self.name()) - } -} - -fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_component.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_component.stderr deleted file mode 100644 index fa3ca580..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_component.stderr +++ /dev/null @@ -1,9 +0,0 @@ -error[E0210]: type parameter `__Components__` must be used as the type parameter for some local type (e.g., `MyStruct<__Components__>`) - --> tests/acceptable/cgp_namespace/default_impl_foreign_component.rs:27:1 - | -27 | #[cgp_impl(new GreetPolitely)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type parameter `__Components__` must be used as the type parameter for some local type - | - = note: implementing a foreign trait is only possible if at least one of the types for which it is implemented is local - = note: only traits defined in the current crate can be implemented for a type parameter - = note: this error originates in the attribute macro `cgp_impl` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_prefix_path.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_prefix_path.rs deleted file mode 100644 index 8aa516f4..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_prefix_path.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! Acceptable failure: a downstream crate cannot register a default for a -//! *prefixed* upstream component into the upstream namespace with `#[default_impl]`. -//! -//! `cgp-test-crate-a`'s `Announcer` carries `#[prefix(@app in DefaultNamespace)]`, -//! so its namespace key is the path `@app.AnnouncerComponent`. Registering a -//! default at that path in crate-a's `AppNamespace` expands to -//! `impl AppNamespace<_> for PathCons, PathCons>`, -//! whose trait (`AppNamespace`) and every element of the `Self` type (`PathCons` -//! and `Symbol` from `cgp`, `AnnouncerComponent` from crate-a) are all foreign to -//! this crate — an orphan-rule violation (E0210). A per-component default keyed on -//! a *prefix path* can therefore only be written in the crate that owns the -//! namespace; a downstream crate must own the key, which for a prefixed component -//! it does not. (The orphan-safe counterpart — a *local* component key registered -//! into a foreign namespace — is exercised in `cgp-test-crate-b`.) -//! -//! This is why `#[default_impl]` couples an implementation to the namespace's -//! crate and why the guide recommends namespace *body* entries for wiring that -//! must live downstream of the namespace. -//! -//! See docs/errors/wiring/orphan-rule.md. - -use cgp::prelude::*; -use cgp_test_crate_a::{Announcer, AnnouncerComponent, AppNamespace, HasName}; - -#[cgp_impl(new AnnounceQuietly)] -#[default_impl(@app.AnnouncerComponent in AppNamespace)] -impl Announcer -where - Self: HasName, -{ - fn announce(&self) -> String { - format!("(psst, {})", self.name()) - } -} - -fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_prefix_path.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_prefix_path.stderr deleted file mode 100644 index 5fc9529e..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_prefix_path.stderr +++ /dev/null @@ -1,9 +0,0 @@ -error[E0210]: type parameter `__Components__` must be used as the type parameter for some local type (e.g., `MyStruct<__Components__>`) - --> tests/acceptable/cgp_namespace/default_impl_foreign_prefix_path.rs:25:1 - | -25 | #[cgp_impl(new AnnounceQuietly)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type parameter `__Components__` must be used as the type parameter for some local type - | - = note: implementing a foreign trait is only possible if at least one of the types for which it is implemented is local - = note: only traits defined in the current crate can be implemented for a type parameter - = note: this error originates in the attribute macro `cgp_impl` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/duplicate_path_key.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/duplicate_path_key.rs deleted file mode 100644 index 20a040e2..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/duplicate_path_key.rs +++ /dev/null @@ -1,26 +0,0 @@ -//! Acceptable failure: two `@`-path prefix-rewrite entries in one `cgp_namespace!` -//! block that map the same path produce two conflicting `MyNamespace<_>` impls -//! (keyed by the same `PathCons<..>` type), rejected with the coherence error -//! E0119 — the namespace analogue of the `delegate_components!` -//! [duplicate_path_key.rs](../delegate_components/duplicate_path_key.rs). -//! -//! This fixture pins the **error span** for a namespace `@`-path key. The key -//! type is a synthesized `PathCons<..>` nest whose own span points at the macro -//! `call_site`; the entry instead carries the span of the path segments the user -//! wrote, so E0119 lands on the duplicated `@foo.bar` leaf segment rather than on -//! the whole `cgp_namespace!` block. If the re-spanning in -//! `build_namespace_impl` (`mapping/eval.rs`) regresses, the caret snaps back to -//! the block and this `.stderr` changes. -//! -//! See docs/errors/wiring/conflicting-wiring.md; error-span mechanics in docs/implementation/entrypoints/cgp_namespace.md. - -use cgp::prelude::*; - -cgp_namespace! { - new MyNamespace { - @foo.bar => @baz, - @foo.bar => @qux, - } -} - -fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/duplicate_path_key.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/duplicate_path_key.stderr deleted file mode 100644 index b32edd27..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/duplicate_path_key.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[E0119]: conflicting implementations of trait `MyNamespace<_>` for type `PathCons>>>, PathCons>>>, _>>` - --> tests/acceptable/cgp_namespace/duplicate_path_key.rs:22:14 - | -21 | @foo.bar => @baz, - | --- first implementation here -22 | @foo.bar => @qux, - | ^^^ conflicting implementation for `PathCons>>>, PathCons>>>, _>>` diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/for_loop_bare_key.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/for_loop_bare_key.rs deleted file mode 100644 index 5f082a83..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/for_loop_bare_key.rs +++ /dev/null @@ -1,55 +0,0 @@ -//! Acceptable failure: a `for` loop that wires a **bare key** (`Key: Value`) -//! instead of embedding it in a path, in a context that also joins a namespace — -//! the two blanket impls overlap and the compiler rejects them with `E0119`. -//! -//! `namespace DefaultNamespace;` emits a blanket `impl -//! DelegateComponent for App where Key: DefaultNamespace` (plus the -//! matching `IsProviderFor` forwarding) that covers *every* key. A `for -//! in GreeterTable { Key: Value }` loop emits a second blanket `impl -//! DelegateComponent for App where Key: GreeterTable` — also over every -//! key — and the two overlap because a key could satisfy both `where` clauses, so -//! coherence rejects the pair (`E0119`, fully generic `DelegateComponent<_>` / -//! `IsProviderFor<_, _, _>`). This is why a loop key must sit inside a path -//! (`@app.SomeComponent.Key: Value`), which keys the impl on a concrete path rather -//! than on every key. CGP lowers both blanket impls faithfully; only the whole -//! program reveals the overlap, so it defers to the compiler. -//! -//! This is the blanket-vs-blanket shape of the overlapping-forwarding class, -//! alongside two_namespaces_joined.rs (two `namespace` joins on one context); -//! contrast the specific-vs-blanket override in override_registered_path.rs. -//! -//! See docs/errors/wiring/namespace-forwarding-conflict.md. - -use cgp::prelude::*; - -#[cgp_component(Greeter)] -pub trait CanGreet { - fn greet(&self) -> String; -} - -#[cgp_impl(new GreetHello)] -impl Greeter { - fn greet(&self) -> String { - "Hello".to_owned() - } -} - -cgp_namespace! { - new GreeterTable { - GreeterComponent: GreetHello, - } -} - -pub struct App; - -delegate_components! { - App { - namespace DefaultNamespace; - - for in GreeterTable { - Key: Value, - } - } -} - -fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/for_loop_bare_key.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/for_loop_bare_key.stderr deleted file mode 100644 index f8f88c87..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/for_loop_bare_key.stderr +++ /dev/null @@ -1,17 +0,0 @@ -error[E0119]: conflicting implementations of trait `IsProviderFor<_, _, _>` for type `App` - --> tests/acceptable/cgp_namespace/for_loop_bare_key.rs:50:13 - | -47 | namespace DefaultNamespace; - | ---------------- first implementation here -... -50 | Key: Value, - | ^^^ conflicting implementation for `App` - -error[E0119]: conflicting implementations of trait `DelegateComponent<_>` for type `App` - --> tests/acceptable/cgp_namespace/for_loop_bare_key.rs:50:13 - | -47 | namespace DefaultNamespace; - | ---------------- first implementation here -... -50 | Key: Value, - | ^^^ conflicting implementation for `App` diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/inheritance_cycle.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/inheritance_cycle.rs deleted file mode 100644 index bf9bdd26..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/inheritance_cycle.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! Acceptable failure: two namespaces that inherit from each other, so resolving -//! any key through either one chases the other and back with no terminating step — -//! a trait-resolution cycle the compiler reports as `E0275` overflow. -//! -//! `new NamespaceA: NamespaceB` emits the inheritance blanket impl -//! `impl NamespaceA for Key where Key: NamespaceB<..>, ..`, -//! and `new NamespaceB: NamespaceA` emits the mirror impl. Evaluating the `where` -//! clause of either impl requires evaluating the other's, which requires the first -//! again — an infinite chain. Unlike the lazy `UseContext` wiring cycle (which is -//! accepted and only overflows when forced through a check), this cycle is caught -//! **eagerly at the two `cgp_namespace!` definitions**: the compiler evaluates each -//! inheritance impl's own `where` bound and overflows, so both definitions carry an -//! `E0275`, with no joining context required. A self-inheriting `new A: A` fails the -//! same way. CGP cannot see that the parent chain is circular from one macro -//! invocation, so it lowers each namespace faithfully and defers to the compiler. -//! -//! See docs/errors/wiring/namespace-inheritance-cycle.md. - -use cgp::prelude::*; - -cgp_namespace! { - new NamespaceA: NamespaceB {} -} - -cgp_namespace! { - new NamespaceB: NamespaceA {} -} - -fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/inheritance_cycle.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/inheritance_cycle.stderr deleted file mode 100644 index a29c16d8..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/inheritance_cycle.stderr +++ /dev/null @@ -1,53 +0,0 @@ -error[E0275]: overflow evaluating the requirement `__Key__: NamespaceA<__NamespaceBComponents>` - --> tests/acceptable/cgp_namespace/inheritance_cycle.rs:21:1 - | -21 | / cgp_namespace! { -22 | | new NamespaceA: NamespaceB {} -23 | | } - | |_^ - | - = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`$CRATE`) -note: required for `__Key__` to implement `NamespaceB<__NamespaceAComponents>` - --> tests/acceptable/cgp_namespace/inheritance_cycle.rs:25:1 - | -25 | / cgp_namespace! { -26 | | new NamespaceB: NamespaceA {} -27 | | } - | |_^ -note: required for `__Key__` to implement `NamespaceA<__NamespaceAComponents>` - --> tests/acceptable/cgp_namespace/inheritance_cycle.rs:21:1 - | -21 | / cgp_namespace! { -22 | | new NamespaceA: NamespaceB {} -23 | | } - | |_^ - = note: 3 redundant requirements hidden - = note: required for `__Key__` to implement `NamespaceB<__Table__>` - = note: this error originates in the macro `cgp_namespace` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0275]: overflow evaluating the requirement `__Key__: NamespaceB<__NamespaceAComponents>` - --> tests/acceptable/cgp_namespace/inheritance_cycle.rs:25:1 - | -25 | / cgp_namespace! { -26 | | new NamespaceB: NamespaceA {} -27 | | } - | |_^ - | - = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`$CRATE`) -note: required for `__Key__` to implement `NamespaceA<__NamespaceBComponents>` - --> tests/acceptable/cgp_namespace/inheritance_cycle.rs:21:1 - | -21 | / cgp_namespace! { -22 | | new NamespaceA: NamespaceB {} -23 | | } - | |_^ -note: required for `__Key__` to implement `NamespaceB<__NamespaceBComponents>` - --> tests/acceptable/cgp_namespace/inheritance_cycle.rs:25:1 - | -25 | / cgp_namespace! { -26 | | new NamespaceB: NamespaceA {} -27 | | } - | |_^ - = note: 3 redundant requirements hidden - = note: required for `__Key__` to implement `NamespaceA<__Table__>` - = note: this error originates in the macro `cgp_namespace` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/inherited_override_conflict.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/inherited_override_conflict.rs deleted file mode 100644 index 86693105..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/inherited_override_conflict.rs +++ /dev/null @@ -1,59 +0,0 @@ -//! Acceptable failure: a child namespace that inherits a parent and then -//! *redefines* a key the parent already binds — a namespace entry cannot be -//! overridden by an inheriting namespace. -//! -//! `new ChildNs: BaseNs` emits the inheritance blanket impl `impl ChildNs
for Key where Key: BaseNs<__ChildNsComponents>, Key: -//! BaseNs`, which forwards *every* key `BaseNs` resolves — -//! including `GreeterComponent`, since `BaseNs` binds it. The child's own -//! `GreeterComponent: GreetBye` entry emits a second impl `impl
ChildNs
-//! for GreeterComponent`, and the two overlap for that key, so coherence rejects the -//! pair (`E0119`, a *single* conflict on `ChildNs<_> for GreeterComponent`, since a -//! namespace emits only its own lookup-trait impl, not the context-side -//! `DelegateComponent`/`IsProviderFor` pair). Inheritance layers new keys onto a -//! parent; it cannot revise the parent's existing keys. To vary a key per -//! configuration, leave it *unbound* in the shared base and bind it in each child, -//! rather than binding it in the base and overriding it. CGP lowers both impls -//! faithfully; only the whole program reveals the overlap, so it defers to the -//! compiler. -//! -//! This is the namespace-level (inheritance) shape of the override-conflict class; -//! contrast the context-level shape in override_registered_path.rs, where a context -//! joining a namespace tries to override a path the namespace registers. -//! -//! See docs/errors/wiring/namespace-override-conflict.md. - -use cgp::prelude::*; - -#[cgp_component(Greeter)] -pub trait CanGreet { - fn greet(&self) -> String; -} - -#[cgp_impl(new GreetHello)] -impl Greeter { - fn greet(&self) -> String { - "Hello".to_owned() - } -} - -#[cgp_impl(new GreetBye)] -impl Greeter { - fn greet(&self) -> String { - "Bye".to_owned() - } -} - -cgp_namespace! { - new BaseNs { - GreeterComponent: GreetHello, - } -} - -cgp_namespace! { - new ChildNs: BaseNs { - GreeterComponent: GreetBye, - } -} - -fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/inherited_override_conflict.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/inherited_override_conflict.stderr deleted file mode 100644 index 2ec41d9e..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/inherited_override_conflict.stderr +++ /dev/null @@ -1,7 +0,0 @@ -error[E0119]: conflicting implementations of trait `ChildNs<_>` for type `GreeterComponent` - --> tests/acceptable/cgp_namespace/inherited_override_conflict.rs:55:9 - | -54 | new ChildNs: BaseNs { - | ------ first implementation here -55 | GreeterComponent: GreetBye, - | ^^^^^^^^^^^^^^^^ conflicting implementation for `GreeterComponent` diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/override_registered_path.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/override_registered_path.rs deleted file mode 100644 index 4f6a1fe5..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/override_registered_path.rs +++ /dev/null @@ -1,61 +0,0 @@ -//! Acceptable failure: a context that joins a namespace with `namespace N;` -//! cannot also wire, directly on itself, a path that `N` already registers. -//! -//! `GreetHello` registers the path `@app.GreeterComponent` into `AppNamespace` -//! with `#[default_impl]`, so `PathCons` implements -//! `AppNamespace<_>`. The `namespace AppNamespace;` header then emits a blanket -//! `impl DelegateComponent for App where Key: AppNamespace`, which -//! already covers that path. The extra `@app.GreeterComponent: GreetBye` entry -//! emits a second `DelegateComponent> for App`, -//! and the two overlap — E0119. CGP lowers both entries faithfully; only the whole -//! program reveals the overlap, so it defers to the compiler. -//! -//! The rule this pins: override a component the namespace routes by shadowing its -//! *marker* only when the namespace does not itself terminate the redirect path, -//! or wire the override on a path the namespace never registers. A namespace that -//! registers the leaf path leaves nothing for the context to override there. -//! -//! This is the context-level (join) shape of the override-conflict class; contrast -//! the namespace-level (inheritance) shape in inherited_override_conflict.rs, where -//! a child namespace tries to override an entry its parent binds. -//! -//! See docs/errors/wiring/namespace-override-conflict.md. - -use cgp::prelude::*; - -#[cgp_component(Greeter)] -#[prefix(@app in DefaultNamespace)] -pub trait CanGreet { - fn greet(&self) -> String; -} - -#[cgp_impl(new GreetHello)] -#[default_impl(@app.GreeterComponent in AppNamespace)] -impl Greeter { - fn greet(&self) -> String { - "Hello".to_owned() - } -} - -#[cgp_impl(new GreetBye)] -impl Greeter { - fn greet(&self) -> String { - "Bye".to_owned() - } -} - -cgp_namespace! { - new AppNamespace: DefaultNamespace {} -} - -pub struct App; - -delegate_components! { - App { - namespace AppNamespace; - - @app.GreeterComponent: GreetBye, - } -} - -fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/override_registered_path.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/override_registered_path.stderr deleted file mode 100644 index 3fa49dd9..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/override_registered_path.stderr +++ /dev/null @@ -1,20 +0,0 @@ -error[E0119]: conflicting implementations of trait `IsProviderFor>>>, PathCons>, _, _>` for type `App` - --> tests/acceptable/cgp_namespace/override_registered_path.rs:57:14 - | -55 | namespace AppNamespace; - | ------------ first implementation here -56 | -57 | @app.GreeterComponent: GreetBye, - | ^^^^^^^^^^^^^^^^ conflicting implementation for `App` - | - = note: downstream crates may implement trait `cgp::prelude::IsProviderFor>>>, cgp::prelude::PathCons>, _, _>` for type `GreetHello` - = note: downstream crates may implement trait `cgp::prelude::IsProviderFor>>>, cgp::prelude::PathCons>, _, _>` for type `GreetBye` - -error[E0119]: conflicting implementations of trait `DelegateComponent>>>, PathCons>>` for type `App` - --> tests/acceptable/cgp_namespace/override_registered_path.rs:57:14 - | -55 | namespace AppNamespace; - | ------------ first implementation here -56 | -57 | @app.GreeterComponent: GreetBye, - | ^^^^^^^^^^^^^^^^ conflicting implementation for `App` diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/reopen_foreign_namespace.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/reopen_foreign_namespace.rs deleted file mode 100644 index 29bdea3e..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/reopen_foreign_namespace.rs +++ /dev/null @@ -1,34 +0,0 @@ -//! Acceptable failure: a `cgp_namespace!` block *without* `new` re-opens a foreign -//! namespace to add an entry keyed on a foreign component — an orphan violation. -//! -//! Omitting `new` tells the macro the namespace trait is declared elsewhere and to -//! emit only the entry impls. Here `AppNamespace` and `GreeterComponent` both come -//! from `cgp-test-crate-a`, so the `GreeterComponent => @foo` entry expands to -//! `impl<__Table__> AppNamespace<__Table__> for GreeterComponent { type Delegate = -//! RedirectLookup<..> }`, whose trait and self type are both foreign. No local type -//! covers the table parameter, so `__Table__` is uncovered and the orphan rule -//! rejects it (`E0210`, caret on the whole `cgp_namespace!` block, offending -//! parameter `__Table__` — the namespace table parameter, distinct from the -//! `__Components__` of a `#[default_impl]`). A crate may only add entries to a -//! namespace whose trait it owns; to extend a foreign namespace, define a *new* -//! local namespace that *inherits* it (`new Local: AppNamespace { .. }`), which is -//! orphan-safe because the emitted impls are for the local trait. CGP lowers the -//! entry faithfully; only the whole program reveals the impl is foreign, so it -//! defers to the compiler. -//! -//! This is the `cgp_namespace!` trigger of the orphan class, alongside the -//! `#[default_impl]` triggers in default_impl_foreign_prefix_path.rs and -//! default_impl_foreign_component.rs. -//! -//! See docs/errors/wiring/orphan-rule.md. - -use cgp::prelude::*; -use cgp_test_crate_a::{AppNamespace, GreeterComponent}; - -cgp_namespace! { - AppNamespace { - GreeterComponent => @foo, - } -} - -fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/reopen_foreign_namespace.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/reopen_foreign_namespace.stderr deleted file mode 100644 index cd2c78e8..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/reopen_foreign_namespace.stderr +++ /dev/null @@ -1,13 +0,0 @@ -error[E0210]: type parameter `__Table__` must be used as the type parameter for some local type (e.g., `MyStruct<__Table__>`) - --> tests/acceptable/cgp_namespace/reopen_foreign_namespace.rs:28:1 - | -28 | / cgp_namespace! { -29 | | AppNamespace { -30 | | GreeterComponent => @foo, -31 | | } -32 | | } - | |_^ type parameter `__Table__` must be used as the type parameter for some local type - | - = note: implementing a foreign trait is only possible if at least one of the types for which it is implemented is local - = note: only traits defined in the current crate can be implemented for a type parameter - = note: this error originates in the macro `cgp_namespace` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/two_namespaces_joined.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/two_namespaces_joined.rs deleted file mode 100644 index 38a539dd..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/two_namespaces_joined.rs +++ /dev/null @@ -1,57 +0,0 @@ -//! Acceptable failure: a context that joins **two** namespaces at once — -//! `namespace NamespaceA; namespace NamespaceB;` — cannot compile, because each -//! join emits a *blanket* forwarding impl over every key and the two overlap. -//! -//! Each `namespace N;` header emits `impl DelegateComponent for -//! App where Key: N` (plus the matching `IsProviderFor` forwarding), a -//! blanket impl that covers *every* key. Joining two namespaces emits two such -//! blanket impls — one keyed through `NamespaceA`, one through `NamespaceB` — and -//! because a key could satisfy both `where` clauses, coherence cannot prove they -//! never overlap and rejects the pair (`E0119`, fully generic `DelegateComponent<_>` -//! / `IsProviderFor<_, _, _>`, carets on the two `namespace` lines, no downstream -//! note). A context therefore forwards through at most one namespace; layer several -//! by having that one namespace *inherit* the others (`new Combined: A { .. }` -//! inheriting further), not by joining several on the context. CGP lowers both -//! blanket impls faithfully; only the whole program reveals the overlap, so it -//! defers to the compiler. -//! -//! This is the blanket-vs-blanket shape of the overlapping-forwarding class, -//! alongside for_loop_bare_key.rs (a namespace join plus a bare-key `for` loop); -//! contrast the specific-vs-blanket override in override_registered_path.rs. -//! -//! See docs/errors/wiring/namespace-forwarding-conflict.md. - -use cgp::prelude::*; - -#[cgp_component(Greeter)] -pub trait CanGreet { - fn greet(&self) -> String; -} - -#[cgp_impl(new GreetHello)] -impl Greeter { - fn greet(&self) -> String { - "Hello".to_owned() - } -} - -cgp_namespace! { - new NamespaceA { - GreeterComponent: GreetHello, - } -} - -cgp_namespace! { - new NamespaceB {} -} - -pub struct App; - -delegate_components! { - App { - namespace NamespaceA; - namespace NamespaceB; - } -} - -fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/two_namespaces_joined.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/two_namespaces_joined.stderr deleted file mode 100644 index d713dfb3..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/two_namespaces_joined.stderr +++ /dev/null @@ -1,15 +0,0 @@ -error[E0119]: conflicting implementations of trait `IsProviderFor<_, _, _>` for type `App` - --> tests/acceptable/cgp_namespace/two_namespaces_joined.rs:53:19 - | -52 | namespace NamespaceA; - | ---------- first implementation here -53 | namespace NamespaceB; - | ^^^^^^^^^^ conflicting implementation for `App` - -error[E0119]: conflicting implementations of trait `DelegateComponent<_>` for type `App` - --> tests/acceptable/cgp_namespace/two_namespaces_joined.rs:53:19 - | -52 | namespace NamespaceA; - | ---------- first implementation here -53 | namespace NamespaceB; - | ^^^^^^^^^^ conflicting implementation for `App` diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/unregistered_prefix_path.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/unregistered_prefix_path.rs deleted file mode 100644 index 2d3562ce..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/unregistered_prefix_path.rs +++ /dev/null @@ -1,52 +0,0 @@ -//! Acceptable failure: a context joins a namespace that *routes* a prefixed -//! component to a path, but no entry ever *terminates* that path with a provider, -//! so the namespace lookup finds no delegate. -//! -//! `CanGreet` carries `#[prefix(@app in DefaultNamespace)]`, so `DefaultNamespace` -//! resolves `GreeterComponent` to `RedirectLookup<_, @app.GreeterComponent>`. `App` -//! joins `DefaultNamespace` with `namespace DefaultNamespace;`, so its -//! `GreeterComponent` lookup follows that redirect — but nothing (no `#[default_impl]`, -//! no namespace body entry, no direct `@app.GreeterComponent:` line) ever binds a -//! provider at that path. The defined `GreetHello` is never wired there. Resolving -//! the component therefore requires `App: DelegateComponent<@app.GreeterComponent>`, -//! for which there is no impl, and the `check_components!` surfaces that as an -//! `E0277` on `PathCons: DefaultNamespace`. -//! -//! This is the *lookup-failed* class — no provider is found at all — distinct from -//! an unsatisfied *dependency*, where a provider is found but its `where` clause is -//! unmet. The forgotten binding (usually a missing `#[default_impl]` or body entry) -//! is the common namespace mistake it captures. CGP lowers the wiring faithfully; -//! only the whole program reveals the path is unbound, so it defers to the compiler. -//! -//! See docs/errors/checks/unregistered-namespace-path.md. - -use cgp::prelude::*; - -#[cgp_component(Greeter)] -#[prefix(@app in DefaultNamespace)] -pub trait CanGreet { - fn greet(&self) -> String; -} - -#[cgp_impl(new GreetHello)] -impl Greeter { - fn greet(&self) -> String { - "Hello".to_owned() - } -} - -pub struct App; - -delegate_components! { - App { - namespace DefaultNamespace; - } -} - -check_components! { - App { - GreeterComponent, - } -} - -fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/unregistered_prefix_path.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/unregistered_prefix_path.stderr deleted file mode 100644 index ab028b3f..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/unregistered_prefix_path.stderr +++ /dev/null @@ -1,43 +0,0 @@ -error[E0277]: the trait bound `PathCons>>>, PathCons>: cgp::prelude::DefaultNamespace` is not satisfied - --> tests/acceptable/cgp_namespace/unregistered_prefix_path.rs:48:9 - | -48 | GreeterComponent, - | ^^^^^^^^^^^^^^^^ unsatisfied trait bound - | - = help: the trait `cgp::prelude::DefaultNamespace` is not implemented for `PathCons>>>, PathCons>` - = help: the following other types implement trait `cgp::prelude::DefaultNamespace`: - AsyncComputerComponent - AsyncComputerRefComponent - ComputerComponent - ComputerRefComponent - ErrorRaiserComponent - ErrorTypeProviderComponent - ErrorWrapperComponent - GreeterComponent - and $N others -note: required for `App` to implement `DelegateComponent>>>, PathCons>>` - --> tests/acceptable/cgp_namespace/unregistered_prefix_path.rs:40:1 - | -40 | / delegate_components! { -41 | | App { - | | ^^^ -42 | | namespace DefaultNamespace; -43 | | } -44 | | } - | |_^ -note: required for `RedirectLookup>>>, PathCons>>` to implement `IsProviderFor` - --> tests/acceptable/cgp_namespace/unregistered_prefix_path.rs:25:1 - | -25 | #[cgp_component(Greeter)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^ - = note: required for `App` to implement `CanUseComponent` -note: required by a bound in `__CheckApp` - --> tests/acceptable/cgp_namespace/unregistered_prefix_path.rs:46:1 - | -46 | / check_components! { -47 | | App { -48 | | GreeterComponent, -49 | | } -50 | | } - | |_^ required by this bound in `__CheckApp` - = note: this error originates in the macro `delegate_components` which comes from the expansion of the macro `check_components` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/dependency_cascade.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/dependency_cascade.rs deleted file mode 100644 index 0ddb7b26..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/dependency_cascade.rs +++ /dev/null @@ -1,90 +0,0 @@ -//! Acceptable failure: one missing field surfaces at every provider that -//! transitively depends on it, so a check of three chained components reports three -//! separate failures for a single root cause. `ProvideFoo` needs the `name` field, -//! `ProvideBar` depends on `CanFoo`, and `ProvideBaz` depends on `CanBar`; wiring all -//! three onto an `App` without a `name` field is accepted, and checking all three -//! forces a cascade of `E0277` blocks — six, not three, because the deeper components -//! also emit intermediate provider-bound failures (`ProvideBar: Bar`, -//! `ProvideFoo: Foo`) beside their `CanUseComponent` failure. Each -//! `CanUseComponent<..>` block does reach the concrete root cause in its `help:` note -//! (`HasField` not implemented for `App`); the intermediate blocks -//! name only an inner provider trait. Checked top-down (`Baz`, `Bar`, `Foo`), the last -//! block is `Foo`'s clean root-cause block. The count reflects the depth of the -//! dependency graph, not the number of mistakes — fixing the one field collapses the -//! whole cascade. This is the check doing its job, not a macro defect. -//! -//! See docs/errors/checks/verbose-cascade.md. - -use cgp::prelude::*; - -#[cgp_auto_getter] -pub trait HasName { - fn name(&self) -> &str; -} - -#[cgp_component(Foo)] -pub trait CanFoo { - fn foo(&self); -} - -#[cgp_component(Bar)] -pub trait CanBar { - fn bar(&self); -} - -#[cgp_component(Baz)] -pub trait CanBaz { - fn baz(&self); -} - -#[cgp_impl(new ProvideFoo)] -impl Foo -where - Self: HasName, -{ - fn foo(&self) { - let _ = self.name(); - } -} - -#[cgp_impl(new ProvideBar)] -#[uses(CanFoo)] -impl Bar { - fn bar(&self) { - self.foo(); - } -} - -#[cgp_impl(new ProvideBaz)] -#[uses(CanBar)] -impl Baz { - fn baz(&self) { - self.bar(); - } -} - -#[derive(HasField)] -pub struct App { - pub age: u8, -} - -delegate_components! { - App { - FooComponent: ProvideFoo, - BarComponent: ProvideBar, - BazComponent: ProvideBaz, - } -} - -// Each checked component fails because `App` lacks the `name` field the innermost -// provider needs, producing one error block per component. Listed top-down, so the -// root-cause block (`Foo`, naming the missing field) is reported last. -check_components! { - App { - BazComponent, - BarComponent, - FooComponent, - } -} - -fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/dependency_cascade.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/dependency_cascade.stderr deleted file mode 100644 index 0f2e2cd8..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/dependency_cascade.stderr +++ /dev/null @@ -1,333 +0,0 @@ -error[E0277]: the trait bound `ProvideBar: Bar` is not satisfied - --> tests/acceptable/check_components/dependency_cascade.rs:84:9 - | -84 | BazComponent, - | ^^^^^^^^^^^^ unsatisfied trait bound - | -help: the trait `Bar` is not implemented for `ProvideBar` - --> tests/acceptable/check_components/dependency_cascade.rs:50:16 - | -50 | #[cgp_impl(new ProvideBar)] - | ^^^^^^^^^^ -help: the trait `Bar<__Context__>` is implemented for `ProvideBar` - --> tests/acceptable/check_components/dependency_cascade.rs:52:1 - | -52 | / impl Bar { -53 | | fn bar(&self) { -54 | | self.foo(); -55 | | } -56 | | } - | |_^ -note: required for `App` to implement `Bar` - --> tests/acceptable/check_components/dependency_cascade.rs:30:1 - | -30 | #[cgp_component(Bar)] - | ^^^^^^^^^^^^^^^^^^^^^ -note: required for `App` to implement `CanBar` - --> tests/acceptable/check_components/dependency_cascade.rs:30:1 - | -30 | #[cgp_component(Bar)] - | ^^^^^^^^^^^^^^^^^^^^^ -31 | pub trait CanBar { - | ^^^^^^ -note: required for `ProvideBaz` to implement `IsProviderFor` - --> tests/acceptable/check_components/dependency_cascade.rs:58:1 - | -58 | #[cgp_impl(new ProvideBaz)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -59 | #[uses(CanBar)] - | ------ unsatisfied trait bound introduced here - = note: required for `App` to implement `CanUseComponent` -note: required by a bound in `__CheckApp` - --> tests/acceptable/check_components/dependency_cascade.rs:82:1 - | -82 | / check_components! { -83 | | App { -84 | | BazComponent, -85 | | BarComponent, -... | -88 | | } - | |_^ required by this bound in `__CheckApp` - = note: this error originates in the attribute macro `cgp_component` which comes from the expansion of the macro `check_components` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: the trait bound `ProvideFoo: Foo` is not satisfied - --> tests/acceptable/check_components/dependency_cascade.rs:84:9 - | -84 | BazComponent, - | ^^^^^^^^^^^^ unsatisfied trait bound - | -help: the trait `Foo` is not implemented for `ProvideFoo` - --> tests/acceptable/check_components/dependency_cascade.rs:40:16 - | -40 | #[cgp_impl(new ProvideFoo)] - | ^^^^^^^^^^ -help: the trait `Foo<__Context__>` is implemented for `ProvideFoo` - --> tests/acceptable/check_components/dependency_cascade.rs:41:1 - | -41 | / impl Foo -42 | | where -43 | | Self: HasName, - | |__________________^ -note: required for `App` to implement `Foo` - --> tests/acceptable/check_components/dependency_cascade.rs:25:1 - | -25 | #[cgp_component(Foo)] - | ^^^^^^^^^^^^^^^^^^^^^ -note: required for `App` to implement `CanFoo` - --> tests/acceptable/check_components/dependency_cascade.rs:25:1 - | -25 | #[cgp_component(Foo)] - | ^^^^^^^^^^^^^^^^^^^^^ -26 | pub trait CanFoo { - | ^^^^^^ -note: required for `ProvideBar` to implement `IsProviderFor` - --> tests/acceptable/check_components/dependency_cascade.rs:50:1 - | -50 | #[cgp_impl(new ProvideBar)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -51 | #[uses(CanFoo)] - | ------ unsatisfied trait bound introduced here - = note: 1 redundant requirement hidden - = note: required for `App` to implement `IsProviderFor` -note: required for `App` to implement `Bar` - --> tests/acceptable/check_components/dependency_cascade.rs:30:1 - | -30 | #[cgp_component(Bar)] - | ^^^^^^^^^^^^^^^^^^^^^ -note: required for `App` to implement `CanBar` - --> tests/acceptable/check_components/dependency_cascade.rs:30:1 - | -30 | #[cgp_component(Bar)] - | ^^^^^^^^^^^^^^^^^^^^^ -31 | pub trait CanBar { - | ^^^^^^ - = note: 1 redundant requirement hidden - = note: required for `ProvideBaz` to implement `IsProviderFor` - = note: required for `App` to implement `CanUseComponent` -note: required by a bound in `__CheckApp` - --> tests/acceptable/check_components/dependency_cascade.rs:82:1 - | -82 | / check_components! { -83 | | App { -84 | | BazComponent, -85 | | BarComponent, -... | -88 | | } - | |_^ required by this bound in `__CheckApp` - = note: this error originates in the attribute macro `cgp_component` which comes from the expansion of the macro `check_components` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: the trait bound `App: CanUseComponent` is not satisfied - --> tests/acceptable/check_components/dependency_cascade.rs:84:9 - | -84 | BazComponent, - | ^^^^^^^^^^^^ unsatisfied trait bound - | -help: the trait `HasField>>>>>` is not implemented for `App` - but trait `HasField>>>>` is implemented for it - --> tests/acceptable/check_components/dependency_cascade.rs:66:10 - | -66 | #[derive(HasField)] - | ^^^^^^^^ -note: required for `App` to implement `HasName` - --> tests/acceptable/check_components/dependency_cascade.rs:20:1 - | -20 | #[cgp_auto_getter] - | ^^^^^^^^^^^^^^^^^^ -21 | pub trait HasName { - | ^^^^^^^ -note: required for `ProvideFoo` to implement `IsProviderFor` - --> tests/acceptable/check_components/dependency_cascade.rs:40:1 - | -40 | #[cgp_impl(new ProvideFoo)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -... -43 | Self: HasName, - | ------- unsatisfied trait bound introduced here - = note: 1 redundant requirement hidden - = note: required for `App` to implement `IsProviderFor` -note: required for `App` to implement `Foo` - --> tests/acceptable/check_components/dependency_cascade.rs:25:1 - | -25 | #[cgp_component(Foo)] - | ^^^^^^^^^^^^^^^^^^^^^ -note: required for `App` to implement `CanFoo` - --> tests/acceptable/check_components/dependency_cascade.rs:25:1 - | -25 | #[cgp_component(Foo)] - | ^^^^^^^^^^^^^^^^^^^^^ -26 | pub trait CanFoo { - | ^^^^^^ - = note: 2 redundant requirements hidden - = note: required for `App` to implement `IsProviderFor` -note: required for `App` to implement `Bar` - --> tests/acceptable/check_components/dependency_cascade.rs:30:1 - | -30 | #[cgp_component(Bar)] - | ^^^^^^^^^^^^^^^^^^^^^ -note: required for `App` to implement `CanBar` - --> tests/acceptable/check_components/dependency_cascade.rs:30:1 - | -30 | #[cgp_component(Bar)] - | ^^^^^^^^^^^^^^^^^^^^^ -31 | pub trait CanBar { - | ^^^^^^ - = note: 1 redundant requirement hidden - = note: required for `ProvideBaz` to implement `IsProviderFor` - = note: required for `App` to implement `CanUseComponent` -note: required by a bound in `__CheckApp` - --> tests/acceptable/check_components/dependency_cascade.rs:82:1 - | -82 | / check_components! { -83 | | App { -84 | | BazComponent, -85 | | BarComponent, -... | -88 | | } - | |_^ required by this bound in `__CheckApp` - = note: this error originates in the derive macro `HasField` which comes from the expansion of the macro `check_components` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: the trait bound `ProvideFoo: Foo` is not satisfied - --> tests/acceptable/check_components/dependency_cascade.rs:85:9 - | -85 | BarComponent, - | ^^^^^^^^^^^^ unsatisfied trait bound - | -help: the trait `Foo` is not implemented for `ProvideFoo` - --> tests/acceptable/check_components/dependency_cascade.rs:40:16 - | -40 | #[cgp_impl(new ProvideFoo)] - | ^^^^^^^^^^ -help: the trait `Foo<__Context__>` is implemented for `ProvideFoo` - --> tests/acceptable/check_components/dependency_cascade.rs:41:1 - | -41 | / impl Foo -42 | | where -43 | | Self: HasName, - | |__________________^ -note: required for `App` to implement `Foo` - --> tests/acceptable/check_components/dependency_cascade.rs:25:1 - | -25 | #[cgp_component(Foo)] - | ^^^^^^^^^^^^^^^^^^^^^ -note: required for `App` to implement `CanFoo` - --> tests/acceptable/check_components/dependency_cascade.rs:25:1 - | -25 | #[cgp_component(Foo)] - | ^^^^^^^^^^^^^^^^^^^^^ -26 | pub trait CanFoo { - | ^^^^^^ -note: required for `ProvideBar` to implement `IsProviderFor` - --> tests/acceptable/check_components/dependency_cascade.rs:50:1 - | -50 | #[cgp_impl(new ProvideBar)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -51 | #[uses(CanFoo)] - | ------ unsatisfied trait bound introduced here - = note: required for `App` to implement `CanUseComponent` -note: required by a bound in `__CheckApp` - --> tests/acceptable/check_components/dependency_cascade.rs:82:1 - | -82 | / check_components! { -83 | | App { -84 | | BazComponent, -85 | | BarComponent, -... | -88 | | } - | |_^ required by this bound in `__CheckApp` - = note: this error originates in the attribute macro `cgp_component` which comes from the expansion of the macro `check_components` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: the trait bound `App: CanUseComponent` is not satisfied - --> tests/acceptable/check_components/dependency_cascade.rs:85:9 - | -85 | BarComponent, - | ^^^^^^^^^^^^ unsatisfied trait bound - | -help: the trait `HasField>>>>>` is not implemented for `App` - but trait `HasField>>>>` is implemented for it - --> tests/acceptable/check_components/dependency_cascade.rs:66:10 - | -66 | #[derive(HasField)] - | ^^^^^^^^ -note: required for `App` to implement `HasName` - --> tests/acceptable/check_components/dependency_cascade.rs:20:1 - | -20 | #[cgp_auto_getter] - | ^^^^^^^^^^^^^^^^^^ -21 | pub trait HasName { - | ^^^^^^^ -note: required for `ProvideFoo` to implement `IsProviderFor` - --> tests/acceptable/check_components/dependency_cascade.rs:40:1 - | -40 | #[cgp_impl(new ProvideFoo)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -... -43 | Self: HasName, - | ------- unsatisfied trait bound introduced here - = note: 1 redundant requirement hidden - = note: required for `App` to implement `IsProviderFor` -note: required for `App` to implement `Foo` - --> tests/acceptable/check_components/dependency_cascade.rs:25:1 - | -25 | #[cgp_component(Foo)] - | ^^^^^^^^^^^^^^^^^^^^^ -note: required for `App` to implement `CanFoo` - --> tests/acceptable/check_components/dependency_cascade.rs:25:1 - | -25 | #[cgp_component(Foo)] - | ^^^^^^^^^^^^^^^^^^^^^ -26 | pub trait CanFoo { - | ^^^^^^ - = note: 1 redundant requirement hidden - = note: required for `ProvideBar` to implement `IsProviderFor` - = note: required for `App` to implement `CanUseComponent` -note: required by a bound in `__CheckApp` - --> tests/acceptable/check_components/dependency_cascade.rs:82:1 - | -82 | / check_components! { -83 | | App { -84 | | BazComponent, -85 | | BarComponent, -... | -88 | | } - | |_^ required by this bound in `__CheckApp` - = note: this error originates in the derive macro `HasField` which comes from the expansion of the macro `check_components` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: the trait bound `App: CanUseComponent` is not satisfied - --> tests/acceptable/check_components/dependency_cascade.rs:86:9 - | -86 | FooComponent, - | ^^^^^^^^^^^^ unsatisfied trait bound - | -help: the trait `HasField>>>>>` is not implemented for `App` - but trait `HasField>>>>` is implemented for it - --> tests/acceptable/check_components/dependency_cascade.rs:66:10 - | -66 | #[derive(HasField)] - | ^^^^^^^^ -note: required for `App` to implement `HasName` - --> tests/acceptable/check_components/dependency_cascade.rs:20:1 - | -20 | #[cgp_auto_getter] - | ^^^^^^^^^^^^^^^^^^ -21 | pub trait HasName { - | ^^^^^^^ -note: required for `ProvideFoo` to implement `IsProviderFor` - --> tests/acceptable/check_components/dependency_cascade.rs:40:1 - | -40 | #[cgp_impl(new ProvideFoo)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -... -43 | Self: HasName, - | ------- unsatisfied trait bound introduced here - = note: required for `App` to implement `CanUseComponent` -note: required by a bound in `__CheckApp` - --> tests/acceptable/check_components/dependency_cascade.rs:82:1 - | -82 | / check_components! { -83 | | App { -84 | | BazComponent, -85 | | BarComponent, -... | -88 | | } - | |_^ required by this bound in `__CheckApp` - = note: this error originates in the derive macro `HasField` which comes from the expansion of the macro `check_components` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/generic_context_ordinary_bound.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/generic_context_ordinary_bound.rs deleted file mode 100644 index ed1648f8..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/generic_context_ordinary_bound.rs +++ /dev/null @@ -1,54 +0,0 @@ -//! Acceptable failure: the ordinary-trait-bound dependency reached through *impl -//! generics* in `delegate_components!`, rather than a concrete context. -//! -//! A generic context ` Wrapper` wires its abstract `Scalar` type to the impl -//! generic `T` (`ScalarTypeProviderComponent: UseType`), and `CompareScalars` -//! needs `Scalar: Eq` — i.e. `T: Eq`. The generic wiring is accepted unconditionally; -//! the bound only bites at a concrete instantiation. Checking `Wrapper` surfaces -//! `f64: Eq` unsatisfied through `IsProviderFor>`, -//! exactly as the concrete-context case does — showing the ordinary-trait-bound class -//! arises anywhere impl generics carry a bound, including a generic -//! `delegate_components!` table checked at one instantiation. -//! -//! See docs/errors/checks/ordinary-trait-bound.md. - -use cgp::prelude::*; - -#[cgp_type] -pub trait HasScalarType { - type Scalar; -} - -#[cgp_component(ScalarEquality)] -#[use_type(HasScalarType.Scalar)] -pub trait CanCompareScalars { - fn scalars_equal(&self, a: &Scalar, b: &Scalar) -> bool; -} - -#[cgp_impl(new CompareScalars)] -#[use_type(HasScalarType.Scalar)] -impl ScalarEquality -where - Scalar: Eq, -{ - fn scalars_equal(&self, a: &Scalar, b: &Scalar) -> bool { - a == b - } -} - -pub struct Wrapper(pub T); - -delegate_components! { - Wrapper { - ScalarTypeProviderComponent: UseType, - ScalarEqualityComponent: CompareScalars, - } -} - -check_components! { - Wrapper { - ScalarEqualityComponent, - } -} - -fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/generic_context_ordinary_bound.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/generic_context_ordinary_bound.stderr deleted file mode 100644 index c2266a33..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/generic_context_ordinary_bound.stderr +++ /dev/null @@ -1,35 +0,0 @@ -error[E0277]: the trait bound `f64: Eq` is not satisfied - --> tests/acceptable/check_components/generic_context_ordinary_bound.rs:50:9 - | -50 | ScalarEqualityComponent, - | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `Eq` is not implemented for `f64` - | - = help: the following other types implement trait `Eq`: - i128 - i16 - i32 - i64 - i8 - isize - u128 - u16 - and $N others -note: required for `CompareScalars` to implement `IsProviderFor>` - --> tests/acceptable/check_components/generic_context_ordinary_bound.rs:28:1 - | -28 | #[cgp_impl(new CompareScalars)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -... -32 | Scalar: Eq, - | -- unsatisfied trait bound introduced here - = note: required for `Wrapper` to implement `CanUseComponent` -note: required by a bound in `__CheckWrapper` - --> tests/acceptable/check_components/generic_context_ordinary_bound.rs:48:1 - | -48 | / check_components! { -49 | | Wrapper { -50 | | ScalarEqualityComponent, -51 | | } -52 | | } - | |_^ required by this bound in `__CheckWrapper` - = note: this error originates in the attribute macro `cgp_impl` which comes from the expansion of the macro `check_components` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/higher_order_inner_dependency.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/higher_order_inner_dependency.rs deleted file mode 100644 index c73c2a30..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/higher_order_inner_dependency.rs +++ /dev/null @@ -1,79 +0,0 @@ -//! Acceptable failure: a higher-order provider whose *inner* layer carries the -//! unmet dependency. `ScaledArea` delegates to an inner `AreaCalculator` -//! and adds its own `Self: HasScaleFactor` dependency; `BaseArea` is that inner -//! provider and needs `Self: HasBaseArea`. `Rectangle` supplies `scale_factor` -//! but not `base_area`, so the *outer* layer's dependency holds and the *inner* -//! layer's fails. -//! -//! This fixture pins where the diagnostic locates the failing layer: the -//! `unsatisfied trait bound introduced here` caret lands on `BaseArea`'s -//! `Self: HasBaseArea` clause (the inner provider), and the `required for …` chain -//! runs *through* `ScaledArea`'s `IsProviderFor` before reaching -//! `CanUseComponent`, so the outer wrapper appears in the chain even though its own -//! bound is satisfied. Contrast higher_order_outer_dependency.rs, whose caret lands -//! on `ScaledArea`'s own clause and whose chain never reaches the inner provider — -//! the two failures are structurally similar but point at different layers, which -//! is why `#[check_providers(...)]` exists to assert `IsProviderFor` per layer. -//! This is the check doing its job, not a macro defect. -//! -//! See docs/errors/checks/higher-order-provider-layer.md. - -use cgp::prelude::*; - -#[cgp_component(AreaCalculator)] -pub trait CanCalculateArea { - fn area(&self) -> f64; -} - -#[cgp_auto_getter] -pub trait HasBaseArea { - fn base_area(&self) -> f64; -} - -#[cgp_auto_getter] -pub trait HasScaleFactor { - fn scale_factor(&self) -> f64; -} - -#[cgp_impl(new BaseArea)] -impl AreaCalculator -where - Self: HasBaseArea, -{ - fn area(&self) -> f64 { - self.base_area() - } -} - -#[cgp_impl(new ScaledArea)] -#[use_provider(Inner: AreaCalculator)] -impl AreaCalculator -where - Self: HasScaleFactor, -{ - fn area(&self) -> f64 { - self.scale_factor() * Inner::area(self) - } -} - -#[derive(HasField)] -pub struct Rectangle { - pub scale_factor: f64, - // missing `base_area`, so the inner `BaseArea` layer fails -} - -delegate_components! { - Rectangle { - AreaCalculatorComponent: ScaledArea, - } -} - -// Fails in the inner `BaseArea` layer: `Rectangle` has `scale_factor` but not -// `base_area`, so `ScaledArea`'s own dependency holds and `BaseArea`'s does not. -check_components! { - Rectangle { - AreaCalculatorComponent, - } -} - -fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/higher_order_inner_dependency.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/higher_order_inner_dependency.stderr deleted file mode 100644 index 63111d57..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/higher_order_inner_dependency.stderr +++ /dev/null @@ -1,75 +0,0 @@ -error[E0277]: the trait bound `BaseArea: AreaCalculator` is not satisfied - --> tests/acceptable/check_components/higher_order_inner_dependency.rs:75:9 - | -75 | AreaCalculatorComponent, - | ^^^^^^^^^^^^^^^^^^^^^^^ unsatisfied trait bound - | -help: the trait `AreaCalculator` is not implemented for `BaseArea` - --> tests/acceptable/check_components/higher_order_inner_dependency.rs:38:16 - | -38 | #[cgp_impl(new BaseArea)] - | ^^^^^^^^ -help: the trait `AreaCalculator<__Context__>` is implemented for `BaseArea` - --> tests/acceptable/check_components/higher_order_inner_dependency.rs:39:1 - | -39 | / impl AreaCalculator -40 | | where -41 | | Self: HasBaseArea, - | |______________________^ -note: required for `ScaledArea` to implement `IsProviderFor` - --> tests/acceptable/check_components/higher_order_inner_dependency.rs:48:1 - | -48 | #[cgp_impl(new ScaledArea)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - = note: required for `Rectangle` to implement `CanUseComponent` -note: required by a bound in `__CheckRectangle` - --> tests/acceptable/check_components/higher_order_inner_dependency.rs:73:1 - | -73 | / check_components! { -74 | | Rectangle { -75 | | AreaCalculatorComponent, -76 | | } -77 | | } - | |_^ required by this bound in `__CheckRectangle` - = note: this error originates in the attribute macro `cgp_impl` which comes from the expansion of the macro `check_components` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: the trait bound `Rectangle: CanUseComponent` is not satisfied - --> tests/acceptable/check_components/higher_order_inner_dependency.rs:75:9 - | -75 | AreaCalculatorComponent, - | ^^^^^^^^^^^^^^^^^^^^^^^ unsatisfied trait bound - | -help: the trait `HasField>>>>>>>>>>` is not implemented for `Rectangle` - but trait `HasField>>>>>>>>>>>>>` is implemented for it - --> tests/acceptable/check_components/higher_order_inner_dependency.rs:59:10 - | -59 | #[derive(HasField)] - | ^^^^^^^^ -note: required for `Rectangle` to implement `HasBaseArea` - --> tests/acceptable/check_components/higher_order_inner_dependency.rs:28:1 - | -28 | #[cgp_auto_getter] - | ^^^^^^^^^^^^^^^^^^ -29 | pub trait HasBaseArea { - | ^^^^^^^^^^^ -note: required for `BaseArea` to implement `IsProviderFor` - --> tests/acceptable/check_components/higher_order_inner_dependency.rs:38:1 - | -38 | #[cgp_impl(new BaseArea)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^ -... -41 | Self: HasBaseArea, - | ----------- unsatisfied trait bound introduced here - = note: 1 redundant requirement hidden - = note: required for `ScaledArea` to implement `IsProviderFor` - = note: required for `Rectangle` to implement `CanUseComponent` -note: required by a bound in `__CheckRectangle` - --> tests/acceptable/check_components/higher_order_inner_dependency.rs:73:1 - | -73 | / check_components! { -74 | | Rectangle { -75 | | AreaCalculatorComponent, -76 | | } -77 | | } - | |_^ required by this bound in `__CheckRectangle` - = note: this error originates in the derive macro `HasField` which comes from the expansion of the macro `check_components` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/higher_order_outer_dependency.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/higher_order_outer_dependency.rs deleted file mode 100644 index c8180b6f..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/higher_order_outer_dependency.rs +++ /dev/null @@ -1,77 +0,0 @@ -//! Acceptable failure: the mirror of higher_order_inner_dependency.rs, where the -//! *outer* layer of the same higher-order provider carries the unmet dependency. -//! `ScaledArea` is wired again, but now `Rectangle` supplies `base_area` -//! and not `scale_factor`, so the *inner* `BaseArea` layer would succeed and the -//! *outer* `ScaledArea` layer fails on its own `Self: HasScaleFactor`. -//! -//! This fixture pins the contrast with the inner-failure case: the `unsatisfied -//! trait bound introduced here` caret lands on `ScaledArea`'s own -//! `Self: HasScaleFactor` clause, and the `required for …` chain is *shorter* — it -//! reaches `ScaledArea`'s `IsProviderFor` and stops, never descending -//! into `BaseArea`, because the outer layer fails before it ever delegates inward. -//! Read alongside higher_order_inner_dependency.rs, the pair shows that the layer -//! at fault is identified by which provider's `where` clause the caret sits on and -//! how deep the chain runs. This is the check doing its job, not a macro defect. -//! -//! See docs/errors/checks/higher-order-provider-layer.md. - -use cgp::prelude::*; - -#[cgp_component(AreaCalculator)] -pub trait CanCalculateArea { - fn area(&self) -> f64; -} - -#[cgp_auto_getter] -pub trait HasBaseArea { - fn base_area(&self) -> f64; -} - -#[cgp_auto_getter] -pub trait HasScaleFactor { - fn scale_factor(&self) -> f64; -} - -#[cgp_impl(new BaseArea)] -impl AreaCalculator -where - Self: HasBaseArea, -{ - fn area(&self) -> f64 { - self.base_area() - } -} - -#[cgp_impl(new ScaledArea)] -#[use_provider(Inner: AreaCalculator)] -impl AreaCalculator -where - Self: HasScaleFactor, -{ - fn area(&self) -> f64 { - self.scale_factor() * Inner::area(self) - } -} - -#[derive(HasField)] -pub struct Rectangle { - pub base_area: f64, - // missing `scale_factor`, so the outer `ScaledArea` layer fails -} - -delegate_components! { - Rectangle { - AreaCalculatorComponent: ScaledArea, - } -} - -// Fails in the outer `ScaledArea` layer: `Rectangle` has `base_area` but not -// `scale_factor`, so the inner `BaseArea` dependency holds and `ScaledArea`'s does -// not. -check_components! { - Rectangle { - AreaCalculatorComponent, - } -} - -fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/higher_order_outer_dependency.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/higher_order_outer_dependency.stderr deleted file mode 100644 index 370ea6ed..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/higher_order_outer_dependency.stderr +++ /dev/null @@ -1,38 +0,0 @@ -error[E0277]: the trait bound `Rectangle: CanUseComponent` is not satisfied - --> tests/acceptable/check_components/higher_order_outer_dependency.rs:73:9 - | -73 | AreaCalculatorComponent, - | ^^^^^^^^^^^^^^^^^^^^^^^ unsatisfied trait bound - | -help: the trait `HasField>>>>>>>>>>>>>` is not implemented for `Rectangle` - but trait `HasField>>>>>>>>>>` is implemented for it - --> tests/acceptable/check_components/higher_order_outer_dependency.rs:56:10 - | -56 | #[derive(HasField)] - | ^^^^^^^^ -note: required for `Rectangle` to implement `HasScaleFactor` - --> tests/acceptable/check_components/higher_order_outer_dependency.rs:30:1 - | -30 | #[cgp_auto_getter] - | ^^^^^^^^^^^^^^^^^^ -31 | pub trait HasScaleFactor { - | ^^^^^^^^^^^^^^ -note: required for `ScaledArea` to implement `IsProviderFor` - --> tests/acceptable/check_components/higher_order_outer_dependency.rs:45:1 - | -45 | #[cgp_impl(new ScaledArea)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -... -49 | Self: HasScaleFactor, - | -------------- unsatisfied trait bound introduced here - = note: required for `Rectangle` to implement `CanUseComponent` -note: required by a bound in `__CheckRectangle` - --> tests/acceptable/check_components/higher_order_outer_dependency.rs:71:1 - | -71 | / check_components! { -72 | | Rectangle { -73 | | AreaCalculatorComponent, -74 | | } -75 | | } - | |_^ required by this bound in `__CheckRectangle` - = note: this error originates in the derive macro `HasField` which comes from the expansion of the macro `check_components` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_dependency.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_dependency.rs deleted file mode 100644 index 0e9f772f..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_dependency.rs +++ /dev/null @@ -1,59 +0,0 @@ -//! Acceptable failure: `GreetHello` carries the impl-side dependency -//! `Self: HasName`, but `Person` has no `name` field, so it cannot satisfy it. -//! `check_components!` exists precisely to surface this at the wiring site rather -//! than lazily at the call site (contrast -//! acceptable/delegate_components/missing_dependency.rs, which leaves the same -//! wiring unchecked and hits the error only when `greet` is called). The failure -//! is the check doing its job, not a macro defect. -//! -//! This fixture pins the `check_components!` error span. The unsatisfied-bound -//! caret falls on `GreeterComponent` inside the `check_components!` block, not on -//! the `Person` context type, because the check impl re-spans the shared context -//! token onto each listed component in turn with `override_span` (see -//! cgp-macro-core/src/types/check_components/table.rs). A regression that dropped -//! that re-span would report the error on the single `Person` token shared by -//! every checked component instead of on the component that actually fails. -//! -//! See docs/errors/checks/check-trait-failure.md; span mechanics: check_components.md. - -use cgp::prelude::*; - -#[cgp_component(Greeter)] -pub trait CanGreet { - fn greet(&self); -} - -#[cgp_auto_getter] -pub trait HasName { - fn name(&self) -> &str; -} - -#[cgp_impl(new GreetHello)] -impl Greeter -where - Self: HasName, -{ - fn greet(&self) { - let _ = self.name(); - } -} - -#[derive(HasField)] -pub struct Person { - pub age: u8, -} - -delegate_components! { - Person { - GreeterComponent: GreetHello, - } -} - -// `Person` cannot satisfy `GreetHello`'s `Self: HasName`, so the check fails here. -check_components! { - Person { - GreeterComponent, - } -} - -fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_dependency.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_dependency.stderr deleted file mode 100644 index 75cb10af..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_dependency.stderr +++ /dev/null @@ -1,38 +0,0 @@ -error[E0277]: the trait bound `Person: CanUseComponent` is not satisfied - --> tests/acceptable/check_components/missing_dependency.rs:55:9 - | -55 | GreeterComponent, - | ^^^^^^^^^^^^^^^^ unsatisfied trait bound - | -help: the trait `HasField>>>>>` is not implemented for `Person` - but trait `HasField>>>>` is implemented for it - --> tests/acceptable/check_components/missing_dependency.rs:41:10 - | -41 | #[derive(HasField)] - | ^^^^^^^^ -note: required for `Person` to implement `HasName` - --> tests/acceptable/check_components/missing_dependency.rs:26:1 - | -26 | #[cgp_auto_getter] - | ^^^^^^^^^^^^^^^^^^ -27 | pub trait HasName { - | ^^^^^^^ -note: required for `GreetHello` to implement `IsProviderFor` - --> tests/acceptable/check_components/missing_dependency.rs:31:1 - | -31 | #[cgp_impl(new GreetHello)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -... -34 | Self: HasName, - | ------- unsatisfied trait bound introduced here - = note: required for `Person` to implement `CanUseComponent` -note: required by a bound in `__CheckPerson` - --> tests/acceptable/check_components/missing_dependency.rs:53:1 - | -53 | / check_components! { -54 | | Person { -55 | | GreeterComponent, -56 | | } -57 | | } - | |_^ required by this bound in `__CheckPerson` - = note: this error originates in the derive macro `HasField` which comes from the expansion of the macro `check_components` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_has_field_derive.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_has_field_derive.rs deleted file mode 100644 index c06d3830..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_has_field_derive.rs +++ /dev/null @@ -1,62 +0,0 @@ -//! Acceptable failure: a variant of check_components/missing_dependency.rs where -//! the mistake is not a missing *field* but a missing `#[derive(HasField)]` -//! altogether. `GreetHello` needs `Self: HasName`, and `Person` even has a `name` -//! field — but without the derive, `Person` has *no* `HasField` impls at all, so -//! `HasName` is unsatisfiable and the check fails. -//! -//! This fixture pins the diagnostic that tells this case apart from a single -//! missing field: the `help:` note names `HasField` as -//! unimplemented for `Person` and points at the `Person` struct, but — unlike -//! missing_dependency.rs, where a derived `age` field supplies a "but trait -//! `HasField` is implemented for it" landmark — there is no such -//! landmark here, because `Person` implements the trait for no field. The absence -//! of the landmark is the signal that the whole derive is missing and the fix is -//! to add `#[derive(HasField)]`, not to add a field. This is the check doing its -//! job, not a macro defect. -//! -//! See docs/errors/checks/check-trait-failure.md (the "when the derive is missing -//! entirely" variant). - -use cgp::prelude::*; - -#[cgp_component(Greeter)] -pub trait CanGreet { - fn greet(&self); -} - -#[cgp_auto_getter] -pub trait HasName { - fn name(&self) -> &str; -} - -#[cgp_impl(new GreetHello)] -impl Greeter -where - Self: HasName, -{ - fn greet(&self) { - let _ = self.name(); - } -} - -// The `name` field exists, but without `#[derive(HasField)]` there is no -// `HasField` impl, so `Person` cannot implement `HasName`. -pub struct Person { - pub name: String, -} - -delegate_components! { - Person { - GreeterComponent: GreetHello, - } -} - -// Fails because `Person` has no `HasField` impls at all, not because it lacks the -// `name` field — the fix is the missing derive. -check_components! { - Person { - GreeterComponent, - } -} - -fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_has_field_derive.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_has_field_derive.stderr deleted file mode 100644 index ff3852d6..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_has_field_derive.stderr +++ /dev/null @@ -1,37 +0,0 @@ -error[E0277]: the trait bound `Person: CanUseComponent` is not satisfied - --> tests/acceptable/check_components/missing_has_field_derive.rs:58:9 - | -58 | GreeterComponent, - | ^^^^^^^^^^^^^^^^ unsatisfied trait bound - | -help: the trait `cgp::prelude::HasField>>>>>` is not implemented for `Person` - --> tests/acceptable/check_components/missing_has_field_derive.rs:44:1 - | -44 | pub struct Person { - | ^^^^^^^^^^^^^^^^^ -note: required for `Person` to implement `HasName` - --> tests/acceptable/check_components/missing_has_field_derive.rs:27:1 - | -27 | #[cgp_auto_getter] - | ^^^^^^^^^^^^^^^^^^ -28 | pub trait HasName { - | ^^^^^^^ -note: required for `GreetHello` to implement `IsProviderFor` - --> tests/acceptable/check_components/missing_has_field_derive.rs:32:1 - | -32 | #[cgp_impl(new GreetHello)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -... -35 | Self: HasName, - | ------- unsatisfied trait bound introduced here - = note: required for `Person` to implement `CanUseComponent` -note: required by a bound in `__CheckPerson` - --> tests/acceptable/check_components/missing_has_field_derive.rs:56:1 - | -56 | / check_components! { -57 | | Person { -58 | | GreeterComponent, -59 | | } -60 | | } - | |_^ required by this bound in `__CheckPerson` - = note: this error originates in the attribute macro `cgp_auto_getter` which comes from the expansion of the macro `check_components` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/ordinary_bound_unsatisfied.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/ordinary_bound_unsatisfied.rs deleted file mode 100644 index 5dcd8619..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/ordinary_bound_unsatisfied.rs +++ /dev/null @@ -1,63 +0,0 @@ -//! Acceptable failure: a provider's impl-side dependency is an *ordinary Rust -//! trait bound* — a standard trait (`Eq`), not a CGP capability — on an abstract -//! type, and the concrete type the context wires for that abstract type does not -//! implement it. -//! -//! `CompareScalars` requires `Scalar: Eq` (rewritten by `#[use_type]` to -//! `::Scalar: Eq`). `App` wires its `Scalar` type to `f64`, -//! which is `PartialEq` but not `Eq`, so the dependency is unmet. The wiring is -//! accepted lazily; forcing it through `check_components!` surfaces the failure via -//! `IsProviderFor` as `E0277` — but unlike a missing `HasField` (whose leaf sits in -//! a `help:` note under a `CanUseComponent` primary), the *primary* error names the -//! ordinary bound on the concrete type directly (`f64: Eq` is not satisfied), the -//! `help:` lists the standard types that *do* implement `Eq`, and the `IsProviderFor` -//! note points at the `Scalar: Eq` bound as "introduced here". The fix is to satisfy -//! the ordinary trait (wire an `Eq` type such as an integer, or derive/impl `Eq`), -//! not to wire a component or add a field. -//! -//! CGP lowers the bound faithfully and cannot see the wired type violates it, so it -//! defers to the compiler. This is the same lazy-wiring mechanism as a CGP-capability -//! dependency; only the *kind of leaf* (an ordinary trait) and the fix differ. -//! -//! See docs/errors/checks/ordinary-trait-bound.md. - -use cgp::prelude::*; - -#[cgp_type] -pub trait HasScalarType { - type Scalar; -} - -#[cgp_component(ScalarEquality)] -#[use_type(HasScalarType.Scalar)] -pub trait CanCompareScalars { - fn scalars_equal(&self, a: &Scalar, b: &Scalar) -> bool; -} - -#[cgp_impl(new CompareScalars)] -#[use_type(HasScalarType.Scalar)] -impl ScalarEquality -where - Scalar: Eq, -{ - fn scalars_equal(&self, a: &Scalar, b: &Scalar) -> bool { - a == b - } -} - -pub struct App; - -delegate_components! { - App { - ScalarTypeProviderComponent: UseType, - ScalarEqualityComponent: CompareScalars, - } -} - -check_components! { - App { - ScalarEqualityComponent, - } -} - -fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/ordinary_bound_unsatisfied.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/ordinary_bound_unsatisfied.stderr deleted file mode 100644 index 6496051a..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/ordinary_bound_unsatisfied.stderr +++ /dev/null @@ -1,35 +0,0 @@ -error[E0277]: the trait bound `f64: Eq` is not satisfied - --> tests/acceptable/check_components/ordinary_bound_unsatisfied.rs:59:9 - | -59 | ScalarEqualityComponent, - | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `Eq` is not implemented for `f64` - | - = help: the following other types implement trait `Eq`: - i128 - i16 - i32 - i64 - i8 - isize - u128 - u16 - and $N others -note: required for `CompareScalars` to implement `IsProviderFor` - --> tests/acceptable/check_components/ordinary_bound_unsatisfied.rs:37:1 - | -37 | #[cgp_impl(new CompareScalars)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -... -41 | Scalar: Eq, - | -- unsatisfied trait bound introduced here - = note: required for `App` to implement `CanUseComponent` -note: required by a bound in `__CheckApp` - --> tests/acceptable/check_components/ordinary_bound_unsatisfied.rs:57:1 - | -57 | / check_components! { -58 | | App { -59 | | ScalarEqualityComponent, -60 | | } -61 | | } - | |_^ required by this bound in `__CheckApp` - = note: this error originates in the attribute macro `cgp_impl` which comes from the expansion of the macro `check_components` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_key.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_key.rs deleted file mode 100644 index cf5f1aab..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_key.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! Acceptable failure: two `delegate_components!` blocks that map the same -//! component key produce two conflicting `DelegateComponent` -//! impls for `Person`, which the Rust compiler rejects with the coherence error -//! E0119. `delegate_components!` cannot catch this — it lowers each block -//! independently and has no view of the other block — so it correctly defers to -//! the compiler, exactly as two hand-written overlapping impls would. -//! -//! See docs/errors/wiring/conflicting-wiring.md. - -use cgp::prelude::*; - -#[cgp_component(Greeter)] -pub trait CanGreet { - fn greet(&self); -} - -#[cgp_impl(new GreetHello)] -impl Greeter { - fn greet(&self) {} -} - -#[cgp_impl(new GreetGoodbye)] -impl Greeter { - fn greet(&self) {} -} - -pub struct Person; - -delegate_components! { - Person { - GreeterComponent: GreetHello, - } -} - -// Re-delegating the same key in a second block emits a conflicting impl. -delegate_components! { - Person { - GreeterComponent: GreetGoodbye, - } -} - -fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_key.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_key.stderr deleted file mode 100644 index 63fc344c..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_key.stderr +++ /dev/null @@ -1,17 +0,0 @@ -error[E0119]: conflicting implementations of trait `IsProviderFor` for type `Person` - --> tests/acceptable/delegate_components/duplicate_key.rs:38:9 - | -31 | GreeterComponent: GreetHello, - | ---------------- first implementation here -... -38 | GreeterComponent: GreetGoodbye, - | ^^^^^^^^^^^^^^^^ conflicting implementation for `Person` - -error[E0119]: conflicting implementations of trait `DelegateComponent` for type `Person` - --> tests/acceptable/delegate_components/duplicate_key.rs:38:9 - | -31 | GreeterComponent: GreetHello, - | ---------------- first implementation here -... -38 | GreeterComponent: GreetGoodbye, - | ^^^^^^^^^^^^^^^^ conflicting implementation for `Person` diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_key_same_block.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_key_same_block.rs deleted file mode 100644 index 58282ee5..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_key_same_block.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! Acceptable failure: two entries in a *single* `delegate_components!` block -//! that map the same component key produce two conflicting -//! `DelegateComponent` impls for `Person`, rejected with the -//! coherence error E0119 — the same failure as [duplicate_key.rs], reached with -//! one block instead of two. -//! -//! This fixture exists to pin the **error span**: each conflicting entry lowers -//! to an impl re-spanned onto its own key, so E0119 points at the two distinct -//! `GreeterComponent` tokens (the "first implementation here" note lands on the -//! first entry, the conflict caret on the second) rather than at the whole -//! block. If the per-entry re-spanning in `mapping/eval.rs` regresses, both -//! carets snap back to the macro invocation and this `.stderr` changes. -//! -//! See docs/errors/wiring/conflicting-wiring.md; error-span mechanics in docs/implementation/entrypoints/delegate_components.md. - -use cgp::prelude::*; - -#[cgp_component(Greeter)] -pub trait CanGreet { - fn greet(&self); -} - -#[cgp_impl(new GreetHello)] -impl Greeter { - fn greet(&self) {} -} - -#[cgp_impl(new GreetGoodbye)] -impl Greeter { - fn greet(&self) {} -} - -pub struct Person; - -delegate_components! { - Person { - GreeterComponent: GreetHello, - GreeterComponent: GreetGoodbye, - } -} - -fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_key_same_block.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_key_same_block.stderr deleted file mode 100644 index 4cddc5d3..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_key_same_block.stderr +++ /dev/null @@ -1,15 +0,0 @@ -error[E0119]: conflicting implementations of trait `IsProviderFor` for type `Person` - --> tests/acceptable/delegate_components/duplicate_key_same_block.rs:38:9 - | -37 | GreeterComponent: GreetHello, - | ---------------- first implementation here -38 | GreeterComponent: GreetGoodbye, - | ^^^^^^^^^^^^^^^^ conflicting implementation for `Person` - -error[E0119]: conflicting implementations of trait `DelegateComponent` for type `Person` - --> tests/acceptable/delegate_components/duplicate_key_same_block.rs:38:9 - | -37 | GreeterComponent: GreetHello, - | ---------------- first implementation here -38 | GreeterComponent: GreetGoodbye, - | ^^^^^^^^^^^^^^^^ conflicting implementation for `Person` diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_open_key.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_open_key.rs deleted file mode 100644 index cf3f7373..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_open_key.rs +++ /dev/null @@ -1,39 +0,0 @@ -//! Acceptable failure: an `open` header and an explicit mapping that both wire -//! the same component. `open GreeterComponent;` emits a `DelegateComponent< -//! GreeterComponent>` redirect impl, and the following `GreeterComponent: -//! GreetHello` emits another, so they conflict with the coherence error E0119 — -//! the third duplicate-key shape named in the Failure modes doc, alongside -//! [duplicate_key.rs] (two blocks) and [duplicate_key_same_block.rs] (two plain -//! entries). -//! -//! This fixture pins the **error span** for the `open`-header entry, whose span -//! is sourced in `statement/open.rs` from the opened component (a distinct source -//! from the plain key path): the "first implementation here" note lands on the -//! `GreeterComponent` inside `open …;`, and the conflict caret on the explicit -//! mapping's key. -//! -//! See docs/errors/wiring/conflicting-wiring.md; error-span mechanics in docs/implementation/entrypoints/delegate_components.md. - -use cgp::prelude::*; - -#[cgp_component(Greeter)] -pub trait CanGreet { - fn greet(&self); -} - -#[cgp_impl(new GreetHello)] -impl Greeter { - fn greet(&self) {} -} - -pub struct Person; - -delegate_components! { - Person { - open GreeterComponent; - - GreeterComponent: GreetHello, - } -} - -fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_open_key.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_open_key.stderr deleted file mode 100644 index bd7f1429..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_open_key.stderr +++ /dev/null @@ -1,19 +0,0 @@ -error[E0119]: conflicting implementations of trait `IsProviderFor` for type `Person` - --> tests/acceptable/delegate_components/duplicate_open_key.rs:35:9 - | -33 | open GreeterComponent; - | ---------------- first implementation here -34 | -35 | GreeterComponent: GreetHello, - | ^^^^^^^^^^^^^^^^ conflicting implementation for `Person` - | - = note: downstream crates may implement trait `cgp::prelude::IsProviderFor` for type `cgp::prelude::RedirectLookup>` - -error[E0119]: conflicting implementations of trait `DelegateComponent` for type `Person` - --> tests/acceptable/delegate_components/duplicate_open_key.rs:35:9 - | -33 | open GreeterComponent; - | ---------------- first implementation here -34 | -35 | GreeterComponent: GreetHello, - | ^^^^^^^^^^^^^^^^ conflicting implementation for `Person` diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_path_key.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_path_key.rs deleted file mode 100644 index 5d8b4799..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_path_key.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! Acceptable failure: two `@`-path entries in one `delegate_components!` block -//! that map the same namespace path produce two conflicting `DelegateComponent` -//! impls (keyed by the same `PathCons<..>` type) for `App`, rejected with the -//! coherence error E0119 — the `@`-path analogue of [duplicate_key_same_block.rs]. -//! -//! This fixture pins the **error span** for an `@`-path key. The key type is a -//! synthesized `PathCons<..>` nest whose own span points at the macro -//! `call_site`; the entry instead carries the span of the path segments the user -//! wrote, so E0119 lands on the duplicated `ErrorTypeProviderComponent` segment -//! (the path's leaf) rather than on the whole block. If the path-key span -//! threading in `key/path.rs` regresses, the caret snaps back to the block and -//! this `.stderr` changes. -//! -//! See docs/errors/wiring/conflicting-wiring.md; error-span mechanics in docs/implementation/entrypoints/delegate_components.md. - -use cgp::core::error::ErrorTypeProviderComponent; -use cgp::prelude::*; - -pub struct App; - -delegate_components! { - App { - namespace DefaultNamespace; - - @cgp.core.error.ErrorTypeProviderComponent: UseType, - @cgp.core.error.ErrorTypeProviderComponent: UseType, - } -} - -fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_path_key.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_path_key.stderr deleted file mode 100644 index f2f6c109..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_path_key.stderr +++ /dev/null @@ -1,17 +0,0 @@ -error[E0119]: conflicting implementations of trait `DelegateComponent>>>, PathCons>>>>, PathCons>>>>>, PathCons>>>>` for type `App` - --> tests/acceptable/delegate_components/duplicate_path_key.rs:26:25 - | -25 | @cgp.core.error.ErrorTypeProviderComponent: UseType, - | -------------------------- first implementation here -26 | @cgp.core.error.ErrorTypeProviderComponent: UseType, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `App` - -error[E0119]: conflicting implementations of trait `IsProviderFor>>>, PathCons>>>>, PathCons>>>>>, PathCons>>>, _, _>` for type `App` - --> tests/acceptable/delegate_components/duplicate_path_key.rs:26:25 - | -25 | @cgp.core.error.ErrorTypeProviderComponent: UseType, - | -------------------------- first implementation here -26 | @cgp.core.error.ErrorTypeProviderComponent: UseType, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `App` - | - = note: downstream crates may implement trait `cgp::prelude::IsProviderFor>>>, cgp::prelude::PathCons>>>>, cgp::prelude::PathCons>>>>>, cgp::prelude::PathCons>>>, _, _>` for type `cgp::prelude::UseType` diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/missing_dependency.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/missing_dependency.rs deleted file mode 100644 index 7d39c88d..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/missing_dependency.rs +++ /dev/null @@ -1,51 +0,0 @@ -//! Acceptable failure: `GreetHello` carries the impl-side dependency -//! `Self: HasName`, but `Person` has no `name` field, so it does not implement -//! `HasName`. CGP wiring is lazy by design — `delegate_components!` accepts the -//! entry without checking the provider's transitive dependencies — so the unmet -//! dependency surfaces only when the consumer trait is finally called, reported -//! by the compiler through `IsProviderFor`. Using `check_components!` (or -//! `delegate_and_check_components!`) would move this same error to the wiring -//! site; deferring it to the use site is the intended lazy behavior, not a -//! macro defect. -//! -//! See docs/errors/hidden/unsatisfied-dependency.md; its surfaced counterpart is -//! docs/errors/checks/check-trait-failure.md. - -use cgp::prelude::*; - -#[cgp_component(Greeter)] -pub trait CanGreet { - fn greet(&self); -} - -#[cgp_auto_getter] -pub trait HasName { - fn name(&self) -> &str; -} - -#[cgp_impl(new GreetHello)] -impl Greeter -where - Self: HasName, -{ - fn greet(&self) { - let _ = self.name(); - } -} - -#[derive(HasField)] -pub struct Person { - pub age: u8, -} - -// Accepted even though `Person` cannot satisfy `GreetHello`'s `Self: HasName`. -delegate_components! { - Person { - GreeterComponent: GreetHello, - } -} - -fn main() { - // The unmet dependency is reported here, at the call site. - Person { age: 0 }.greet(); -} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/missing_dependency.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/missing_dependency.stderr deleted file mode 100644 index 9ed6ef49..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/missing_dependency.stderr +++ /dev/null @@ -1,41 +0,0 @@ -error[E0599]: the method `greet` exists for struct `Person`, but its trait bounds were not satisfied - --> tests/acceptable/delegate_components/missing_dependency.rs:50:23 - | -37 | pub struct Person { - | ----------------- method `greet` not found for this struct because it doesn't satisfy `Person: CanGreet` or `Person: Greeter` -... -50 | Person { age: 0 }.greet(); - | ^^^^^ this is an associated function, not a method - | - = note: found the following associated functions; to be used as methods, functions must have a `self` parameter -note: the candidate is defined in the trait `Greeter` - --> tests/acceptable/delegate_components/missing_dependency.rs:18:5 - | -18 | fn greet(&self); - | ^^^^^^^^^^^^^^^^ -note: trait bound `Person: Greeter` was not satisfied - --> tests/acceptable/delegate_components/missing_dependency.rs:16:1 - | -16 | #[cgp_component(Greeter)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^ -17 | pub trait CanGreet { - | ^^^^^^^^ -note: the trait `Greeter` must be implemented - --> tests/acceptable/delegate_components/missing_dependency.rs:17:1 - | -17 | / pub trait CanGreet { -18 | | fn greet(&self); -19 | | } - | |_^ - = help: items from traits can only be used if the trait is implemented and in scope -note: `CanGreet` defines an item `greet`, perhaps you need to implement it - --> tests/acceptable/delegate_components/missing_dependency.rs:17:1 - | -17 | pub trait CanGreet { - | ^^^^^^^^^^^^^^^^^^ - = note: this error originates in the attribute macro `cgp_component` (in Nightly builds, run with -Z macro-backtrace for more info) -help: use associated function syntax instead - | -50 - Person { age: 0 }.greet(); -50 + Person::greet(); - | diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/ordinary_bound_unsatisfied.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/ordinary_bound_unsatisfied.rs deleted file mode 100644 index 737f4969..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/ordinary_bound_unsatisfied.rs +++ /dev/null @@ -1,55 +0,0 @@ -//! Acceptable failure: the same unmet *ordinary Rust trait bound* dependency as -//! check_components/ordinary_bound_unsatisfied.rs (`Scalar: Eq` with `f64` wired), -//! but exercised by calling the consumer method rather than a check — so the cause -//! is *hidden*. -//! -//! Calling `app.scalars_equal(..)` produces the `E0599` "method exists but its -//! trait bounds were not satisfied" shape: it names `App: CanCompareScalars` / -//! `App: ScalarEquality`, misclassifies the method as an associated function -//! (the provider method has no `self` receiver), and suggests `App::scalars_equal()` -//! — but never mentions the unmet `f64: Eq`. This is byte-for-shape identical to the -//! HasName hidden case in delegate_components/missing_dependency.rs: the compiler's -//! method-probe heuristic drops the nested `where`-clause bound regardless of whether -//! that bound is a `HasField`, a CGP capability, or an ordinary trait. Promote it with -//! `check_components!` to surface the `f64: Eq` cause. -//! -//! See docs/errors/hidden/unsatisfied-dependency.md; the surfaced counterpart is -//! docs/errors/checks/ordinary-trait-bound.md. - -use cgp::prelude::*; - -#[cgp_type] -pub trait HasScalarType { - type Scalar; -} - -#[cgp_component(ScalarEquality)] -#[use_type(HasScalarType.Scalar)] -pub trait CanCompareScalars { - fn scalars_equal(&self, a: &Scalar, b: &Scalar) -> bool; -} - -#[cgp_impl(new CompareScalars)] -#[use_type(HasScalarType.Scalar)] -impl ScalarEquality -where - Scalar: Eq, -{ - fn scalars_equal(&self, a: &Scalar, b: &Scalar) -> bool { - a == b - } -} - -pub struct App; - -delegate_components! { - App { - ScalarTypeProviderComponent: UseType, - ScalarEqualityComponent: CompareScalars, - } -} - -fn main() { - let app = App; - let _ = app.scalars_equal(&1.0, &2.0); -} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/ordinary_bound_unsatisfied.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/ordinary_bound_unsatisfied.stderr deleted file mode 100644 index ee0632d6..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/ordinary_bound_unsatisfied.stderr +++ /dev/null @@ -1,44 +0,0 @@ -error[E0599]: the method `scalars_equal` exists for struct `App`, but its trait bounds were not satisfied - --> tests/acceptable/delegate_components/ordinary_bound_unsatisfied.rs:54:17 - | -43 | pub struct App; - | -------------- method `scalars_equal` not found for this struct because it doesn't satisfy `App: CanCompareScalars` or `App: ScalarEquality` -... -54 | let _ = app.scalars_equal(&1.0, &2.0); - | ^^^^^^^^^^^^^ this is an associated function, not a method - | - = note: found the following associated functions; to be used as methods, functions must have a `self` parameter -note: the candidate is defined in the trait `ScalarEquality` - --> tests/acceptable/delegate_components/ordinary_bound_unsatisfied.rs:29:5 - | -29 | fn scalars_equal(&self, a: &Scalar, b: &Scalar) -> bool; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -note: trait bound `App: ScalarEquality` was not satisfied - --> tests/acceptable/delegate_components/ordinary_bound_unsatisfied.rs:26:1 - | -26 | #[cgp_component(ScalarEquality)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -27 | #[use_type(HasScalarType.Scalar)] -28 | pub trait CanCompareScalars { - | ^^^^^^^^^^^^^^^^^ -note: the trait `ScalarEquality` must be implemented - --> tests/acceptable/delegate_components/ordinary_bound_unsatisfied.rs:28:1 - | -28 | / pub trait CanCompareScalars { -29 | | fn scalars_equal(&self, a: &Scalar, b: &Scalar) -> bool; -30 | | } - | |_^ - = help: items from traits can only be used if the trait is implemented and in scope -note: `CanCompareScalars` defines an item `scalars_equal`, perhaps you need to implement it - --> tests/acceptable/delegate_components/ordinary_bound_unsatisfied.rs:28:1 - | -28 | / pub trait CanCompareScalars { -29 | | fn scalars_equal(&self, a: &Scalar, b: &Scalar) -> bool; -30 | | } - | |_^ - = note: this error originates in the attribute macro `cgp_component` (in Nightly builds, run with -Z macro-backtrace for more info) -help: use associated function syntax instead - | -54 - let _ = app.scalars_equal(&1.0, &2.0); -54 + let _ = App::scalars_equal(&1.0, &2.0); - | diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/overlapping_generic.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/overlapping_generic.rs deleted file mode 100644 index ebe3b845..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/overlapping_generic.rs +++ /dev/null @@ -1,39 +0,0 @@ -//! Acceptable failure: a generic `delegate_components!` entry that wires every -//! `Wrapper` overlaps a second entry that wires the specific `Wrapper`. -//! Stable Rust has no specialization, so the two `DelegateComponent` impls -//! overlap at `Wrapper` and the compiler rejects them with E0119. -//! `delegate_components!` expands each entry to the impl the user asked for and -//! defers the overlap check to the compiler, the same as two overlapping -//! hand-written generic impls. -//! -//! See docs/errors/wiring/conflicting-wiring.md. - -use cgp::prelude::*; - -#[cgp_component(Greeter)] -pub trait CanGreet { - fn greet(&self); -} - -#[cgp_impl(new GreetHello)] -impl Greeter { - fn greet(&self) {} -} - -pub struct Wrapper(pub T); - -// Wires the whole `Wrapper` family. -delegate_components! { - Wrapper { - GreeterComponent: GreetHello, - } -} - -// Overlaps the generic entry at `Wrapper`. -delegate_components! { - Wrapper { - GreeterComponent: GreetHello, - } -} - -fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/overlapping_generic.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/overlapping_generic.stderr deleted file mode 100644 index f728a9f4..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/overlapping_generic.stderr +++ /dev/null @@ -1,17 +0,0 @@ -error[E0119]: conflicting implementations of trait `IsProviderFor` for type `Wrapper` - --> tests/acceptable/delegate_components/overlapping_generic.rs:35:9 - | -28 | GreeterComponent: GreetHello, - | ---------------- first implementation here -... -35 | GreeterComponent: GreetHello, - | ^^^^^^^^^^^^^^^^ conflicting implementation for `Wrapper` - -error[E0119]: conflicting implementations of trait `DelegateComponent` for type `Wrapper` - --> tests/acceptable/delegate_components/overlapping_generic.rs:35:9 - | -28 | GreeterComponent: GreetHello, - | ---------------- first implementation here -... -35 | GreeterComponent: GreetHello, - | ^^^^^^^^^^^^^^^^ conflicting implementation for `Wrapper` diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/unconstrained_generic.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/unconstrained_generic.rs deleted file mode 100644 index 3d82c93b..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/unconstrained_generic.rs +++ /dev/null @@ -1,43 +0,0 @@ -//! Acceptable failure: a per-entry generic list on a `delegate_components!` -//! mapping whose parameter appears only in the *provider value* and not in the -//! *key*. The macro faithfully lowers ` GreeterComponent: GreetWith` into -//! `impl DelegateComponent for Person { type Delegate = -//! GreetWith; }`, where `T` is constrained by neither the trait, the self -//! type, nor a predicate — so the compiler rejects it with E0207. -//! -//! A per-entry generic is only well-formed when it appears in the key (as in -//! ` BazKey: BarValue`, where `DelegateComponent>` -//! binds it). Writing one that never reaches the key is ill-formed input, and -//! the macro lowers it faithfully rather than second-guessing it — so `rustc` -//! rejects the unconstrained parameter with exactly the E0207 it would give a -//! hand-written `impl` with an unused parameter. Deferring this to the -//! compiler is the intended behavior, not a macro defect. -//! -//! See docs/errors/wiring/unconstrained-generic.md. - -use core::marker::PhantomData; - -use cgp::prelude::*; - -#[cgp_component(Greeter)] -pub trait CanGreet { - fn greet(&self); -} - -pub struct GreetWith(pub PhantomData); - -#[cgp_provider] -impl Greeter for GreetWith { - fn greet(_context: &Context) {} -} - -pub struct Person; - -// `T` is declared for the entry but only used in the value `GreetWith`. -delegate_components! { - Person { - GreeterComponent: GreetWith, - } -} - -fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/unconstrained_generic.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/unconstrained_generic.stderr deleted file mode 100644 index 7e401441..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/unconstrained_generic.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error[E0207]: the type parameter `T` is not constrained by the impl trait, self type, or predicates - --> tests/acceptable/delegate_components/unconstrained_generic.rs:39:10 - | -39 | GreeterComponent: GreetWith, - | ^ unconstrained type parameter diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/use_context_cycle.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/use_context_cycle.rs deleted file mode 100644 index a5810611..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/use_context_cycle.rs +++ /dev/null @@ -1,44 +0,0 @@ -//! Acceptable failure: wiring a component to `UseContext` when the context's only -//! implementation of that component *is* that same delegation forms a cycle. -//! `UseContext` implements the provider trait by routing back through the context's -//! own consumer-trait impl, but that consumer impl exists only via this delegation -//! to `UseContext` — so resolving `Person: Greeter` requires resolving -//! `Person: CanGreet`, which requires `Person: Greeter` again. The trait -//! solver chases the cycle until it overflows the recursion limit (`E0275`). CGP -//! lowers the wiring faithfully and cannot see that the delegation is self-referential -//! without a whole-program view, so it defers the failure to the compiler. The fix is -//! to wire the component to a concrete provider that terminates the lookup. -//! -//! See docs/errors/wiring/wiring-cycle.md. - -use cgp::prelude::*; - -#[cgp_component(Greeter)] -pub trait CanGreet { - fn greet(&self); -} - -#[derive(HasField)] -pub struct Person { - pub name: String, -} - -// `Person`'s only source of `CanGreet` is this delegation to `UseContext`, which -// resolves back to `CanGreet` — a cycle. -delegate_components! { - Person { - GreeterComponent: UseContext, - } -} - -// Forcing the wiring through a check drives the solver into the cycle directly, so it -// overflows with `E0275` and the note chain names the cycle. (A plain method call on -// `Person` would instead surface the hidden `E0599`, since the method probe treats the -// unresolvable cycle as an unsatisfied bound — see docs/errors/hidden/.) -check_components! { - Person { - GreeterComponent, - } -} - -fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/use_context_cycle.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/use_context_cycle.stderr deleted file mode 100644 index 171533c1..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/use_context_cycle.stderr +++ /dev/null @@ -1,36 +0,0 @@ -error[E0275]: overflow evaluating the requirement `Person: IsProviderFor` - --> tests/acceptable/delegate_components/use_context_cycle.rs:40:9 - | -40 | GreeterComponent, - | ^^^^^^^^^^^^^^^^ - | -note: required for `Person` to implement `Greeter` - --> tests/acceptable/delegate_components/use_context_cycle.rs:16:1 - | -16 | #[cgp_component(Greeter)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^ -note: required for `Person` to implement `CanGreet` - --> tests/acceptable/delegate_components/use_context_cycle.rs:16:1 - | -16 | #[cgp_component(Greeter)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^ -17 | pub trait CanGreet { - | ^^^^^^^^ -note: required for `cgp::prelude::UseContext` to implement `IsProviderFor` - --> tests/acceptable/delegate_components/use_context_cycle.rs:16:1 - | -16 | #[cgp_component(Greeter)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^ -17 | pub trait CanGreet { - | -------- unsatisfied trait bound introduced here - = note: required for `Person` to implement `CanUseComponent` -note: required by a bound in `__CheckPerson` - --> tests/acceptable/delegate_components/use_context_cycle.rs:38:1 - | -38 | / check_components! { -39 | | Person { -40 | | GreeterComponent, -41 | | } -42 | | } - | |_^ required by this bound in `__CheckPerson` - = note: this error originates in the attribute macro `cgp_component` which comes from the expansion of the macro `check_components` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/tests/cgp-compile-fail-tests/tests/compile_fail_tests.rs b/crates/tests/cgp-compile-fail-tests/tests/compile_fail_tests.rs deleted file mode 100644 index f6e49e59..00000000 --- a/crates/tests/cgp-compile-fail-tests/tests/compile_fail_tests.rs +++ /dev/null @@ -1,27 +0,0 @@ -//! `trybuild` driver for the CGP compile-fail suite. -//! -//! Each fixture under `tests/acceptable//` and -//! `tests/problematic//` is compiled as its own crate and its -//! diagnostics compared against the sibling `.stderr` file. See the crate -//! [README](../README.md) for what belongs in each category and how to -//! regenerate the `.stderr` snapshots. -//! -//! A single `TestCases` runs both globs so `trybuild` invokes `cargo` once; -//! splitting the categories across two `#[test]` functions would race on the -//! shared build directory. The `**` in each glob descends into the per-construct -//! subdirectories, so a new fixture is picked up with no change here. - -#[test] -fn compile_fail() { - let t = trybuild::TestCases::new(); - - // Failures CGP intentionally delegates to the Rust compiler — CGP is - // working as designed and `rustc` is the right place for the check. These - // are documented under each owning macro's `## Failure modes` section. - t.compile_fail("tests/acceptable/**/*.rs"); - - // Failures that are a CGP defect: input a macro should have rejected, or an - // expansion that emits invalid Rust. Each is cross-linked to a `## Known - // issues` entry in the owning macro's implementation document. - t.compile_fail("tests/problematic/**/*.rs"); -} diff --git a/crates/tests/cgp-test-crate-a/Cargo.toml b/crates/tests/cgp-test-crate-a/Cargo.toml deleted file mode 100644 index 5f09d360..00000000 --- a/crates/tests/cgp-test-crate-a/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "cgp-test-crate-a" -version = "0.8.0-alpha" -publish = false -edition = { workspace = true } -license = { workspace = true } -repository = { workspace = true } -authors = { workspace = true } -rust-version = { workspace = true } -keywords = { workspace = true } - -[dependencies] -cgp = { workspace = true } diff --git a/crates/tests/cgp-test-crate-a/src/lib.rs b/crates/tests/cgp-test-crate-a/src/lib.rs deleted file mode 100644 index 70e49617..00000000 --- a/crates/tests/cgp-test-crate-a/src/lib.rs +++ /dev/null @@ -1,63 +0,0 @@ -//! Upstream crate for cross-crate CGP coherence tests. -//! -//! This crate defines a getter capability, a component with a provider, and a -//! component registered under the `@app` namespace. The downstream crate -//! `cgp-test-crate-b` wires these onto its own contexts, supplies its own -//! provider for the foreign `Greeter` component, and participates in the `@app` -//! namespace — exercising that CGP's two-trait split stays within Rust's -//! coherence and orphan rules across crate boundaries. -//! -//! See crates/tests/AGENTS.md and docs/concepts/coherence.md. - -use cgp::prelude::*; - -/// A published field accessor. Any context with a `name` field gains it through -/// the blanket `#[cgp_auto_getter]` impl, with no wiring required. -#[cgp_auto_getter] -pub trait HasName { - fn name(&self) -> &str; -} - -/// A component whose provider a downstream context can wire — or replace with its -/// own provider. -#[cgp_component(Greeter)] -pub trait CanGreet { - fn greet(&self) -> String; -} - -#[cgp_impl(new GreetHello)] -impl Greeter -where - Self: HasName, -{ - fn greet(&self) -> String { - format!("Hello, {}!", self.name()) - } -} - -/// A component registered under the `@app` namespace. A downstream context wires -/// it through `delegate_components! { … namespace DefaultNamespace; @app.…: … }`. -#[cgp_component(Announcer)] -#[prefix(@app in DefaultNamespace)] -pub trait CanAnnounce { - fn announce(&self) -> String; -} - -#[cgp_impl(new AnnounceLoudly)] -impl Announcer -where - Self: HasName, -{ - fn announce(&self) -> String { - format!("ANNOUNCEMENT from {}!", self.name()) - } -} - -// A shared namespace downstream crates populate and join. It inherits the -// built-in `DefaultNamespace`, so a context joining it also inherits the standard -// defaults. `cgp-test-crate-b` registers a *local* component into it with -// `#[default_impl]` — orphan-safe because the crate owns the component key even -// though it does not own this namespace. -cgp_namespace! { - new AppNamespace: DefaultNamespace {} -} diff --git a/crates/tests/cgp-test-crate-b/Cargo.toml b/crates/tests/cgp-test-crate-b/Cargo.toml deleted file mode 100644 index 3b845245..00000000 --- a/crates/tests/cgp-test-crate-b/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "cgp-test-crate-b" -version = "0.8.0-alpha" -publish = false -edition = { workspace = true } -license = { workspace = true } -repository = { workspace = true } -authors = { workspace = true } -rust-version = { workspace = true } -keywords = { workspace = true } - -[dependencies] -cgp = { workspace = true } -cgp-test-crate-a = { workspace = true } diff --git a/crates/tests/cgp-test-crate-b/src/lib.rs b/crates/tests/cgp-test-crate-b/src/lib.rs deleted file mode 100644 index 561b0255..00000000 --- a/crates/tests/cgp-test-crate-b/src/lib.rs +++ /dev/null @@ -1,147 +0,0 @@ -//! Downstream crate for cross-crate CGP coherence tests. -//! -//! Everything here consumes the CGP surface defined in `cgp-test-crate-a`, -//! demonstrating four cross-crate abilities that Rust's coherence rules would -//! otherwise make awkward: -//! -//! 1. wiring a foreign component to a foreign provider on a local context; -//! 2. defining a *local* provider for a *foreign* provider trait (orphan-safe, -//! because the provider struct is local) and wiring a context to it; -//! 3. participating in a namespace declared upstream; -//! 4. registering a *local* component into an upstream namespace with -//! `#[default_impl]` (orphan-safe because the crate owns the component key). -//! -//! See crates/tests/AGENTS.md and docs/concepts/coherence.md. - -use cgp::prelude::*; -use cgp_test_crate_a::{ - AnnounceLoudly, AnnouncerComponent, AppNamespace, GreetHello, Greeter, GreeterComponent, - HasName, -}; - -/// (1) A local context wires the foreign `Greeter` component to the foreign -/// `GreetHello` provider. `GreetHello` needs `HasName`, satisfied by the `name` -/// field through crate-a's auto getter. -#[derive(HasField)] -pub struct Person { - pub name: String, -} - -delegate_components! { - Person { - GreeterComponent: GreetHello, - } -} - -/// (2) A provider defined in *this* crate for the foreign `Greeter` provider -/// trait. This is allowed because `GreetGoodbye` is local, even though `Greeter` -/// is not — the coherence win that CGP's provider structs are built for. -#[cgp_impl(new GreetGoodbye)] -impl Greeter -where - Self: cgp_test_crate_a::HasName, -{ - fn greet(&self) -> String { - format!("Goodbye, {}!", self.name()) - } -} - -#[derive(HasField)] -pub struct FormalPerson { - pub name: String, -} - -delegate_components! { - FormalPerson { - GreeterComponent: GreetGoodbye, - } -} - -/// (3) A local context participates in crate-a's `@app` namespace, wiring the -/// upstream `Announcer` component through the namespace path. -#[derive(HasField)] -pub struct Broadcaster { - pub name: String, -} - -delegate_components! { - Broadcaster { - namespace DefaultNamespace; - - @app.AnnouncerComponent: AnnounceLoudly, - } -} - -/// (4) A *local* component and provider registered into crate-a's *foreign* -/// `AppNamespace` with `#[default_impl]`. This is orphan-safe because the key — -/// the local `FarewellComponent` — is owned by this crate, even though -/// `AppNamespace` is not: registering a per-component default needs the crate to -/// own either the namespace or the component key. (A `#[prefix]`-ed component, -/// whose key is a foreign `PathCons<..>` path rather than a local marker, could -/// only be registered from the crate that owns the namespace.) `Leaver` then joins -/// `AppNamespace` and resolves the farewell through it, with no direct wiring. -#[cgp_component(Farewell)] -pub trait CanFarewell { - fn farewell(&self) -> String; -} - -#[cgp_impl(new GoodbyeFarewell)] -#[default_impl(FarewellComponent in AppNamespace)] -impl Farewell -where - Self: HasName, -{ - fn farewell(&self) -> String { - format!("Goodbye, {}!", self.name()) - } -} - -#[derive(HasField)] -pub struct Leaver { - pub name: String, -} - -delegate_components! { - Leaver { - namespace AppNamespace; - } -} - -#[cfg(test)] -mod tests { - use cgp_test_crate_a::{CanAnnounce, CanGreet}; - - use super::*; - - #[test] - fn default_impl_into_upstream_namespace() { - let leaver = Leaver { - name: "John".to_owned(), - }; - assert_eq!(leaver.farewell(), "Goodbye, John!"); - } - - #[test] - fn wire_foreign_provider() { - let person = Person { - name: "John".to_owned(), - }; - assert_eq!(person.greet(), "Hello, John!"); - } - - #[test] - fn local_provider_for_foreign_component() { - let person = FormalPerson { - name: "John".to_owned(), - }; - assert_eq!(person.greet(), "Goodbye, John!"); - } - - #[test] - fn participate_in_upstream_namespace() { - let broadcaster = Broadcaster { - name: "John".to_owned(), - }; - assert_eq!(broadcaster.announce(), "ANNOUNCEMENT from John!"); - } -} diff --git a/docs/AGENTS.md b/docs/AGENTS.md index 92ce3067..ce42a25d 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -44,7 +44,7 @@ A guide leans on the other sections rather than restating them. Link to the refe ## The errors directory -The [errors/](errors/README.md) directory catalogs the compiler errors CGP produces *after* codegen — input a macro accepts and lowers to Rust that then fails to compile — organized by the kind of error rather than by the macro. It is the counterpart to the implementation tree's per-macro failure documentation, gathered into a reader-facing catalog and built around whether the compiler surfaces or hides each class's root cause. The dividing line with the rest of the knowledge base is ownership: a failure the *compiler* raises while checking a macro's output is documented here and is the canonical home for the post-codegen [compile-fail fixtures](../crates/tests/cgp-compile-fail-tests); a failure a macro raises by *rejecting* its input stays with the macro's implementation document. Its own [errors/AGENTS.md](errors/AGENTS.md) carries the authoring rules — chiefly that a document summarizes the *kind* of error and the *position* of its root cause rather than pasting verbatim output — and [errors/README.md](errors/README.md) is the catalog you register a new error document in. The migration of the acceptable-failure prose out of the implementation documents' `## Failure modes` sections and into this catalog is rolled out class by class; the catalog's README tracks which classes are done. +The [errors/](errors/README.md) directory catalogs the compiler errors CGP produces *after* codegen — input a macro accepts and lowers to Rust that then fails to compile — organized by the kind of error rather than by the macro. It is the counterpart to the implementation tree's per-macro failure documentation, gathered into a reader-facing catalog and built around whether the compiler surfaces or hides each class's root cause. The dividing line with the rest of the knowledge base is ownership: a failure the *compiler* raises while checking a macro's output is documented here and is the canonical home for the post-codegen compile-fail cases, which are pinned as UI fixtures in [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp) (migrated out of the former `cgp-compile-fail-tests` suite, now removed) and linked from each class as a GitHub URL; a failure a macro raises by *rejecting* its input stays with the macro's implementation document. Its own [errors/AGENTS.md](errors/AGENTS.md) carries the authoring rules — chiefly that a document summarizes the *kind* of error, records both the raw diagnostic and how `cargo-cgp` reshapes it, and states the *position* of the root cause rather than pasting verbatim output — and [errors/README.md](errors/README.md) is the catalog you register a new error document in. A macro's implementation `## Failure modes` section now links out to the catalog class and its `cargo-cgp` fixture rather than describing a local one; per the cross-project [sync rule](../AGENTS.md#the-two-projects-cgp-and-cargo-cgp), a diagnostic change updates the fixture in `cargo-cgp` and the class here together. ## The skills directory diff --git a/docs/README.md b/docs/README.md index 9b03eabf..bfa13fc0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -22,7 +22,7 @@ The [examples/](examples/README.md) directory holds self-contained worked exampl The [guides/](guides/README.md) directory holds the guides to *writing* CGP — documents that direct the choices an author makes when more than one construct could express the same thing. Where the reference and concepts explain what a construct means and why it exists, a guide is prescriptive: it recommends a default form, names the trade-offs of the alternatives, and usually walks a concrete before/after refactoring. Choosing a construct's vanilla-looking form over its explicit equivalent, keeping wiring tables short with namespaces, and debugging a wiring that will not compile all live here. -The [errors/](errors/README.md) directory catalogs the compiler errors CGP produces *after* codegen — input a macro accepts and lowers to Rust that then fails to compile — organized by the kind of error rather than by the macro that produced it. Each document records the anatomy of one class: the mistake that triggers it, the shape of the diagnostic, whether the root cause is present in the output, and where it sits when it is. The catalog is built around the distinction between errors that *surface* their root cause and those the compiler *hides*, and it is the canonical documentation for the post-codegen compile-fail fixtures, so it exists both to guide debugging agents and to map the ground a `cargo-cgp`-style tool would post-process. Failures a macro raises by *rejecting* its input stay with the macro's implementation document instead. +The [errors/](errors/README.md) directory catalogs the compiler errors CGP produces *after* codegen — input a macro accepts and lowers to Rust that then fails to compile — organized by the kind of error rather than by the macro that produced it. Each document records the anatomy of one class: the mistake that triggers it, the shape of the diagnostic, whether the root cause is present in the output, and where it sits when it is. The catalog is built around the distinction between errors that *surface* their root cause and those the compiler *hides*, and it is the canonical documentation for the post-codegen compile-fail cases — now pinned as UI fixtures in [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp), CGP's first-class error toolchain. Each class records both the raw diagnostic and how `cargo-cgp` reshapes it, so the catalog serves debugging agents, guides the tool, and doubles as the reference behind [`cargo-cgp`'s own](reference/cargo-cgp.md) `[CGP-Exxx]` output. Failures a macro raises by *rejecting* its input stay with the macro's implementation document instead. The [related-work/](related-work/README.md) directory looks outward instead of inward: each document takes an external concept, framework, or language feature that resembles CGP — dependency injection, implicit parameters, and so on — explains it faithfully and with citations, weighs what its users like and dislike about it, and positions CGP against it. These documents exist to serve future user-facing writing, giving an agent who must explain CGP to readers of a particular background the honest comparison and the positioning strategy to build on the intuitions those readers already hold. diff --git a/docs/communication-strategy/README.md b/docs/communication-strategy/README.md index a9369dea..ea66e52a 100644 --- a/docs/communication-strategy/README.md +++ b/docs/communication-strategy/README.md @@ -10,6 +10,8 @@ CGP adds language-level capabilities that many Rust developers have never seen The purpose is twofold: to help writing **gain attention**, by naming which content and hooks draw the most interest, and to help it **minimize misunderstanding**, by naming the misreadings CGP reliably provokes and how to preempt them. Both goals run through the reader: attention is won by leading with what a specific audience cares about, and misunderstanding is avoided by defusing the specific misreading that audience is prone to. +One development is worth flagging at the section level, because it shifts a long-standing piece of CGP's positioning. The obstacle CGP's audience has cited most is not conceptual but practical: its compiler errors, where a small wiring mistake expands into a wall of generated-type noise with the real cause buried or hidden. That obstacle now has a first-class answer — [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp), a cargo subcommand that rewrites those errors into a compact, root-cause-first form (see the [cargo-cgp reference](../reference/cargo-cgp.md) and the [error catalog](../errors/README.md)). For an advocate this is a de-risking asset to lead with, not a cost to quietly concede — subject to the one honesty caveat the whole section is bound by: cargo-cgp is an early pre-release that already handles the common wiring errors but does not yet reshape every class, so present it as *the error experience is dramatically better and actively improving*, never as solved. The [skepticism](skepticism.md), [problems solved](problems-solved.md), [selling points](selling-points.md), and [technical barriers](technical-barriers.md) documents work this shift out in detail. + ## Relationship to related work This section is the natural companion to [related-work/](../related-work/README.md), and the two are read together when preparing public writing. A related-work document explains one external idea — dependency injection, type classes, reflection — faithfully, records what its users like and dislike, and positions CGP against it; a communication-strategy document generalizes across those comparisons into audience-level guidance about which readers exist, what they already believe, and how a piece should be shaped for them. When a related-work document records a sentiment — that Rust developers reach for Dagger to escape reflection's runtime cost, say — this section turns it into a reader trait an author can plan around. Read the matching related-work document for the depth of a comparison; read here for the shape of the audience. diff --git a/docs/communication-strategy/attention-and-engagement.md b/docs/communication-strategy/attention-and-engagement.md index 741aa755..4005a172 100644 --- a/docs/communication-strategy/attention-and-engagement.md +++ b/docs/communication-strategy/attention-and-engagement.md @@ -12,7 +12,7 @@ A caution on reading the evidence: public engagement metrics are noisy, and abse The clearest signal of where attention sits is the annual survey, and it names two costs to concede and one benefit to lean on. The [2024 State of Rust survey](https://blog.rust-lang.org/2025/02/13/2024-State-Of-Rust-Survey-results/) reports that slow compile times remain the perennial top pain, that subpar debugging support is among the leading tooling complaints, and that 45.2% of respondents named the language's growing *complexity* as a worry for its future — while, asked to prioritize the project's work, developers ranked runtime performance second only to fixing compiler bugs. Read together, these dictate three moves for a CGP writer. -- **Concede compile-time cost and verbose diagnostics early and plainly.** They are the community's live sore spots, and a reader is actively scanning a new abstraction for whether it worsens them, exactly as [skepticism.md](skepticism.md) prescribes. A piece that stays silent on them reads as either naive or evasive to the survey's respondents. +- **Concede compile-time cost and verbose diagnostics early and plainly.** They are the community's live sore spots, and a reader is actively scanning a new abstraction for whether it worsens them, exactly as [skepticism.md](skepticism.md) prescribes. A piece that stays silent on them reads as either naive or evasive to the survey's respondents. On the diagnostics, that concession now travels with a response — CGP's [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp) toolchain reshapes the recognized error classes to lead with the root cause — though it remains a young pre-release, so the concession is not retired, only paired with the fix. - **Treat "this adds complexity" as the most dangerous perception a piece can leave.** Complexity is the community's named fear for the language's future, so "still ordinary Rust," gradual adoption, and problem-first restraint are not merely pleasant framings — they are the direct answer to the audience's stated anxiety, and the reason [key-features.md](key-features.md) reserves a headline slot for "Still Ordinary Rust." - **Lean hard on zero runtime cost.** The survey shows the community explicitly prizes runtime performance, so "resolved at compile time and compiled to a direct call" lands as an answer to something they already care about, not as an abstract virtue. This is the empirical backing for the [zero-runtime-cost selling point](selling-points.md). @@ -41,7 +41,7 @@ Five patterns recur in that discussion, and each maps to guidance elsewhere in t - **Traceability of control flow.** A distinct, repeated objection is that the indirection makes a codebase hard to navigate, because it is "basically impossible to tell which particular bit of code will be entered on a method call." This is not the generic "macros are magic" complaint but a specific worry about following execution, now recorded as its own entry in [skepticism.md](skepticism.md); the answer is that the wiring table is the one explicit, greppable place naming the provider for each component. - **"Isn't this just X reinvented."** Knowledgeable readers reach for prior art — aspect-oriented programming, Microsoft's COM, the ML module system (one comment invoked Greenspun's tenth rule) — as a skeptical frame. These deserve the honest engagement the [related-work](../related-work/README.md) documents supply, not deflection, because the reader making the comparison is exactly the one who can be won or lost on it. - **The name does not communicate.** Multiple commenters said plainly that "context-generic programming" obscures more than it conveys, that "coining a new phrase makes it harder to understand," and reached instead for "structural typing" or "duck typing for statically-typed code" to name what they thought was being described. This is direct field validation of the [tag-lines.md](tag-lines.md) rule that a name is not a pitch, and it surfaces a community-supplied bridge term worth testing — with the caveat that CGP is nominal-and-wired, not truly structural. -- **Do not overstate the ergonomics.** When CGP claims a reader need not understand its internals, a skeptic answers that "when I hit a compilation error, I'm going to have to understand the desugaring," and they are right. The honest position, per [technical-barriers.md](technical-barriers.md) and [skepticism.md](skepticism.md), concedes the error-message barrier rather than promising the internals stay out of sight. +- **Do not overstate the ergonomics.** When CGP claims a reader need not understand its internals, a skeptic answers that "when I hit a compilation error, I'm going to have to understand the desugaring," and they are right. The honest position, per [technical-barriers.md](technical-barriers.md) and [skepticism.md](skepticism.md), concedes the error-message barrier rather than promising the internals stay out of sight. This is the complaint CGP has answered most directly since: [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp), CGP's error toolchain, exists specifically to un-hide and lead with the root cause of a CGP compile error, so a piece meeting this objection can now point to a deliberate response rather than only conceding the cost. The tool is new — v0.1.0-alpha — and has no reception evidence of its own yet; do not manufacture any, and point to it as the deliberate answer to a recorded complaint, not as something the community is already praising. Two further lessons come from outside that discussion and still hold. "Dependency injection" is not a safe general hook, because idiomatic Rust already does lightweight DI with traits and generics and a large part of the audience treats DI *frameworks* as an unwanted import — the distinct native objection now in [skepticism.md](skepticism.md). And social proof is worth building: the comparable success stories in adjacent ecosystems turned on a flagship adopter, the way TypeScript's adoption accelerated once a major framework endorsed it, so CGP's most convincing answer to the evaluator's "is anyone really using this" is a real, non-trivial system built with it and shown as a worked example, not more argument. diff --git a/docs/communication-strategy/problems-solved.md b/docs/communication-strategy/problems-solved.md index a997a979..7eb48a3f 100644 --- a/docs/communication-strategy/problems-solved.md +++ b/docs/communication-strategy/problems-solved.md @@ -98,6 +98,24 @@ A subtler pain, felt by library authors, is that trait-based dependency injectio A caller who bounds on the consumer trait sees only the capability, never the dependency types the provider happens to need, so a library can require an internal helper without publishing it. This is a targeted pitch for the **library author** and complements the [explicit-and-compiler-checked-dependencies selling point](selling-points.md), which now carries the encapsulation angle; it rests on [impl-side dependencies](../concepts/impl-side-dependencies.md). The honest limit: this is a narrower, more advanced benefit than the others here, so it belongs in a piece aimed at library authors rather than in a general hook, where it would land as abstract. +## Read a CGP compile error without decoding a wall of generated types + +The pain that has done CGP the most adoption damage lives not in the language but in its errors: a small wiring mistake — a context missing one field a provider needs — expands, through the generated code, into screens of diagnostics naming `IsProviderFor`, `CanUseComponent`, and a nested `Symbol<…>` spine the programmer never wrote, with the real cause buried in the machinery or, in the worst class, suppressed entirely. A developer who meets that wall on their first mis-wire often concludes CGP is unusable and leaves, which is why this is the pain most worth showing removed. + +The "before" is a `Rectangle` context wired to an area calculator whose provider reads a `height` field the struct does not have. Under plain `cargo check`, the failure surfaces as an `E0277`/`E0599` cascade that names the consumer and provider traits but not the missing field — the [hidden unsatisfied-dependency class](../errors/hidden/unsatisfied-dependency.md), where the default trait solver omits the cause rather than merely burying it. The "after" is the same mistake through CGP's error toolchain: + +```text +$ cargo cgp check +error[E0277]: [CGP-E001] the consumer trait `CanCalculateArea` is not implemented for context `Rectangle` + = note: root cause: [CGP-E106] missing field `height` on `Rectangle` + this is required through the dependency chain: + [CGP-E101] consumer trait impl `CanCalculateArea` for context `Rectangle` + └─ [CGP-E102] provider trait impl `AreaCalculator` with context `Rectangle` for provider `RectangleArea` + └─ [CGP-E106] missing field `height` on `Rectangle` +``` + +[`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp) turns on Rust's next-generation trait solver to un-hide the suppressed cause, then leads with it — naming the missing `height` field and drawing the dependency chain that reaches it as a `cargo tree`-style tree, each line tagged with a `[CGP-Exxx]` code. This maps to the [tooling selling point](selling-points.md) and answers the ["wall of generated types"](skepticism.md) objection head-on, for every reader but most sharply the **working developer and the evaluator** ([reader-profiles.md](reader-profiles.md)) weighing whether a team can live with the diagnostics. The honest limit, stated in the same breath: `cargo-cgp` is v0.1.0-alpha and reshapes the classes it recognizes — the core wiring errors — while some, orphan-rule errors among them, still pass through as the compiler wrote them, so the honest claim is that the error experience is dramatically better and improving, not that it is solved. The reference [`cargo-cgp.md`](../reference/cargo-cgp.md) and the [error catalog](../errors/README.md) carry the fuller picture. + ## Choosing which problem to lead with -The last decision is which of these to open on, and it is settled by the reader, not by which problem is most impressive. Name the one or two dominant profiles for the piece and its channel first, per [reader-profiles.md](reader-profiles.md), then pick the problem that reader feels most sharply: the mock-in-tests swap for the pragmatic majority, the orphan-rule escape for the trait-heavy developer, overlapping implementations for the type-system reader, the error-type swap for the systems and ML-module reader, the monolith decomposition for the evaluator, and the structure-generic framework for the tooling author. When a piece must serve a broad public audience, lead with the mock-in-tests swap or the orphan-rule escape, because they are the most widely felt and the least likely to read as astronaut architecture — and hold the more advanced problems for the channels where their reader gathers. +The last decision is which of these to open on, and it is settled by the reader, not by which problem is most impressive. Name the one or two dominant profiles for the piece and its channel first, per [reader-profiles.md](reader-profiles.md), then pick the problem that reader feels most sharply: the mock-in-tests swap for the pragmatic majority, the orphan-rule escape for the trait-heavy developer, overlapping implementations for the type-system reader, the error-type swap for the systems and ML-module reader, the monolith decomposition for the evaluator, and the structure-generic framework for the tooling author — and the readable-errors story for the evaluator or any reader who has heard CGP's diagnostics are unusable. When a piece must serve a broad public audience, lead with the mock-in-tests swap or the orphan-rule escape, because they are the most widely felt and the least likely to read as astronaut architecture — and hold the more advanced problems for the channels where their reader gathers. diff --git a/docs/communication-strategy/reader-profiles.md b/docs/communication-strategy/reader-profiles.md index aab7b7d5..50c2ce2c 100644 --- a/docs/communication-strategy/reader-profiles.md +++ b/docs/communication-strategy/reader-profiles.md @@ -24,7 +24,7 @@ The risk with the newcomer is that the full paradigm buries them: the consumer/p The working developer is fluent in traits, generics, and everyday patterns, ships production Rust, and judges any new tool by whether it earns the complexity it adds — this is the pragmatic majority of the Rust audience and the one most writing should target. They know trait objects, the common ecosystem crates, and the builder and newtype patterns; they are comfortable reading generic code but actively value simplicity and are allergic to over-abstraction. What excites this reader is a concrete, familiar pain that CGP removes: swapping a real implementation for a mock across tests and production without paying for `dyn` dispatch, escaping the orphan-rule newtype dance, or decoupling modules without pulling in a dependency-injection framework — shown on code that looks like code they already write. -The working developer's skepticism is the decisive obstacle, because they have watched "enterprise" abstraction ruin codebases and will pattern-match CGP to that at the first sign of ceremony. Their questions are whether it is over-engineered, what it does to compile times, how one debugs generated code, how long a teammate takes to learn it, and whether it is a lock-in risk. Win this reader by leading with a problem rather than the paradigm, showing a before/after on realistic code, and being candid about cost and about when *not* to reach for CGP. Emphasize that wiring is resolved at compile time and erased — there is no runtime cost — and demonstrate the vanilla-looking idioms so the code does not read as alien machinery. Restraint earns this reader's trust; a single overclaim loses them, often permanently and loudly. +The working developer's skepticism is the decisive obstacle, because they have watched "enterprise" abstraction ruin codebases and will pattern-match CGP to that at the first sign of ceremony. Their questions are whether it is over-engineered, what it does to compile times, how one debugs generated code, how long a teammate takes to learn it, and whether it is a lock-in risk. Win this reader by leading with a problem rather than the paradigm, showing a before/after on realistic code, and being candid about cost and about when *not* to reach for CGP. On the debugging and error-message worry specifically, point this reader at [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp), CGP's error toolchain, which reshapes CGP's compiler errors to lead with the root cause — conceding in the same breath that it is a v0.1.0-alpha that handles the core wiring errors rather than every class. Emphasize that wiring is resolved at compile time and erased — there is no runtime cost — and demonstrate the vanilla-looking idioms so the code does not read as alien machinery. Restraint earns this reader's trust; a single overclaim loses them, often permanently and loudly. ### The advanced Rust developer @@ -74,7 +74,7 @@ Write for the skimmer by leading with the single clearest benefit on concrete co This reader is a tech lead, staff engineer, or architect weighing whether to adopt CGP for a team or a product, and they read for risk as much as for benefit. The benefits that move them are maintainability, testability, decoupling, performance guarantees, and reduced boilerplate at scale, backed by evidence that it works in real systems. Their skepticism is about cost and risk they will own: team ramp-up and hiring, the maturity of a young paradigm and ecosystem, how the code is debugged, and the wisdom of betting a codebase on something novel. -Reach this reader with candor rather than enthusiasm: be honest about maturity and cost, show that CGP is a superset of ordinary traits so it can be adopted incrementally and stepped back from, and state plainly where it fits and where it does not. Address the "can my team learn this" question head-on rather than letting it fester. Overselling is fatal here, because this reader's job is to discount hype. +Reach this reader with candor rather than enthusiasm: be honest about maturity and cost, show that CGP is a superset of ordinary traits so it can be adopted incrementally and stepped back from, and state plainly where it fits and where it does not. When the debugging worry surfaces, point to [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp) as CGP's deliberate answer to unreadable errors, and model the candor this reader rewards by conceding it is itself an early pre-release (v0.1.0-alpha) that reshapes the core wiring errors rather than every class. Address the "can my team learn this" question head-on rather than letting it fester. Overselling is fatal here, because this reader's job is to discount hype. ## What nearly every reader shares diff --git a/docs/communication-strategy/selling-points.md b/docs/communication-strategy/selling-points.md index 75350563..b7361665 100644 --- a/docs/communication-strategy/selling-points.md +++ b/docs/communication-strategy/selling-points.md @@ -51,7 +51,21 @@ Say it like this: - "[`check_components!`](../reference/macros/check_components.md) is your container's startup validation — run at compile time instead of at boot." - "Dependencies stay in the implementation, not the public interface, so they never leak internal types into your API the way a public generic trait does." -Avoid overselling the verification as effortless. The check is something you write, and its error messages, though they name the missing dependency, are verbose — say so when the audience is technical, because pretending the diagnostics are pretty is exactly the kind of small dishonesty that costs trust. Frame the trade honestly: you write a check line, and in return the whole class of runtime wiring failures cannot occur. +Avoid overselling the verification as effortless. The check is something you write, and its error messages, though they name the missing dependency, are verbose — say so when the audience is technical, because pretending the diagnostics are pretty is exactly the kind of small dishonesty that costs trust. Frame the trade honestly: you write a check line, and in return the whole class of runtime wiring failures cannot occur. The verbosity itself now has a dedicated answer in the tooling — the next selling point — but keep telling a technical reader that the raw compiler output is a wall of generated types, because that is what they meet without the tool. + +## First-class tooling makes CGP's errors readable + +A selling point CGP could not honestly make until recently is that its compiler errors are now served by a dedicated checker that leads with the root cause, so the single biggest thing that has ever driven readers away from CGP is being attacked directly. The tool is [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp), CGP's error toolchain: a cargo subcommand, `cargo cgp check`, that stands in for `cargo check` and rewrites CGP's compiler errors into a compact, root-cause-first form — much as Clippy layers its own analysis on top of `rustc`. Two mechanisms carry the pitch. It turns on Rust's next-generation trait solver, which **un-hides** the dependency errors the default solver suppresses — the worst class of CGP error, where plain `cargo check` says only that a method's bounds are unsatisfied and never names the missing field, now names it — and it **rewrites the classes it recognizes** to lead with the real cause, tagging each with a `[CGP-Exxx]` code and drawing the dependency chain as a `cargo tree`-style tree. This is the selling point that finally answers ["the error messages are a wall of generated types"](skepticism.md), the objection most likely to bite a real user. + +The honesty this selling point demands is unusually load-bearing, because the reader can `cargo install` the tool and check the claim within the hour. `cargo-cgp` is **v0.1.0-alpha**, a young pre-release: it already handles the core wiring-error classes well — missing field, missing wiring, unmet dependency chains, wrong field type, duplicate wiring — but it does *not* yet reshape every class, and some (orphan-rule errors among them) still pass through as the compiler wrote them. So the frame to hold is "the error experience is dramatically better and actively improving," never "solved." Conceding the alpha is what makes the claim believable to this audience, not a hedge that weakens it; the reference [`cargo-cgp.md`](../reference/cargo-cgp.md) and the [error catalog](../errors/README.md) carry the class-by-class detail. + +Say it like this: + +- "CGP's errors are served by a dedicated checker that leads with the root cause — `cargo cgp check` names the missing field instead of a wall of generated types." +- "The next-generation solver un-hides the cause the default compiler buries; the tool then leads with it and tags it with a code." +- "A young but fast-improving toolchain — v0.1.0-alpha — already reshapes the core wiring errors." + +Avoid the overclaim the tool's newness makes tempting. Do not say "CGP errors are now as clear as any other Rust error," and do not call the diagnostics "solved" or "fixed" — the tool reshapes the *recognized* classes and concedes the rest, so a reader who hits an unreshaped class after reading "solved" discounts everything else you said. Do not imply cargo-cgp changes what CGP does: the errors were always shaped by CGP's real design — lazy wiring, generated types — and the tool makes them *readable*, it does not remove them from the language. And never state the capability without the alpha concession nearby, because with this audience the concession is the credibility. ## It reads like ordinary Rust, and you adopt it gradually diff --git a/docs/communication-strategy/skepticism.md b/docs/communication-strategy/skepticism.md index a565e5db..b83a6a5b 100644 --- a/docs/communication-strategy/skepticism.md +++ b/docs/communication-strategy/skepticism.md @@ -78,9 +78,11 @@ Word it by conceding the direction and declining to invent a magnitude. Acknowle ### "The error messages are a wall of generated types" -This is justified, and it is the cost most likely to bite a real user, so it must be conceded without flinching. A mis-wired context can produce long, generated-type-heavy diagnostics, and a reader who has hit one will not believe a writer who pretends the experience is pleasant. +This is justified, it is the cost most likely to bite a real user, and for most of CGP's life it had to be conceded as a standing sharp edge — but that has changed, and the response is no longer "this is the price you pay" but "this is being fixed directly, use the tool." A mis-wired context can still produce long, generated-type-heavy diagnostics under plain `cargo check`, and a reader who has hit one will not believe a writer who pretends the raw experience is pleasant, so the concession stays. What is new is that CGP now ships a dedicated answer to exactly this pain, which downgrades the objection from a conceded cost to a cost actively being removed. -Word it by owning the problem and naming the mitigation precisely. Say that CGP's errors can be verbose, that [`check_components!`](../reference/macros/check_components.md) exists specifically to force the real cause — a missing field, type, or dependency — to be named at the wiring site rather than surfacing far away, and that this localizes the failure even though it does not make the message short. This is honest and still reassuring: the tool has a known sharp edge and a known way to blunt it. Claiming the diagnostics are good is the fastest way to lose the technical reader. +Word it by conceding the raw diagnostics and then pointing at [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp), CGP's error toolchain. Say that CGP's raw errors can be verbose, that [`check_components!`](../reference/macros/check_components.md) forces the real cause — a missing field, type, or dependency — to be named at the wiring site, and that `cargo cgp check` goes further: it turns on Rust's next-generation trait solver to **un-hide** the cause the default solver suppresses (the worst class, where the compiler names no missing field at all), then leads with that root cause, tags it with a `[CGP-Exxx]` code, and draws the dependency chain as a tree. The claim to make is that a dedicated checker leads with the root cause — not that CGP's errors are now as clear as any other Rust error's. + +Concede the tool's youth in the same breath, because the reader can install it and check within the hour. `cargo-cgp` is v0.1.0-alpha: it already reshapes the core wiring-error classes but does not yet cover every class, and some still pass through as the compiler wrote them. The honest frame is that the error experience is dramatically better and actively improving — not solved. That candor is what makes the rebuttal land; claiming the diagnostics are now good, or the problem finished, is the fastest way to lose the technical reader. The [selling-points](selling-points.md) mirror carries the capability side of this same claim, and the [error catalog](../errors/README.md) the class-by-class detail. ### "Rust doesn't need dependency injection — that's a Java-ism" @@ -117,5 +119,5 @@ The words and moves to avoid, each paired with what to do instead, are worth kee - Do not flatly call CGP "a DI framework," "a reflection system," or "an effect system." Qualify each — "compile-time, reflection-free dependency injection," "compile-time structural reflection," "the dynamic-binding fragment of effect handlers" — and state the distinguishing limit in the same breath. - Do not imply any runtime component. Say the wiring is resolved at compile time and erased; a wired call is a direct call. - Do not claim "no boilerplate," "replaces traits," or an unbenchmarked "faster." Say "moves boilerplate into one place," "a superset of traits," and "no runtime cost." -- Do not pretend the diagnostics are clean or the compile-time cost is nil. Concede both, and name [`check_components!`](../reference/macros/check_components.md) and the compile-time-versus-runtime trade as the honest mitigations. +- Do not pretend the diagnostics are clean or the compile-time cost is nil. Concede both — and for the diagnostics, point at [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp), which un-hides and leads with the root cause for the classes it recognizes while conceding it is a v0.1.0-alpha that does not yet reshape them all; name [`check_components!`](../reference/macros/check_components.md) and the compile-time-versus-runtime trade as the other honest mitigations. - Do not sell CGP as universally better than plain traits, or promise runtime dynamism it lacks. Name where the plainer tool or a runtime mechanism wins; the concession is what makes the rest believable. diff --git a/docs/communication-strategy/technical-barriers.md b/docs/communication-strategy/technical-barriers.md index 8bd958c7..983f601c 100644 --- a/docs/communication-strategy/technical-barriers.md +++ b/docs/communication-strategy/technical-barriers.md @@ -54,7 +54,9 @@ The design keeps the surface simple — a [`delegate_components!`](../reference/ A barrier that design can lower but not remove is the error message: a mis-wired context can produce a wall of generated types, and understanding that wall requires exactly the machinery a beginner is trying to avoid. This is where a reader who was following comfortably can be thrown, because the failure speaks in a register the ergonomic surface had spared them. -CGP's mitigation is real but partial. [`check_components!`](../reference/macros/check_components.md) forces the failure to surface at the wiring site and names the actual missing dependency — a field, a type, a capability — rather than letting it erupt far away, and the [errors catalog](../errors/README.md) documents the recurring shapes. The teaching move is to teach the reader to run a check and read the root cause first, and to set the expectation honestly: CGP's diagnostics can be verbose, a check localizes them, and that is the current state of the art rather than a solved problem. This is the one barrier where pretending it away costs more trust than admitting it, which is why [skepticism.md](skepticism.md) treats the same point from the persuasion side. +CGP's mitigations are real, and one of them is now a dedicated tool. The oldest is [`check_components!`](../reference/macros/check_components.md), which forces the failure to surface at the wiring site and names the actual missing dependency — a field, a type, a capability — rather than letting it erupt far away; the [errors catalog](../errors/README.md) documents the recurring shapes. The newest, and the one that most lowers this barrier, is [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp), CGP's error toolchain: run with `cargo cgp check` in place of `cargo check`, it turns on Rust's next-generation trait solver to **un-hide** the dependency errors the default solver suppresses — recovering the root cause of the very worst class, where plain `cargo check` reports only that a method's bounds are unsatisfied and never names the missing field — and then rewrites the classes it recognizes to lead with that cause, tagging each with a `[CGP-Exxx]` code. + +The teaching move is to hand the reader the tool early and teach them to read the root cause first, while setting the expectation with full honesty. Say that CGP's raw diagnostics can be verbose, that a check localizes them, and that `cargo cgp check` reshapes the recognized classes to lead with the cause — but that the tool is a v0.1.0-alpha that reshapes the core wiring errors rather than every class, leaving some (orphan-rule errors, for one) passing through as the compiler wrote them. The honest frame is that the error experience is dramatically better and actively improving, not solved. This is the one barrier where pretending it away costs more trust than admitting it, which is why [skepticism.md](skepticism.md) treats the same point from the persuasion side; the reference [`cargo-cgp.md`](../reference/cargo-cgp.md) covers installing and running the tool. ## Knowing where to start, and why it is worth it @@ -69,4 +71,4 @@ The barriers above share one strategy, and a writer can apply it as a short chec - **Motivate before mechanism.** Open on a concrete problem the reader already has, not on the consumer/provider split or coherence; the mechanism lands only after the reader wants it. - **Introduce vocabulary gradually and by analogy.** Defer the intimidating words — generic, blanket impl, coherence, higher-ranked trait bound, monomorphization — until they are needed, and anchor the ones you do use to a familiar picture, such as a wiring table as a settings map. - **Show rather than reassure.** Demonstrating that CGP code can look like plain Rust convinces a nervous reader; telling them "it's not that hard" does not. -- **Be honest about the barrier design cannot remove.** Set expectations for verbose error messages and point at the check that localizes them, rather than implying the diagnostics are as smooth as the surface syntax. +- **Be honest about the barrier design cannot fully remove — and point at the tooling that now lowers it.** Set expectations for verbose raw diagnostics, point at [`check_components!`](../reference/macros/check_components.md) and at `cargo cgp check`, which un-hides and leads with the root cause for the classes it recognizes, and concede that the tool is a young pre-release that does not yet reshape every class — rather than implying the diagnostics are as smooth as the surface syntax. diff --git a/docs/communication-strategy/vocabulary.md b/docs/communication-strategy/vocabulary.md index 807f8c08..ebd840b3 100644 --- a/docs/communication-strategy/vocabulary.md +++ b/docs/communication-strategy/vocabulary.md @@ -20,6 +20,7 @@ The established CGP vocabulary is the same in public writing as in the rest of t - **Impl-side dependency** — a requirement a provider states in its own implementation rather than in the interface callers see. Introduce it as "the provider declares what it needs, and callers never see it," because the encapsulation benefit is the point and the phrase "impl-side" means nothing cold. - **Pluggable trait implementations** — the chosen lead descriptor for the core capability, from [tag-lines.md](tag-lines.md): one interface with many interchangeable implementations, each chosen per context. Prefer "pluggable" as the novelty word and pair it with "at compile-time," because the tension between the two — pluggability a Rust reader expects at runtime, delivered statically — is the pitch in miniature. - **Context-generic programming** — the name of the paradigm, never the pitch. Introduce it only after a concrete capability has landed, always paired with a plain descriptor, per [tag-lines.md](tag-lines.md). +- **`cargo-cgp`** — CGP's error toolchain, the cargo subcommand that reshapes CGP's compiler errors. Refer to it as "cargo-cgp, CGP's error toolchain" and to its use as "the `cargo cgp check` command" or "running `cargo cgp check`." Introduce what it does as "a dedicated checker that leads with the root cause" — it un-hides the buried cause and names the missing field instead of printing a wall of generated types. Prefer the framing **readable, root-cause-first, dramatically better and actively improving**, and always concede in the same breath that it is a young pre-release (**v0.1.0-alpha**) reshaping the core wiring errors but not yet every class. Never say CGP's errors are **"solved," "fixed,"** or **"as clear as any other Rust error's"** — the tool reshapes the recognized classes and concedes the rest, and this audience will install it and catch an overclaim within the hour. The facts live in the reference [`cargo-cgp.md`](../reference/cargo-cgp.md) and the [error catalog](../errors/README.md). ## Terms to defer, and how to reveal them @@ -44,6 +45,7 @@ A handful of words reliably create the misunderstandings the section spends its - Avoid **"reusable"** as the word for the novelty — to a Rust reader traits are already the reuse mechanism, so "reusable trait implementations" names nothing new and points at the wrong axis. Say **"pluggable"** (or "interchangeable," "many implementations chosen per context"), which names the capability coherence normally forbids, per [tag-lines.md](tag-lines.md). - Avoid **"just"** in a competitor's description — "just macros," "just another DI framework" are the reader's dismissals, not ours, and echoing them concedes the frame. - Avoid overstating maturity — **"works on stable Rust today"** is true and worth saying, while **"production-proven at scale"** needs evidence the [evaluator](reader-profiles.md) will notice is missing. +- Avoid calling CGP's errors **"solved," "fixed,"** or **"now as clear as any other Rust error's."** Say a dedicated checker — **`cargo cgp check`** — **"leads with the root cause"** for the classes it recognizes, and concede it is a **v0.1.0-alpha** that does not yet reshape them all: **"dramatically better and actively improving,"** not done. ## The name, and the community's bridge terms diff --git a/docs/errors/AGENTS.md b/docs/errors/AGENTS.md index 04fb0301..07b6ceb0 100644 --- a/docs/errors/AGENTS.md +++ b/docs/errors/AGENTS.md @@ -8,11 +8,11 @@ Invoke the `/cgp` skill before writing or revising any document here, and write An error document records the *anatomy* of one class of post-codegen compile failure, so that a tool author, a debugging agent, or an error-extraction sub-agent can recognize the class and act on it without re-deriving it from a raw error dump. Where the [debugging guide](../guides/debugging.md) is prescriptive — "trace it like this" — an error document is descriptive: it states what the mistake is, what the compiler prints, whether the root cause is in that output, and where. It is the reference the guide and the tooling index into. -The catalog is the **canonical documentation for the post-codegen compile-fail fixtures** in [cgp-compile-fail-tests](../../crates/tests/cgp-compile-fail-tests). A macro's [implementation document](../implementation/README.md) still owns the failures the macro raises by *rejecting* its input (the `assert_macro_rejects` cases in `cgp-macro-tests`), but a failure that lands on the *compiler* checking the macro's output is documented here, and its fixture cross-links here. See [What belongs here](README.md#what-belongs-here-and-what-does-not) for the dividing line. +The catalog is the **canonical documentation for the post-codegen compile-fail cases**, which are pinned as UI fixtures in [cargo-cgp](https://github.com/contextgeneric/cargo-cgp) — CGP's first-class error toolchain. Those fixtures were migrated out of the former `cgp-compile-fail-tests` suite (now removed): a `cargo-cgp` UI fixture snapshots *both* the raw compiler output (`.rust.stderr`) and the readable form the tool rewrites it into (`.cgp.stderr`), so it captures the two halves this catalog describes. A macro's [implementation document](../implementation/README.md) still owns the failures the macro raises by *rejecting* its input (the `assert_macro_rejects` cases in `cgp-macro-tests`), but a failure that lands on the *compiler* checking the macro's output is documented here, and its `cargo-cgp` fixture links back here. See [What belongs here](README.md#what-belongs-here-and-what-does-not) for the dividing line. ## The synchronization rule applies here too -An error document must stay in sync with the code and its fixtures, and keeping it in sync is part of the change. The source of truth is the diagnostic the pinned toolchain actually emits, captured in a fixture's `.stderr`. When a change alters what the compiler prints for a class — a new error code, a root cause that moves from hidden to surfaced, a caret that lands elsewhere — revise the matching document in the same change; when you add, move, or reclassify a compile-fail fixture, update the document that catalogs its class so the catalog stays the canonical index. Verify a claim against a real compilation, not memory: regenerate the fixture's `.stderr` with `TRYBUILD=overwrite` (see [crates/tests/AGENTS.md](../../crates/tests/AGENTS.md)) and read what the compiler prints before you describe it. +An error document must stay in sync with the code and its fixtures, and keeping it in sync is part of the change. The source of truth is a `cargo-cgp` UI fixture, which pins both halves the document describes: the raw compiler diagnostic in its `.rust.stderr` and the reshaped `cargo-cgp` output in its `.cgp.stderr`. When a change alters either — a new error code, a root cause that moves from hidden to surfaced, a caret that lands elsewhere, or a `[CGP-Exxx]` reshaping that changes — revise the matching document in the same change; when you add, move, or reclassify a fixture in `cargo-cgp`, update the document that catalogs its class so the catalog stays the canonical index. This is the cross-project [sync rule](../../AGENTS.md#the-two-projects-cgp-and-cargo-cgp) in action: the fixture lives in `cargo-cgp`, the class doc here, and the two move together when `../cargo-cgp` is checked out. Verify a claim against a real compilation, not memory: read the fixture's blessed `.rust.stderr`/`.cgp.stderr`, or re-run `cargo cgp check` on the fixture, before you describe what it prints. Document the present, not the history, following [../AGENTS.md](../AGENTS.md): describe the diagnostic as it is emitted today, and delete superseded wording outright rather than leaving "previously" traces. @@ -31,26 +31,27 @@ In place of the output, every document must record three facts, because these ar Each error document follows the same shape so a reader can navigate any of them by habit. Open with a level-one heading naming the class and a one-sentence summary of the mistake and the error code it produces. Then proceed through these sections, using the same headings: - **What triggers it** — the CGP mistake that produces the class, with a short, self-contained code snippet of the failing input. Name the construct involved and link to its reference. -- **The diagnostic** — the *kind* of error the compiler emits, per the rule above: the code(s), the trait(s) named, and the shape. State plainly whether this is a hidden or a surfaced class. -- **Where the root cause is** — whether the true cause appears in the output and, if so, its position; if hidden, say so and explain briefly *why* the compiler suppresses it (the blanket-impl-plus-candidates heuristic, an elided type, cascade depth). +- **The raw diagnostic** — the *kind* of error **plain `rustc`/`cargo check` emits**, per the rule above: the code(s), the trait(s) named, and the shape. State plainly whether this is a hidden or a surfaced class. This section is the **fallback for when `cargo-cgp` is not available**, so it is kept in full even though the tool improves on it — an agent reading a raw cascade must still be able to recognize the class here. +- **Where the root cause is** — whether the true cause appears in the *raw* output and, if so, its position; if hidden, say so and explain briefly *why* the compiler suppresses it (the blanket-impl-plus-candidates heuristic, an elided type, cascade depth). +- **How cargo-cgp presents it** — how `cargo-cgp` reshapes the class into a readable form: the `[CGP-Exxx]` code(s) it stamps on the rewritten message (linking the [cargo-cgp error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md)), and the shape of the result — a `root cause:` note over a dependency tree, a de-duplicated headline, or a pass-through when the tool does not (yet) rewrite the class. This is the section that tells a reader what they will actually see with the recommended toolchain; keep it distinct from the raw fallback above. - **Resolving it** — the fix for the underlying mistake, and — for a hidden class — how to *promote* the error into a surfaced one (typically a `check_components!` at the wiring site) so the cause becomes visible. -- **Notes for tooling** — what a `cargo-cgp`-style post-processor should do for this class: which fragment to extract as the headline, what to suppress as noise, and whether recovering the cause needs compiler-internal introspection (`rustc_driver`) because the ordinary output hides it. Omit the heading when a class needs no special tool handling beyond the general rules. -- **Backing fixtures** — a bullet list of the [cgp-compile-fail-tests](../../crates/tests/cgp-compile-fail-tests) fixtures that pin this class, each linked with a one-line note of what it exercises. This is where the catalog's test pointers live; every documented class must have at least one fixture. +- **Notes for tooling** — anything a `cargo-cgp`-style post-processor still needs beyond what it already does: a class it does not yet rewrite (a usability gap), a fragment recovering the cause needs compiler-internal introspection (`rustc_driver`) for, and the like. Omit the heading when the "How cargo-cgp presents it" section already covers the handling. +- **Backing fixtures** — a bullet list of the `cargo-cgp` UI fixtures that pin this class, each written as a **GitHub URL** (`https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/.rs`) with a one-line note of what it exercises. This is where the catalog's test pointers live; every documented class must have at least one fixture. (When `../cargo-cgp` is checked out, read the fixture there; the committed link stays a GitHub URL per the [cross-project rule](../../AGENTS.md#the-two-projects-cgp-and-cargo-cgp).) - **Related** — links to the relevant reference documents, the [debugging guide](../guides/debugging.md) section that handles the class, the sibling error classes it contrasts with, and — for a problematic/defect class — the owning macro's `## Known issues`. ## The error-code reference The [error_codes/](error_codes/README.md) directory is a supporting reference, not a set of classes: one entry per `rustc` error code the catalog surfaces, stating what the code means in plain Rust, the rule behind it, and the RFC or issue that defines it. It is the forward index (code → meaning → the classes that emit it) that complements the class documents (mistake → diagnostic). Three rules keep it honest. First, **every fact in an entry is verified against the official Rust documentation** — the [error index](https://doc.rust-lang.org/error_codes/), the [reference](https://doc.rust-lang.org/reference/), and the linked RFCs and `rust-lang/rust` issues — not against memory; the message wording is confirmed against a real compilation (a fixture's `.stderr`) where the class docs already pin it. Second, **a class document cites the local `error_codes/` entry in place of a raw `doc.rust-lang.org/error_codes/…` URL**, so the official links live in one place; the entry's "Where CGP produces it" section links back to every class that emits the code, and the two directions must stay in sync when a class is added, moved, or renamed. Third, an entry states the *Rust* rule only — the CGP-specific anatomy stays in the class document, so the two never duplicate each other. Add an entry when a class first surfaces a code not already covered. -## Backing every class with a fixture, and organizing the fixtures +The directory also documents **`cargo-cgp`'s own `[CGP-Exxx]` codes** in [cargo-cgp-codes.md](error_codes/cargo-cgp-codes.md): a short reference that lists the codes the tool stamps on a rewritten message and points at `cargo-cgp`'s authoritative [error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md) for the full definitions. A class document's *How cargo-cgp presents it* section names the specific `[CGP-Exxx]` and links there, the same way it cites a local `rustc`-code entry — and since `cargo-cgp` owns those codes, the entry here is a pointer, not a second definition (the [sync rule](../../AGENTS.md#the-two-projects-cgp-and-cargo-cgp) keeps it from drifting). -Every error class must be backed by at least one compile-fail fixture, and reciprocally every post-codegen compile-fail fixture must be cataloged by a class here. When you write a class that has no fixture yet, add one under [cgp-compile-fail-tests](../../crates/tests/cgp-compile-fail-tests) following [crates/tests/AGENTS.md](../../crates/tests/AGENTS.md): place it under `acceptable/` or `problematic/` by the split above, write a header comment stating what it exercises and why it must not compile, and cross-link the fixture's header to the class document here (and, for a problematic case, to the owning macro's Known issues as well). Regenerate the `.stderr` and review it before committing. +## Backing every class with a fixture -Because the catalog is expected to grow a large number of fixtures, **keep each fixture directory small — no more than roughly a dozen cases — and split into further nested subdirectories when one grows past that.** The compile-fail driver globs the tree with `**`, so a new nesting level needs no registration. This mirrors the "split a category before it sprawls" rule the main suite already follows; the difference is only that the compile-fail tree groups by the failing macro's expansion rather than by concept, so a nested split subdivides within an owning macro's directory. +Every error class must be backed by at least one `cargo-cgp` UI fixture, and reciprocally every post-codegen compile-fail fixture in `cargo-cgp` must be cataloged by a class here. When you write a class that has no fixture yet, add one in `cargo-cgp` following its [UI-test guide](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/README.md): drop a `.rs` program (with `fn main`, and a `//@aux-build:` directive if it is cross-crate) into the category matching the output quality, bless it so both snapshots are written, and open it with a header stating what it exercises and linking the class document here as a GitHub URL. Then link it back from this class's Backing fixtures section. When `../cargo-cgp` is checked out, do both in the same change per the [sync rule](../../AGENTS.md#the-two-projects-cgp-and-cargo-cgp); when it is not, state the fixture that needs adding. ## Handling the acceptable / problematic / rejection split -A class's home is decided by the same three-way split the fixtures use, and getting it right keeps the catalog from either duplicating or losing information. An **acceptable** failure — one CGP intentionally defers to the compiler — is documented here in full; nothing about it belongs in a macro's Known issues, because it is not a defect. A **problematic** failure — a CGP defect — is cataloged here for the *observable error a user hits*, but the explanation of why it is a defect and what the correct behavior would be stays under the owning macro's `## Known issues`; link the two so neither drifts, and when the defect is fixed, update both in the same change. A failure the macro catches by **rejecting its input** is not cataloged here at all — it stays with the macro, per [What belongs here](README.md#what-belongs-here-and-what-does-not). +Two orthogonal classifications apply, and keeping them separate is what stops the catalog from duplicating or losing information. **Where the fixture lives in `cargo-cgp`** is decided by *output quality* — `ok/` (clean compile), `acceptable/` (the tool leads with the cause), or `usability/` (the cause is present but buried) — per `cargo-cgp`'s own guide; this catalog does not mirror those directories. **Whether the failure is a CGP defect** is the distinction that still governs this catalog: an **acceptable** failure — one CGP intentionally defers to the compiler — is documented here in full, and nothing about it belongs in a macro's Known issues, because it is not a defect; a **problematic** failure — a CGP defect — is cataloged here for the *observable error a user hits*, but the explanation of why it is a defect stays under the owning macro's `## Known issues`, linked both ways and updated together when the defect is fixed. A failure the macro catches by **rejecting its input** is not cataloged here at all — it stays with the macro, per [What belongs here](README.md#what-belongs-here-and-what-does-not). ## Gathering an error with a sub-agent diff --git a/docs/errors/README.md b/docs/errors/README.md index 3c0c6e2e..87143b81 100644 --- a/docs/errors/README.md +++ b/docs/errors/README.md @@ -6,7 +6,7 @@ This directory is a catalog of the compiler errors a programmer encounters when A CGP macro expands to ordinary Rust, so many mistakes are not caught by the macro at all — they surface later, when the compiler type-checks the generated code. Those downstream errors are the subject of this catalog. They are shaped by CGP's machinery in ways that make them hard to read: a single mistake can produce a wall of errors naming generated types the user never wrote, and the root cause is often buried or, worse, suppressed entirely. Documenting these classes in one place serves three readers at once. -The first reader is a **tool author**. The long-term goal is specialized tooling — a `cargo-cgp` that post-processes `rustc`'s output into a compact, root-cause-first form, much as Clippy layers its own analysis on top of the compiler. Such a tool needs a complete map of which error classes CGP produces, which of them hide the root cause, and where the root cause sits when it is present, before it can decide what to extract, what to suppress, and how to re-present it. A tool that reaches into compiler internals through `rustc_driver` to recover *suppressed* information especially needs to know, class by class, which information the compiler hides. This catalog is that map. +The first reader is a **tool author**. That tool now exists: [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp) is CGP's first-class toolchain, a cargo subcommand that post-processes `rustc`'s output into a compact, root-cause-first form, much as Clippy layers its own analysis on top of the compiler. It needs a complete map of which error classes CGP produces, which of them hide the root cause, and where the root cause sits when it is present, to decide what to extract, what to suppress, and how to re-present it; where it reaches into compiler internals through `rustc_driver` to recover *suppressed* information, it needs to know, class by class, which information the compiler hides. This catalog is that map — and each class here records both the raw diagnostic and how `cargo-cgp` reshapes it, so the catalog and the tool stay two views of one truth. The second reader is an **agent debugging CGP code**, in this repository or any project that uses CGP. Recognizing an error's class on sight — before decoding a single nested type — tells the agent what kind of mistake to look for and which technique will surface it. Grepping the error headlines (`grep -nE '^error'`) is the mechanical form of that recognition, and each class here has a signature line an agent can grep to confirm its cause without reading the whole cascade — the [debugging guide](../guides/debugging.md) collects those patterns in a table. The guide is the playbook; this catalog is the reference the playbook indexes into. @@ -14,11 +14,11 @@ The third reader is a **sub-agent extracting an error message on the main agent' ## What belongs here, and what does not -This catalog covers only **compilation failures that occur after CGP codegen** — input a macro *accepts* and lowers to Rust that then fails to type-, trait-, or borrow-check. This is exactly the set of failures pinned by the `trybuild` fixtures in [cgp-compile-fail-tests](../../crates/tests/cgp-compile-fail-tests), and the catalog is the canonical documentation for them: each documented class is backed by a fixture, and the fixtures cross-link back to the class they exercise. +This catalog covers only **compilation failures that occur after CGP codegen** — input a macro *accepts* and lowers to Rust that then fails to type-, trait-, or borrow-check. This is exactly the set of failures pinned by the UI fixtures in [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/README.md) (migrated there out of the former `cgp-compile-fail-tests` suite, now removed), and the catalog is the canonical documentation for them: each documented class is backed by a `cargo-cgp` fixture, and the fixtures cross-link back to the class they exercise. Because a `cargo-cgp` fixture snapshots the raw compiler output *and* the tool's reshaped output side by side, each class here can describe both — the raw diagnostic as a fallback, and how `cargo-cgp` presents it. Errors a macro raises by **rejecting its input** do not belong here. When a macro refuses malformed or unsupported input and returns a spanned `syn::Error` during expansion, that diagnostic is a property of the macro's parser, and it stays in the macro's [implementation document](../implementation/README.md) — described there and pinned by an `assert_macro_rejects` case in `cgp-macro-tests`. The dividing line is simple: if the macro produced the error, it is documented with the macro; if the *compiler* produced the error while checking the macro's output, it is documented here. -One nuance follows the [acceptable / problematic split](../implementation/AGENTS.md) that the compile-fail fixtures already use. An **acceptable** failure is one CGP intentionally defers to the compiler because it lacks the whole-program view the check needs; its documentation lives here in full. A **problematic** failure is a CGP *defect* — input a macro should have rejected, or an expansion that emits invalid Rust; the observable error a user hits is cataloged here like any other class, but the explanation of *why it is a defect and what the fix would be* stays under the owning macro's `## Known issues`, cross-linked both ways. +One nuance follows the [acceptable / problematic split](AGENTS.md#handling-the-acceptable--problematic--rejection-split) — whether a failure is a CGP *defect*. An **acceptable** failure is one CGP intentionally defers to the compiler because it lacks the whole-program view the check needs; its documentation lives here in full. A **problematic** failure is a CGP *defect* — input a macro should have rejected, or an expansion that emits invalid Rust; the observable error a user hits is cataloged here like any other class, but the explanation of *why it is a defect and what the fix would be* stays under the owning macro's `## Known issues`, cross-linked both ways. (This is orthogonal to which `cargo-cgp` directory — `acceptable/`, `usability/`, `ok/` — the backing fixture lives in, which is decided by output quality.) ## The central axis: hidden versus surfaced @@ -26,7 +26,7 @@ The single most important distinction in this catalog is whether the compiler ** When the failure is forced through a **check trait** — `check_components!` asserting `CanUseComponent`, which walks through [`IsProviderFor`](../reference/traits/is_provider_for.md) — the compiler evaluates the provider's `where` clause and reports the real unmet bound, naming the missing `HasField`, abstract type, or transitive dependency. The root cause is **surfaced** — the concrete missing bound is named in the diagnostic (for a single-component check, in the compiler's `help:` note), and a `required for …` note chain traces the dependency path from it back to the check. These errors are cataloged under [checks/](checks/). -When the same broken wiring is instead exercised by **calling the consumer-trait method directly** on the context, the compiler sees a blanket impl of the consumer trait alongside the other candidate impls, cannot commit to one, and falls back to a heuristic that reports only "the method exists but its trait bounds were not satisfied" — naming the consumer and provider traits but *not* descending into the dependency that actually failed. The root cause is **hidden**: it is absent from the output, not merely buried. Because these errors report nothing about the true cause, mixing them with the surfaced classes would mislead a reader into looking for a root cause that is not there. They are isolated under [hidden/](hidden/), and recovering their root cause requires either re-checking the wiring to promote the error into a surfaced one, or the kind of compiler-internal introspection a `cargo-cgp` tool would perform. +When the same broken wiring is instead exercised by **calling the consumer-trait method directly** on the context, the compiler sees a blanket impl of the consumer trait alongside the other candidate impls, cannot commit to one, and falls back to a heuristic that reports only "the method exists but its trait bounds were not satisfied" — naming the consumer and provider traits but *not* descending into the dependency that actually failed. The root cause is **hidden**: it is absent from the output, not merely buried. Because these errors report nothing about the true cause, mixing them with the surfaced classes would mislead a reader into looking for a root cause that is not there. They are isolated under [hidden/](hidden/), and recovering their root cause requires either re-checking the wiring to promote the error into a surfaced one, or the compiler-internal introspection `cargo-cgp` performs through `rustc_driver` (which is how, under its next-generation solver, it already surfaces causes plain `cargo check` hides). ## The verbosity problem: many errors, one cause @@ -46,7 +46,7 @@ The [wiring/](wiring/) directory holds the whole-program **structural** failures The [lowering/](lowering/) directory holds the classes where the failure is not in the wiring at all but in what a macro *lowered* the user's input into — an accepted shorthand or type combination expanded into Rust that is ill-formed on its own terms (an unsized type, for instance), so the compiler rejects the generated code rather than any wiring decision. -A fifth directory, [error_codes/](error_codes/), is a supporting *reference* rather than a class of error: one entry per `rustc` error code the catalog surfaces (`E0119`, `E0117`, `E0207`, `E0210`, `E0275`, `E0277`, `E0428`, `E0599`), stating what the code means in plain Rust, the rule behind it, and the RFC or issue that defines it, grounded in the official documentation. The class documents cite these entries in place of the raw `doc.rust-lang.org` URLs, so the Rust-language facts live in one verified place and the class docs carry only the CGP-specific anatomy. +A fifth directory, [error_codes/](error_codes/), is a supporting *reference* rather than a class of error: one entry per `rustc` error code the catalog surfaces (`E0119`, `E0117`, `E0207`, `E0210`, `E0275`, `E0277`, `E0428`, `E0576`, `E0599`), stating what the code means in plain Rust, the rule behind it, and the RFC or issue that defines it, grounded in the official documentation. The class documents cite these entries in place of the raw `doc.rust-lang.org` URLs, so the Rust-language facts live in one verified place and the class docs carry only the CGP-specific anatomy. The same directory holds [cargo-cgp-codes.md](error_codes/cargo-cgp-codes.md), the pointer to `cargo-cgp`'s own `[CGP-Exxx]` codes that each class's *How cargo-cgp presents it* section cites. ## Catalog @@ -81,8 +81,8 @@ Lowering errors — [lowering/](lowering/): Error-code reference — [error_codes/](error_codes/): -- One entry per `rustc` code the catalog surfaces — [`E0119`](error_codes/e0119.md), [`E0117`](error_codes/e0117.md), [`E0207`](error_codes/e0207.md), [`E0210`](error_codes/e0210.md), [`E0275`](error_codes/e0275.md), [`E0277`](error_codes/e0277.md), [`E0428`](error_codes/e0428.md), [`E0576`](error_codes/e0576.md), [`E0599`](error_codes/e0599.md) — each recording what the code means in plain Rust, the rule behind it, and where CGP produces it. See the [error-code reference index](error_codes/README.md). +- One entry per `rustc` code the catalog surfaces — [`E0119`](error_codes/e0119.md), [`E0117`](error_codes/e0117.md), [`E0207`](error_codes/e0207.md), [`E0210`](error_codes/e0210.md), [`E0275`](error_codes/e0275.md), [`E0277`](error_codes/e0277.md), [`E0428`](error_codes/e0428.md), [`E0576`](error_codes/e0576.md), [`E0599`](error_codes/e0599.md) — each recording what the code means in plain Rust, the rule behind it, and where CGP produces it. Plus [cargo-cgp-codes.md](error_codes/cargo-cgp-codes.md), the pointer to `cargo-cgp`'s `[CGP-Exxx]` codes. See the [error-code reference index](error_codes/README.md). ## Relationship to the rest of the knowledge base -This catalog is one of the views of CGP's truth and is bound by the [synchronization rule](../AGENTS.md): a class whose diagnostic the code no longer produces is a bug in the change that made it stale. It leans on the other sections rather than restating them. It links to the [reference](../reference/README.md) for the traits and macros a class involves, to the [check-traits concept](../concepts/check-traits.md) for why wiring is lazy, and above all to the [debugging guide](../guides/debugging.md), which is the prescriptive playbook this reference-style catalog supports. The rules for authoring and maintaining these documents — including how they stay in sync with the compile-fail fixtures and how a sub-agent extracts the errors they describe — live in [AGENTS.md](AGENTS.md). +This catalog is one of the views of CGP's truth and is bound by the [synchronization rule](../AGENTS.md): a class whose diagnostic the code no longer produces is a bug in the change that made it stale. It leans on the other sections rather than restating them. It links to the [reference](../reference/README.md) for the traits and macros a class involves, to the [check-traits concept](../concepts/check-traits.md) for why wiring is lazy, and above all to the [debugging guide](../guides/debugging.md), which is the prescriptive playbook this reference-style catalog supports. The rules for authoring and maintaining these documents — including how they stay in sync with the `cargo-cgp` fixtures and how a sub-agent extracts the errors they describe — live in [AGENTS.md](AGENTS.md). diff --git a/docs/errors/checks/check-trait-failure.md b/docs/errors/checks/check-trait-failure.md index 3a527bca..1faa234e 100644 --- a/docs/errors/checks/check-trait-failure.md +++ b/docs/errors/checks/check-trait-failure.md @@ -48,9 +48,9 @@ check_components! { A `check_components!` is the canonical way to force this diagnostic, but it is not the only one: any *direct* obligation on a capability bound produces the same shape. A [`#[use_type]`](../../reference/attributes/use_type.md) foreign import (`HasScalarType.Scalar in Types`) puts the capability bound `Types: HasScalarType` — grounded to `::Types: HasScalarType` for a nested import — onto the *generated trait itself*, so asserting or using that trait for a context whose supplied type does not implement the capability surfaces the identical `E0277` with a capability leaf, without any `check_components!`. Here the `required for …` chain runs through the trait's own `where` bound (`required by a bound in CanCalculateArea` / `GetScalar`) rather than through `CanUseComponent`, but the leaf, the `DelegateComponent`/`IsProviderFor` `help:`, and the position of the cause are the same. -## The diagnostic +## The raw diagnostic -This is a **surfaced** class: the compiler prints an `E0277` that names the concrete missing bound, unlike the hidden class that omits it. The primary error reports that `Person: CanUseComponent` is not satisfied, and its caret lands on the `GreeterComponent` entry *inside the `check_components!` block* — not on the `Person` context type — because the check re-spans the shared context token onto each listed component in turn. Immediately below, a `help:` note gives the actual unmet leaf bound: that `HasField` is not implemented for `Person`, "but trait `HasField` is implemented for it." That second half is a useful landmark — the compiler is pointing at the *nearest existing* field impl, which tells you the context has a field, just not the one the provider expects. +This section describes what plain `cargo check` prints — the fallback when `cargo-cgp` is not on hand; [How cargo-cgp presents it](#how-cargo-cgp-presents-it) below covers the readable form. This is a **surfaced** class: the compiler prints an `E0277` that names the concrete missing bound, unlike the hidden class that omits it. The primary error reports that `Person: CanUseComponent` is not satisfied, and its caret lands on the `GreeterComponent` entry *inside the `check_components!` block* — not on the `Person` context type — because the check re-spans the shared context token onto each listed component in turn. Immediately below, a `help:` note gives the actual unmet leaf bound: that `HasField` is not implemented for `Person`, "but trait `HasField` is implemented for it." That second half is a useful landmark — the compiler is pointing at the *nearest existing* field impl, which tells you the context has a field, just not the one the provider expects. Below the `help:` note, a `required for …` chain traces the dependency path outward from the leaf: `Person` to implement `HasName`, then `GreetHello` to implement `IsProviderFor`, then `Person` to implement `CanUseComponent`, and finally the bound in the generated `__CheckPerson` trait that the `check_components!` block emitted. The chain is the scaffolding; the leaf in the `help:` note is the cause. @@ -66,20 +66,24 @@ A distinct sub-case of this class is worth recognizing because its fix differs a Read the absence as its own signal: a checked context that implements `HasField` for nothing behind a `#[derive(HasField)]`-shaped requirement has most likely forgotten the derive, and the fix is to add `#[derive(HasField)]` to the struct, not to add fields one at a time. A tool handling this class should special-case it — zero `HasField` impls plus an unmet `HasField` leaf means the derive, and the headline should say so rather than sending the user after a single field. +## How cargo-cgp presents it + +`cargo-cgp` recognizes this class and leads with the cause. It keeps the `E0277` code but rewrites the headline to `[CGP-E001] the consumer trait \`CanGreet\` is not implemented for context \`Person\``, and replaces the `required for …` scaffolding with a single `root cause:` note over a compact dependency tree — `[CGP-E101]` consumer-trait-impl hop → `[CGP-E102]` provider-trait-impl hop → the leaf. The leaf is `[CGP-E106] missing field \`name\` on \`Person\`` for a genuinely absent field, or `[CGP-E108]` for the [derive-missing variant](#when-the-derive-is-missing-entirely), which coalesces every underived field on the struct into one "add `#[derive(HasField)]`" cause rather than listing them singly. The `#[use_type]` foreign-import form bottoms out on `[CGP-E107]` (the context supplies no wiring for the capability). The `Symbol!("name")` spine is resugared and the `CanUseComponent`/`IsProviderFor`/`__Check…` frames are dropped, so what remains is the field to fix and the wiring entry it hangs from. The codes are defined in the [cargo-cgp error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md). + ## Resolving it The fix is what the diagnostic already points to: satisfy the named leaf bound. Add the `name` field to `Person`, or wire the getter component to the existing field, so `Person` implements `HasName` and `GreetHello` becomes a valid provider for the component. Because the check surfaced both the concrete bound *and* the wiring entry, no further tracing is usually needed — which is exactly why the standard remedy for a [hidden](../hidden/unsatisfied-dependency.md) failure is to add a check and read this class instead. ## Notes for tooling -For a `cargo-cgp`-style post-processor, the fact to extract as the headline is the **leaf bound in the `help:` note** — the `HasField`, abstract type, or capability that is genuinely unimplemented — together with the caret's component entry. The `CanUseComponent` / `IsProviderFor` / `__Check…` frames in the `required for` chain are CGP scaffolding a user did not write and should be suppressed or collapsed into a short dependency path. This class is the *target* a tool aims for when handling the hidden class: promoting a hidden failure by synthesizing a check produces precisely this diagnostic, so a tool can normalize both to the same headline. +This class is the *target* `cargo-cgp` normalizes toward, and it already handles it fully (above). It is also where the hidden class lands once promoted: synthesizing a `check_components!` for a [hidden](../hidden/unsatisfied-dependency.md) failure produces exactly this diagnostic, so both reduce to the same `[CGP-E001]` headline and `root cause:` tree. ## Backing fixtures -- [acceptable/check_components/missing_dependency.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_dependency.rs) — the surfaced `E0277` for `GreetHello`'s unmet `Self: HasName`, whose `.stderr` pins the `help:` note naming `HasField` and the caret landing on `GreeterComponent` inside the block. Its unchecked counterpart, [acceptable/delegate_components/missing_dependency.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/missing_dependency.rs), pins the [hidden](../hidden/unsatisfied-dependency.md) `E0599` for the same mistake. The fused `delegate_and_check_components!` form produces the same surfaced shape. -- [acceptable/cgp_component/use_type_foreign_unsatisfied.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/use_type_foreign_unsatisfied.rs) — the capability bound reached through a `#[use_type]` foreign import instead of a check: naming a component for a `Types` that does not implement the imported `HasScalarType` surfaces `E0277` on `NoScalar: HasScalarType`, pinning that the foreign bound is *enforced on the generated trait* rather than silently dropped. -- [acceptable/cgp_fn/use_type_nested_unsatisfied.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs) — the same, but through a *nested* two-hop import, so the grounded bound `::Types: HasScalarType` is the one enforced; its `.stderr` pins the `required by this bound in GetScalar` note at the `HasScalarType.Scalar in Types` attribute, confirming the transitively-grounded foreign bound is checked at depth. -- [acceptable/check_components/missing_has_field_derive.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_has_field_derive.rs) — the [derive-missing variant](#when-the-derive-is-missing-entirely): a `Person` that has the `name` field but no `#[derive(HasField)]`, so the check fails with the same shape but *without* the "but trait `HasField<…>` is implemented" landmark, since `Person` implements the trait for no field. Its `.stderr` pins that absent landmark as the signal the whole derive is missing. +- [`acceptable/fields/missing_dependency.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/fields/missing_dependency.rs) — the surfaced case for `GreetHello`'s unmet `Self: HasName`; its `.rust.stderr` pins the raw `help:` note naming `HasField` and the caret on `GreeterComponent`, and its `.cgp.stderr` the `[CGP-E001]`/`[CGP-E106]` reshaping. Its use-site counterpart, [`acceptable/use-site/missing_dependency.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/use-site/missing_dependency.rs), reaches the same mistake by a method call — the [hidden](../hidden/unsatisfied-dependency.md) `E0599` in the raw output, which `cargo-cgp`'s next-gen solver recovers to the same headline. +- [`acceptable/use-type/use_type_foreign_unsatisfied.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/use-type/use_type_foreign_unsatisfied.rs) — the capability bound reached through a `#[use_type]` foreign import instead of a check: naming a component for a `Types` that does not implement the imported `HasScalarType` surfaces `E0277`, pinning that the foreign bound is *enforced on the generated trait* rather than silently dropped. +- [`acceptable/use-type/use_type_nested_unsatisfied.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/use-type/use_type_nested_unsatisfied.rs) — the same through a *nested* two-hop import, so the grounded bound `::Types: HasScalarType` is the one enforced, confirming the transitively-grounded foreign bound is checked at depth. +- [`acceptable/fields/missing_has_field_derive.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/fields/missing_has_field_derive.rs) — the [derive-missing variant](#when-the-derive-is-missing-entirely): a `Person` with the `name` field but no `#[derive(HasField)]`, so the raw output drops the "but trait `HasField<…>` is implemented" landmark (the signal the whole derive is missing), and `cargo-cgp` coalesces it to one `[CGP-E108]` cause. Related field-derive fixtures — [`base_area_2`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/fields/base_area_2.rs), [`empty_field_struct`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/fields/empty_field_struct.rs), [`underived_and_missing_field`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/fields/underived_and_missing_field.rs) — pin the coalescing and the mixed missing-plus-underived case. ## Related diff --git a/docs/errors/checks/higher-order-provider-layer.md b/docs/errors/checks/higher-order-provider-layer.md index 9938879f..c8705dc7 100644 --- a/docs/errors/checks/higher-order-provider-layer.md +++ b/docs/errors/checks/higher-order-provider-layer.md @@ -35,9 +35,9 @@ check_components! { } ``` -## The diagnostic +## The raw diagnostic -This is a **surfaced** class: the `help:` note names the concrete unmet leaf exactly as in a single-layer [check-trait failure](check-trait-failure.md), and the leaf tells you the offending field. What is specific to a higher-order provider is that the *rest* of the diagnostic differs by layer, so the two cases are told apart by shape rather than by the leaf alone. +This section describes what plain `cargo check` prints — the fallback when `cargo-cgp` is not on hand; [How cargo-cgp presents it](#how-cargo-cgp-presents-it) below covers the readable form. This is a **surfaced** class: the `help:` note names the concrete unmet leaf exactly as in a single-layer [check-trait failure](check-trait-failure.md), and the leaf tells you the offending field. What is specific to a higher-order provider is that the *rest* of the diagnostic differs by layer, so the two cases are told apart by shape rather than by the leaf alone. When the **inner** layer fails, the compiler prints **two** `E0277` blocks. The first is an intermediate block reporting that the inner provider does not implement its provider trait for the concrete context — `BaseArea: AreaCalculator` is not satisfied — carrying the [near-contradiction](../../guides/debugging.md) hint that `AreaCalculator<__Context__>` *is* implemented for `BaseArea` (the generic impl exists but is inapplicable here). The second block is the real one: `Rectangle: CanUseComponent` is not satisfied, its `help:` naming the missing `HasField`, its `unsatisfied trait bound introduced here` caret landing on the *inner* provider's `Self: HasBaseArea` clause, and its `required for …` chain running through `BaseArea`'s `IsProviderFor` and then — after a `1 redundant requirement hidden` note — through `ScaledArea`'s `IsProviderFor` to `CanUseComponent`. The chain passing through both providers' `IsProviderFor`, and the extra intermediate block, are the marks of an inner-layer failure. @@ -47,6 +47,10 @@ When the **outer** layer fails, the compiler prints a **single**, shorter `E0277 The root cause is **present** in the `help:` note of the `CanUseComponent` block in both cases, and the failing *layer* is present too, in two reinforcing places. The `unsatisfied trait bound introduced here` caret sits on the `where` clause of the provider whose dependency is unmet — the inner provider for an inner failure, the outer provider for an outer one — which is the most direct signal. The chain depth confirms it: an inner failure runs through *both* providers' `IsProviderFor` and carries the `1 redundant requirement hidden` note, while an outer failure stops at the outer provider's `IsProviderFor` and never names the inner one. The intermediate `Inner: ProviderTrait` block that appears only for the inner case is a third, weaker signal — its presence points inward, but it is also the noise a reader should skip past to reach the `CanUseComponent` block that carries the leaf. +## How cargo-cgp presents it + +`cargo-cgp` reshapes both cases into a single `[CGP-E001]` headline — `[CGP-E001] the consumer trait \`CanCalculateArea\` is not implemented for context \`Rectangle\`` — over one `root cause:` tree, and **encodes the failing layer in the shape of that tree** rather than leaving it to be read from chain depth and caret position. For the inner failure the tree bottoms out at `[CGP-E106] missing field \`base_area\`` beneath *two* stacked `[CGP-E102]` provider-trait hops — first `ScaledArea`, then the `BaseArea` it wraps — before reaching `[CGP-E105] HasBaseArea` and the field; the two provider hops are the wrapper relationship made explicit. For the outer failure the tree bottoms out at `[CGP-E106] missing field \`scale_factor\`` beneath a *single* `[CGP-E102]` hop (`ScaledArea`) with the inner provider never named, exactly mirroring the shorter raw block. The intermediate near-contradiction block that clutters the raw inner-layer output is dropped entirely, so the recovered form reads the same in both cases save for that one-hop-versus-two-hop depth. The codes are defined in the [cargo-cgp error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md). + ## Resolving it Fix the dependency at the layer the diagnostic points to. For the inner failure, supply the field the inner provider needs — add `base_area` to `Rectangle` so `BaseArea` implements `HasBaseArea`; for the outer failure, supply the field the outer provider needs — add `scale_factor` so `ScaledArea` implements `HasScaleFactor`. Wiring is unaffected: the stack `ScaledArea` is correct in both cases, and the fix is always to satisfy the named leaf, never to change the delegation. @@ -55,12 +59,12 @@ When a stack is deep enough that reading the chain is awkward, force the layer b ## Notes for tooling -For a `cargo-cgp`-style post-processor, the headline is again the leaf in the `CanUseComponent` block's `help:` note, but this class adds a second fact worth reporting: **the provider layer at fault**, recoverable as the provider named in the innermost `IsProviderFor` note whose `where` clause the `introduced here` caret sits on. A tool should present the two together — "`Rectangle` is missing field `base_area`, needed by the inner provider `BaseArea` (wrapped by `ScaledArea`)" — reconstructing the wrapper relationship from the `IsProviderFor` chain rather than dumping it. The intermediate `BaseArea: AreaCalculator` near-contradiction block should be suppressed as derived noise, exactly as in a [verbose cascade](verbose-cascade.md); it names only a provider trait and never reaches the field. Emulating `#[check_providers(...)]` — re-asserting `IsProviderFor` per layer — is the internal move a tool can make to localize the layer mechanically instead of parsing the chain. +`cargo-cgp` already does the work this class used to ask of a post-processor — it suppresses the intermediate near-contradiction block and reconstructs the wrapper relationship as the stacked `[CGP-E102]` hops of the tree. What it does *not* yet do is state the layer in words: the tree shows the wrapper structurally (two hops for an inner failure, one for an outer), but a reader still infers "inner versus outer" from the depth rather than reading a phrase like "needed by the inner provider `BaseArea`, wrapped by `ScaledArea`." A tool wanting to name the layer explicitly can emulate `#[check_providers(...)]`, re-asserting `IsProviderFor` per layer to localize it mechanically. ## Backing fixtures -- [acceptable/check_components/higher_order_inner_dependency.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/higher_order_inner_dependency.rs) — `ScaledArea` wired onto a `Rectangle` with `scale_factor` but no `base_area`, so the inner layer fails; its `.stderr` pins the two-block shape, the `HasField` leaf, the caret on `BaseArea`'s `Self: HasBaseArea`, and the chain through both providers' `IsProviderFor` with the `1 redundant requirement hidden` note. -- [acceptable/check_components/higher_order_outer_dependency.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/higher_order_outer_dependency.rs) — the mirror, with `base_area` but no `scale_factor`, so the outer layer fails; its `.stderr` pins the single, shorter block, the `HasField` leaf, the caret on `ScaledArea`'s `Self: HasScaleFactor`, and a chain that stops at the outer `IsProviderFor` without naming the inner provider. +- [`acceptable/providers/higher_order_inner_dependency.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/providers/higher_order_inner_dependency.rs) — `ScaledArea` wired onto a `Rectangle` with `scale_factor` but no `base_area`, so the inner layer fails; its `.rust.stderr` pins the two-block shape, the `HasField` leaf, the caret on `BaseArea`'s `Self: HasBaseArea`, and the chain through both providers' `IsProviderFor` with the `1 redundant requirement hidden` note, while its `.cgp.stderr` collapses that to one `[CGP-E001]` block whose `root cause: [CGP-E106] missing field \`base_area\`` tree carries *two* `[CGP-E102]` provider hops (`ScaledArea` then `BaseArea`). +- [`acceptable/providers/higher_order_outer_dependency.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/providers/higher_order_outer_dependency.rs) — the mirror, with `base_area` but no `scale_factor`, so the outer layer fails; its `.rust.stderr` pins the single, shorter block, the `HasField` leaf, the caret on `ScaledArea`'s `Self: HasScaleFactor`, and a chain that stops at the outer `IsProviderFor` without naming the inner provider, and its `.cgp.stderr` a `[CGP-E001]` block whose `root cause: [CGP-E106] missing field \`scale_factor\`` tree carries a *single* `[CGP-E102]` hop with the inner provider absent. ## Related diff --git a/docs/errors/checks/ordinary-trait-bound.md b/docs/errors/checks/ordinary-trait-bound.md index 71e91856..a628eb39 100644 --- a/docs/errors/checks/ordinary-trait-bound.md +++ b/docs/errors/checks/ordinary-trait-bound.md @@ -41,9 +41,9 @@ delegate_components! { The same failure arises wherever CGP or Rust accepts a generic trait bound, which is what makes the class broad. An ordinary bound on an **impl generic** in `delegate_components!` behaves identically: a generic context ` Wrapper` that wires its abstract type to `T` carries the provider's `Scalar: Eq` bound as `T: Eq`, unconditionally accepted, and checking `Wrapper` surfaces `f64: Eq` at that one instantiation. The bound may equally sit on an explicit `impl` generic in `#[cgp_impl]`, on a `#[cgp_fn]` `#[extend_where]` clause, or on a `for … in … where` loop — anywhere an ordinary bound rides on a parameter the context ultimately fills. CGP lowers the bound faithfully and cannot see that the wired type violates it, so it defers to the compiler. -## The diagnostic +## The raw diagnostic -This is a **surfaced** class: forcing the wiring through a [`check_components!`](../../reference/macros/check_components.md) produces an `E0277` that names the ordinary bound on the concrete type, and its shape is the tell that distinguishes it from a `HasField` check-trait failure. The **primary** error is the leaf itself — "the trait bound `f64: Eq` is not satisfied," with the caret on the checked component entry and the label "the trait `Eq` is not implemented for `f64`." Beneath it, a `help:` note lists the standard types that *do* implement the trait (`i128`, `i16`, `i32`, …), which is `rustc`'s stock "these types implement the trait" hint for a missing ordinary impl — not the near-contradiction "but trait `HasField` is implemented" hint a missing field produces. +This section describes what plain `cargo check` prints — the fallback when `cargo-cgp` is not on hand; [How cargo-cgp presents it](#how-cargo-cgp-presents-it) below covers the readable form. This is a **surfaced** class: forcing the wiring through a [`check_components!`](../../reference/macros/check_components.md) produces an `E0277` that names the ordinary bound on the concrete type, and its shape is the tell that distinguishes it from a `HasField` check-trait failure. The **primary** error is the leaf itself — "the trait bound `f64: Eq` is not satisfied," with the caret on the checked component entry and the label "the trait `Eq` is not implemented for `f64`." Beneath it, a `help:` note lists the standard types that *do* implement the trait (`i128`, `i16`, `i32`, …), which is `rustc`'s stock "these types implement the trait" hint for a missing ordinary impl — not the near-contradiction "but trait `HasField` is implemented" hint a missing field produces. The position of the cause is the sharp contrast with a [check-trait failure](check-trait-failure.md), and knowing it tells the two apart on sight. A missing `HasField` roots the *primary* error at `Ctx: CanUseComponent` and tucks the concrete leaf into a `help:` note below it; an unmet ordinary bound roots the primary error at the leaf (`f64: Eq`) directly, and the `CanUseComponent` obligation appears only as a `note` further down. Between them runs the same scaffolding: a `note: required for CompareScalars to implement IsProviderFor` that points at the `Scalar: Eq` bound with "unsatisfied trait bound introduced here", then `required for App to implement CanUseComponent<…>`, then the bound in the generated `__CheckApp` trait. [`IsProviderFor`](../../reference/traits/is_provider_for.md) is again the supertrait that carries the provider's `where` clause into the proof, which is why the ordinary bound is named here and suppressed in the hidden form. The whole thing is ordinary `rustc` obligation resolution for [`E0277`](../error_codes/e0277.md); only the leaf's *kind* — a standard trait on a concrete type — changes what the message and its `help:` look like. @@ -53,20 +53,26 @@ Exercised without a check — by calling the consumer method — the same broken The root cause is **present and sits at the very top** of the checked output: the primary `E0277` names the concrete type and the ordinary trait it fails (`f64: Eq`) outright. This is the most direct of the surfaced classes — there is no `help:`-note indirection as with a `HasField` leaf, and the `required for` notes below build *outward* from the leaf toward the check. The caret lands on the checked component entry, so the error also points at a site the user controls. The one thing the diagnostic does not state is which *wired type* is at fault when the bound is on an abstract type: the leaf names `f64`, and the `IsProviderFor` note names the `Scalar: Eq` bound, but connecting "`f64` is what `Scalar` resolves to" is left to the reader — trace it to the `ScalarTypeProviderComponent: UseType` wiring. +## How cargo-cgp presents it + +`cargo-cgp` treats this class differently from the CGP-capability failures, and the difference is instructive: because the raw primary error already names the ordinary bound at the top, `cargo-cgp` **keeps rustc's headline uncoded** — no `[CGP-Exxx]` tag is stamped, the header stays `error[E0277]: the trait bound \`f64: Eq\` is not satisfied`. What it rewrites is the scaffolding below. The `required for … IsProviderFor` / `CanUseComponent` / `__CheckApp` chain collapses into a compact dependency tree — `[CGP-E101]` consumer-trait hop → `[CGP-E102]` provider-trait hop → the leaf — where the ordinary bound rides as an uncoded pass-through (`the trait bound \`f64: Eq\` is not satisfied`) because it is a plain Rust trait, not a CGP construct the tool renders. This is the one surfaced check class with no `[CGP-E0xx]` headline: the tool leaves the already-clear `rustc` sentence alone and only tidies the frames beneath it. + +The contrast with the [hidden method-call form](../hidden/unsatisfied-dependency.md) sharpens the point. There the raw `E0599` buries the bound entirely, so `cargo-cgp` must *recover* it and does stamp a headline — `[CGP-E001]` over a `root cause: [CGP-E201] the trait bound \`f64: Eq\` is not satisfied` tree, `[CGP-E201]` being the dedicated root-cause lead for an ordinary bound whose terminal is uncoded. A code appears there because the tool reclassified a hidden error; none appears here because the surfaced error was already its own clearest statement. The codes are defined in the [cargo-cgp error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md). + ## Resolving it The fix is to satisfy the ordinary trait, which is a different remedy from every capability-dependency class — not "wire a component" or "add a field," but make the concrete type implement the trait. Wire the abstract type (or instantiate the generic) to a type that *does* satisfy the bound — an integer type for `Eq`, say, instead of `f64` — or, when the offending type is one you own, derive or implement the trait for it (`#[derive(Eq)]`, a manual `impl Ord`). When the bound is stricter than the behavior needs — requiring `Eq` where `PartialEq` would do — relax the provider's `where` clause instead, so more concrete types qualify. The diagnostic already names both halves: the concrete type in the primary error and the bound in the `IsProviderFor` note. ## Notes for tooling -For a `cargo-cgp`-style post-processor, this class is nearly self-describing, so the value is small and mostly connective. The headline to extract is already the primary error — the concrete type and the ordinary trait (`f64: Eq`) — and the tool should **link it to the wiring that chose the type** by following the abstract-type component to its `UseType<…>` entry, reporting "`Scalar` is wired to `f64`, which the provider `CompareScalars` requires to be `Eq`." The `IsProviderFor` / `CanUseComponent` / `__Check…` frames are CGP scaffolding to collapse, and `rustc`'s list of conforming standard types is worth keeping as the actionable hint. Recognizing that the leaf is an ordinary trait rather than a `HasField` lets the tool phrase the fix as "satisfy or relax the trait bound" rather than "wire a component." +One usability gap remains beyond what `cargo-cgp` already collapses: the tree stops at the ordinary bound (`f64: Eq`) but does not **link the concrete type back to the wiring that chose it**. Following the abstract-type component to its `UseType<…>` entry — to report "`Scalar` is wired to `f64`, which the provider `CompareScalars` requires to be `Eq`" — is the connective step still left to the reader, since the passed-through leaf names `f64` without saying it is what `Scalar` resolves to. ## Backing fixtures -- [acceptable/check_components/ordinary_bound_unsatisfied.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/ordinary_bound_unsatisfied.rs) — a provider requiring `Scalar: Eq` on a context that wires `Scalar` to `f64`, checked; its `.stderr` pins the primary `E0277` on `f64: Eq`, the `help:` list of conforming standard types, and the `IsProviderFor` note pointing at the `Scalar: Eq` bound. -- [acceptable/check_components/generic_context_ordinary_bound.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/generic_context_ordinary_bound.rs) — the same bound reached through impl generics: a generic ` Wrapper` table checked at `Wrapper`, surfacing `f64: Eq` through `IsProviderFor<…, Wrapper>`. +- [`acceptable/resolution/ordinary_bound_unsatisfied.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/resolution/ordinary_bound_unsatisfied.rs) — a provider requiring `Scalar: Eq` on a context that wires `Scalar` to `f64`, checked; its `.rust.stderr` pins the primary `E0277` on `f64: Eq`, the `help:` list of conforming standard types, and the `IsProviderFor` note pointing at the `Scalar: Eq` bound, while its `.cgp.stderr` keeps that same uncoded headline and rewrites only the chain into the `[CGP-E101]`/`[CGP-E102]` tree over the pass-through leaf. +- [`acceptable/generic/generic_context_ordinary_bound.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/generic/generic_context_ordinary_bound.rs) — the same bound reached through impl generics: a generic ` Wrapper` table checked at `Wrapper`, surfacing `f64: Eq` through `IsProviderFor<…, Wrapper>`, with the `.cgp.stderr` tree naming the context as `Wrapper`. -The hidden (method-call) form of the same mistake is [acceptable/delegate_components/ordinary_bound_unsatisfied.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/ordinary_bound_unsatisfied.rs), cataloged with the [hidden unsatisfied dependency](../hidden/unsatisfied-dependency.md) class it shares its shape with. +The hidden (method-call) form of the same mistake is [`acceptable/use-site/ordinary_bound_unsatisfied.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/use-site/ordinary_bound_unsatisfied.rs), cataloged with the [hidden unsatisfied dependency](../hidden/unsatisfied-dependency.md) class it shares its shape with; there the `.cgp.stderr` must recover the buried bound, so it does lead with `[CGP-E001]` and a `root cause: [CGP-E201]` tree. ## Related diff --git a/docs/errors/checks/unregistered-namespace-path.md b/docs/errors/checks/unregistered-namespace-path.md index 69a45326..75bff2f1 100644 --- a/docs/errors/checks/unregistered-namespace-path.md +++ b/docs/errors/checks/unregistered-namespace-path.md @@ -38,9 +38,9 @@ check_components! { The same shape appears whenever a redirect lands on an empty path: a context whose joined namespace does not inherit the base namespace a component was prefixed into, a `@`-path entry with a mistyped or too-short segment, or a component prefixed under one path but wired under another. In every case the redirect resolves to a table slot that holds nothing. -## The diagnostic +## The raw diagnostic -This is a **surfaced** class, and unusually the root cause *is the primary error* rather than a note beneath it. Forcing the wiring through a [`check_components!`](../../reference/macros/check_components.md) produces an `E0277` whose headline is that the *path* does not implement the namespace lookup trait — `PathCons>: DefaultNamespace` is not satisfied — with the caret on the checked `GreeterComponent` entry. The failing bound's `Self` is a `PathCons<…>` path, not a bare component marker, and that is the signature of the class: the compiler is reporting that the redirect target has no table entry. +This section describes what plain `cargo check` prints — the fallback when `cargo-cgp` is not on hand; [How cargo-cgp presents it](#how-cargo-cgp-presents-it) below covers the readable form. This is a **surfaced** class, and unusually the root cause *is the primary error* rather than a note beneath it. Forcing the wiring through a [`check_components!`](../../reference/macros/check_components.md) produces an `E0277` whose headline is that the *path* does not implement the namespace lookup trait — `PathCons>: DefaultNamespace` is not satisfied — with the caret on the checked `GreeterComponent` entry. The failing bound's `Self` is a `PathCons<…>` path, not a bare component marker, and that is the signature of the class: the compiler is reporting that the redirect target has no table entry. Two notes below the headline confirm the reading and are worth recognizing. A `note: required for App to implement DelegateComponent>` names the exact missing table entry — the path for which `App` has no delegate — and a `note: required for RedirectLookup> to implement IsProviderFor` names the [`RedirectLookup`](../../reference/providers/redirect_lookup.md) provider the redirect resolved to. A `Self` that is a `RedirectLookup` or a `PathCons` path, rather than a real provider struct, always means the failure is in the lookup, not in a provider's dependencies. @@ -54,6 +54,12 @@ The root cause is **present and sits at the top of the output**, in the primary Exercised without a check — by calling the consumer-trait method on the context — the same broken wiring instead produces the [hidden `E0599`](../hidden/unsatisfied-dependency.md): "the method `greet` exists for struct `App`, but its trait bounds were not satisfied," naming only `App: CanGreet`/`App: Greeter` and descending no further. That output is byte-for-byte the shape of the hidden unsatisfied-dependency class, because the compiler's method-probe heuristic drops the nested lookup bound exactly as it drops a nested dependency bound. So an unregistered path is hidden when reached by a call and surfaced when reached by a check, and only the check reveals which path is empty. +## How cargo-cgp presents it + +`cargo-cgp` recasts the raw lookup failure from the machinery's point of view to the programmer's: it keeps the `E0277` code but replaces the `PathCons<…>: DefaultNamespace` headline with `[CGP-E001] the consumer trait \`CanGreet\` is not implemented for context \`App\``, then states the empty path in a `root cause: [CGP-E107] context \`App\` does not contain any delegate entry for \`@app.GreeterComponent\`` note. The tree between them is short — `[CGP-E101]` consumer-trait hop → `[CGP-E104]` redirect-lookup hop naming the path → the `[CGP-E107]` leaf — so the `RedirectLookup` and `DelegateComponent` scaffolding of the raw form collapses into one readable "redirect lands on an empty slot" chain. The `PathCons` spine is resugared to its surface `@app.GreeterComponent` notation throughout, which is the single biggest readability win over the raw output, where the path is an abbreviated `PathCons>, _>`. + +The shape scales with how the redirect fails. A deep prefix renders its whole path (`@app.finance.types.QuantityTypeProviderComponent`); a chain of redirects that never terminates prints one `[CGP-E104]` hop per link before the `[CGP-E107]` leaf (`@start.GreeterComponent` → `@middle` → `@end`); and an `open` per-type dispatch missing a key names the type in the path (`@ItemEncoderComponent.Vec`). In every case the leaf is `[CGP-E107]` — the context wires no provider at the path the component routes to. The codes are defined in the [cargo-cgp error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md). + ## Resolving it The fix is to bind a provider at the path the component routes to. Register it from the provider's own definition with [`#[default_impl(@app.GreeterComponent in Namespace)]`](../../reference/traits/default_namespace.md), add a namespace **body** entry (`@app.GreeterComponent: GreetHello`) in the namespace's own crate, or wire the path directly on the context (`@app.GreeterComponent: GreetHello`) — whichever matches where the wiring belongs, per the [namespaces guide](../../guides/namespaces-and-prefixes.md). When the path itself is wrong — a prefix and a wiring that disagree on the path, or a joined namespace that does not inherit the base the component was prefixed into — the fix is to reconcile the two so the redirect and the binding name the same path. @@ -62,11 +68,14 @@ A related variant resolves to a delegate that exists but is not a provider for t ## Notes for tooling -For a `cargo-cgp`-style post-processor the fact to extract is the **unbound path**: decode the `PathCons` key of the failing `DefaultNamespace`/`DelegateComponent` bound back to its readable `@app.GreeterComponent` form and report "no provider is wired at `@app.GreeterComponent`, which `GreeterComponent` routes to through `DefaultNamespace`." The `RedirectLookup` and `CanUseComponent` frames in the `required for` chain are scaffolding to collapse, and the `help:` list of markers that *do* resolve is worth mining for the near-contradiction — if the bare marker is in that list, the tool can add "the component is registered but its path is empty; you likely forgot a `#[default_impl]` or body entry." Because the method-call form hides the cause as `E0599`, a tool that promotes hidden errors by synthesizing a check (see the [hidden class](../hidden/unsatisfied-dependency.md)) turns this from its hidden form into the surfaced diagnostic it can then decode. +`cargo-cgp` already extracts the unbound path — the `[CGP-E107]` leaf decodes the `PathCons` key back to `@app.GreeterComponent` and reports that the context wires nothing there. What remains for a tool is the **fix hint** the raw `help:` list makes available but `[CGP-E107]` does not spell out: because the bare component marker still resolves through the namespace while the path does not, the registration succeeded and only the leaf binding is missing — so the actionable advice "you likely forgot a `#[default_impl]` or body entry" could be added rather than left for the reader to infer. ## Backing fixtures -- [acceptable/cgp_namespace/unregistered_prefix_path.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/unregistered_prefix_path.rs) — a `#[prefix]`-ed `Greeter` joined through `DefaultNamespace` with no provider ever bound at `@app.GreeterComponent`; its `.stderr` pins the primary `E0277` on `PathCons: DefaultNamespace`, the `required for … DelegateComponent>` note that names the empty path, the `RedirectLookup … IsProviderFor` note, and the `help:` list in which the bare `GreeterComponent` marker appears. +- [`acceptable/resolution/unregistered_prefix_path.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/resolution/unregistered_prefix_path.rs) — a `#[prefix]`-ed `Greeter` joined through `DefaultNamespace` with no provider ever bound at `@app.GreeterComponent`; its `.rust.stderr` pins the primary `E0277` on `PathCons<…>: DefaultNamespace`, the `required for … DelegateComponent>` note that names the empty path, the `RedirectLookup … IsProviderFor` note, and the `help:` list in which the bare `GreeterComponent` marker appears, while its `.cgp.stderr` recasts this as `[CGP-E001]` over a `root cause: [CGP-E107] … @app.GreeterComponent` tree with a `[CGP-E104]` redirect hop. +- [`acceptable/wiring/namespace-paths/qualified_prefix_path.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/namespace-paths/qualified_prefix_path.rs) — a deep prefix whose `[CGP-E107]` leaf renders the full `@app.finance.types.QuantityTypeProviderComponent` path, pinning that the resugaring reproduces multi-segment paths intact. +- [`acceptable/wiring/namespace-paths/multi_redirect_missing.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/namespace-paths/multi_redirect_missing.rs) — a chain of redirects that never terminates, so the `.cgp.stderr` tree stacks one `[CGP-E104]` hop per link (`@start.GreeterComponent` → `@middle` → `@end`) before the `[CGP-E107]` leaf at `@end`. +- [`acceptable/wiring/namespace-paths/open_missing_type_key.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/namespace-paths/open_missing_type_key.rs) — an `open` per-type dispatch missing a type key, where the `[CGP-E107]` leaf names the type in the path (`@ItemEncoderComponent.Vec`) and the consumer headline is the generic `CanEncodeItem>`. ## Related diff --git a/docs/errors/checks/verbose-cascade.md b/docs/errors/checks/verbose-cascade.md index 65bb655a..c8304f2b 100644 --- a/docs/errors/checks/verbose-cascade.md +++ b/docs/errors/checks/verbose-cascade.md @@ -30,9 +30,9 @@ check_components! { } ``` -## The diagnostic +## The raw diagnostic -The output is a cascade whose block count reflects the depth of the graph, not the number of mistakes. Checking three chained components over one missing field produces **six** `E0277` blocks, not three, and they are of two kinds. Each checked component yields a `CanUseComponent<…Component>` block that *does* reach the concrete root cause — its `help:` note names `HasField` as not implemented for `App` (and points at the nearest existing field, `HasField`, as a landmark). But the deeper components additionally emit **intermediate** blocks — `ProvideBar: Bar` is not satisfied, `ProvideFoo: Foo` is not satisfied — that name only an inner provider trait and never descend to the field. The intermediate blocks are the noise; the `CanUseComponent` blocks are the signal. +This section describes what plain `cargo check` prints — the fallback when `cargo-cgp` is not on hand; [How cargo-cgp presents it](#how-cargo-cgp-presents-it) below covers the reshaped form. The output is a cascade whose block count reflects the depth of the graph, not the number of mistakes. Checking three chained components over one missing field produces **six** `E0277` blocks, not three, and they are of two kinds. Each checked component yields a `CanUseComponent<…Component>` block that *does* reach the concrete root cause — its `help:` note names `HasField` as not implemented for `App` (and points at the nearest existing field, `HasField`, as a landmark). But the deeper components additionally emit **intermediate** blocks — `ProvideBar: Bar` is not satisfied, `ProvideFoo: Foo` is not satisfied — that name only an inner provider trait and never descend to the field. The intermediate blocks are the noise; the `CanUseComponent` blocks are the signal. Two pieces of `rustc` machinery shape the cascade and are worth recognizing so they are not misread. Each intermediate block carries the [near-contradiction](../../guides/debugging.md) shape — `help: the trait Bar is not implemented for ProvideBar` immediately followed by `help: the trait Bar<__Context__> is implemented for ProvideBar` — which is `rustc`'s "a similar impl exists" hint reporting that the *generic* provider impl (over `__Context__`) exists but does not hold for the concrete `App`, because a bound deeper in its chain fails. It does not mean the impl is both present and absent; it means the impl is present but inapplicable here. And the longer chains end with `= note: N redundant requirements hidden`, `rustc` collapsing repeated identical obligations it has already printed, so the elision is a sign the chain revisits the same bound, not that a distinct cause was omitted. @@ -42,17 +42,24 @@ The hidden analogue exists too: exercising the same chain through direct consume The root cause is **present and repeated** — the same `HasField` appears in every `CanUseComponent` block's `help:` note. The reliable way to find it is not positional but structural: **look for a block whose `help:` names a concrete missing item — a `HasField`, an abstract type — rather than a provider trait like `ProvideFoo: Foo`.** Those concrete-item blocks all point at the one thing to fix; the provider-trait blocks are intermediate consequences. When components are checked top-down (foundational last, as in the fixture), the final block is a clean concrete-item block, which is why the root cause is *often* found near the last or second-to-last message — but the concrete-item test is what makes it reliable regardless of order. +## How cargo-cgp presents it + +`cargo-cgp` cuts the volume roughly in half but does not yet collapse it to one line, which is why this class's fixture sits in `usability/` rather than `acceptable/` — the cause is recovered but still repeated. The tool **drops every intermediate provider-trait block** as derived noise and recovers each *checked* component's block to a `[CGP-E001]`-headed `root cause:` tree that descends all the way to the leaf, so a six-block raw cascade over three checked components becomes three blocks, each carrying an identical `root cause: [CGP-E106] missing field \`name\`` tree (one first block may instead head with `[CGP-E002]` when rustc chose the provider-side `ProvideFoo: Foo` failure as that entry's primary). Within each surviving block the recovery is complete — the tree threads the full `[CGP-E101]`→`[CGP-E102]` chain down to the field — so a reader no longer has to apply the concrete-item test to sort signal from noise. + +What `cargo-cgp` does *not* do is coalesce those identical per-component root causes into a single headline: three checked components that all bottom out on the one missing `name` field still print three trees naming it. That last deduplication — recognizing that several checked components share one leaf and reporting "`App` is missing field `name`, needed by `ProvideFoo` (via `Bar`, `Baz`)" once — is the remaining usability gap this fixture pins. The codes are defined in the [cargo-cgp error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md). + ## Resolving it Fix the single root cause and the entire cascade collapses at once — supply the `name` field, and all six blocks disappear together. The difficulty is isolation, not repair, so the [debugging guide](../../guides/debugging.md) prescribes two moves: **check one suspect component in isolation** in its own `check_components!` block, which strips the cascade down to that component's chain, or **bisect** a large table by commenting entries out until the failure vanishes. Both converge on the one broken link far faster than reading the cascade top to bottom. ## Notes for tooling -For a `cargo-cgp`-style post-processor, this class is the strongest case for **deduplication**: collapse the whole cascade to its distinct root causes. The rule is to keep the blocks whose `help:` names a concrete unimplemented item, coalesce the ones naming the same item into a single reported cause, and drop the intermediate provider-trait blocks entirely as derived noise. A cascade of six blocks over one missing field should be presented as a single headline — "`App` is missing field `name`, needed by `ProvideFoo` (via `Bar`, `Baz`)" — with the dependency path reconstructed from the `required for` chains rather than dumped. Leading with the innermost concrete cause, not the first block printed, is the whole value a tool adds here. +`cargo-cgp` already performs the first half of the deduplication this class calls for — it drops the intermediate provider-trait blocks and leads each surviving block with the innermost concrete cause. The **cross-component coalescing** is what remains: collapsing several checked components that share one leaf into a single reported cause, so a cascade whose three trees all name the missing `name` field prints one headline — "`App` is missing field `name`, needed by `ProvideFoo` (via `Bar`, `Baz`)" — instead of three. Closing that gap is what would move this class's fixture from `usability/` to `acceptable/`. ## Backing fixtures -- [acceptable/check_components/dependency_cascade.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/dependency_cascade.rs) — a three-deep provider chain over one missing `name` field, checked across all three components; its `.stderr` pins the six-block cascade, the concrete `HasField` in each `CanUseComponent` block's `help:` note, and the intermediate `ProvideFoo: Foo` / `ProvideBar: Bar` noise blocks. +- [`usability/duplication/dependency_cascade.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/usability/duplication/dependency_cascade.rs) — a three-deep provider chain over one missing `name` field, checked across all three components; its `.rust.stderr` pins the six-block cascade, the concrete `HasField` in each `CanUseComponent` block's `help:` note, and the intermediate `ProvideFoo: Foo` / `ProvideBar: Bar` noise blocks, while its `.cgp.stderr` shows the three surviving per-component blocks, each with an identical `root cause: [CGP-E106] missing field \`name\`` tree (the first headed `[CGP-E002]`, the rest `[CGP-E001]`) — the `usability/` placement recording that the identical causes are not yet coalesced. +- [`usability/duplication/density_3.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/usability/duplication/density_3.rs) — a denser two-component check over one missing `height` field where the second component (`CanCalculateDensity`) depends transitively on the first (`CanCalculateArea`); its `.cgp.stderr` pins the same per-component recovery, the deeper block's tree threading `[CGP-E101]`/`[CGP-E102]` hops through the intermediate consumer before reaching the shared `[CGP-E106]` leaf. ## Related diff --git a/docs/errors/error_codes/README.md b/docs/errors/error_codes/README.md index 5f8d0886..5344c546 100644 --- a/docs/errors/error_codes/README.md +++ b/docs/errors/error_codes/README.md @@ -6,7 +6,7 @@ This directory is a compact reference for the handful of `rustc` error codes CGP The per-class documents in the catalog each explain one CGP failure, and several of them lean on the same underlying Rust rule — coherence, the orphan rule, the trait-solver recursion limit. Rather than restate that rule in every class that touches it, each code has one entry here that states it once, grounded in the official Rust documentation and the RFCs and issues that define it, and the class documents link to that entry. This keeps the Rust-language facts in one verified place and the CGP-specific anatomy in the class docs, so neither drifts and neither repeats the other. -Each entry records the same four things: the message `rustc` prints, when the compiler emits it in ordinary Rust, the rule it enforces and where that rule is defined, and which CGP error classes produce it. The facts here are verified against the official [error index](https://doc.rust-lang.org/error_codes/), the [Rust reference](https://doc.rust-lang.org/reference/), and the linked RFCs and `rust-lang/rust` issues, not against memory; the class documents remain the source of truth for the *exact* diagnostic a CGP mistake produces, pinned by their `.stderr` fixtures. +Each entry records the same four things: the message `rustc` prints, when the compiler emits it in ordinary Rust, the rule it enforces and where that rule is defined, and which CGP error classes produce it. The facts here are verified against the official [error index](https://doc.rust-lang.org/error_codes/), the [Rust reference](https://doc.rust-lang.org/reference/), and the linked RFCs and `rust-lang/rust` issues, not against memory; the class documents remain the source of truth for the *exact* diagnostic a CGP mistake produces, pinned by their [`cargo-cgp` UI fixtures](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/README.md). ## The codes @@ -31,6 +31,10 @@ Three are **name-resolution and method-probe** diagnostics: - [`E0576`](e0576.md) — an associated item is named that the trait or type does not declare. - [`E0599`](e0599.md) — a method exists but its trait bounds are not satisfied. +Beyond the `rustc` codes, `cargo-cgp` stamps its own `[CGP-Exxx]` codes on a message it rewrites into a recognized CGP class. Those are `cargo-cgp`'s, not CGP's, so they are cataloged in one pointer entry rather than mixed in above: + +- [cargo-cgp-codes.md](cargo-cgp-codes.md) — the headline (`CGP-E0xx`), dependency-tree (`CGP-E1xx`), and root-cause-lead (`CGP-E2xx`) codes, each with a one-line meaning and a link to `cargo-cgp`'s authoritative [error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md). + ## Relationship to the rest of the catalog These entries are a supporting reference, not a class of error: they describe Rust, and the [class documents](../README.md) describe CGP. A code entry's "Where CGP produces it" section links out to every class that emits it, and each class links back to the code entry in place of the raw `doc.rust-lang.org` URL, so the official links live here and the classes cite this directory. The rules for keeping these entries accurate — verifying wording against a real compilation and the official docs rather than memory — are the same [synchronization rules](../AGENTS.md) that govern the whole catalog. diff --git a/docs/errors/error_codes/cargo-cgp-codes.md b/docs/errors/error_codes/cargo-cgp-codes.md new file mode 100644 index 00000000..f69d644e --- /dev/null +++ b/docs/errors/error_codes/cargo-cgp-codes.md @@ -0,0 +1,50 @@ +# cargo-cgp `[CGP-Exxx]` codes + +This entry catalogs the `[CGP-Exxx]` codes [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp) stamps on a diagnostic it rewrites into a recognized CGP class, so a class document's *How cargo-cgp presents it* section can cite a code by number. Unlike the sibling `rustc`-code entries, these codes are **owned by `cargo-cgp`, not by CGP** — this file is a pointer, and the authoritative definitions (what triggers each code and how to fix it) live in `cargo-cgp`'s [error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md). When the two disagree, that catalog wins; keep this list in sync with it under the [cross-project rule](../../../AGENTS.md#the-two-projects-cgp-and-cargo-cgp). + +## What a code is + +A code is `CGP-E` plus three digits, shown in square brackets at the start of a rewritten message — for example `error[E0277]: [CGP-E001] the consumer trait \`CanCalculateArea\` is not implemented for context \`Rectangle\``. The diagnostic keeps its own `rustc` code (`E0277`, `E0599`, `E0271`, `E0275`), so `rustc --explain` still works and the entries in this directory still apply; the `[CGP-Exxx]` tag rides *inside* the message text. A code is assigned only when `cargo-cgp` both rewrote the message and identified it as a CGP class — everything else is left uncoded by design. + +The three-digit space is split by *what* a code classifies. + +## Main-message headlines — `CGP-E0xx` + +These lead a rewritten main message. `E001`–`E003` and `E009` come from the typed resolver and carry a `root cause:` note; `E004`–`E008` classify a duplicate-key `E0119` conflict (no root-cause note, both `rustc` carets kept); `E010` is a wiring-overflow rewrite of `E0275`. + +- **CGP-E001** — the consumer trait is not implemented for the context (missing wiring, or an unmet transitive dependency). +- **CGP-E002** — the provider trait is not implemented for the provider (its impl-side `where` bounds do not hold). +- **CGP-E003** — a context field has the wrong type (the `HasField` bound holds but its `::Value` projection fails). +- **CGP-E004** — duplicate wiring: the same key mapped twice. +- **CGP-E005** — overlapping wiring: a key already set through another (generic over specific, `@`-path over a namespace forwarding, or a prefix path). +- **CGP-E006** — multiple namespaces used for one target in `delegate_components!`. +- **CGP-E007** — redirect collision: a direct wiring collides with a redirect of the same key. +- **CGP-E008** — duplicate redirect: the same key redirected more than once. +- **CGP-E009** — a hand-written wrapper trait (not a CGP consumer) fails because a CGP component it depends on fails. +- **CGP-E010** — the wiring never resolves (an `E0275` overflow whose requirement is a `CanUseComponent` bound; usually a `UseContext` cycle). + +## Dependency-tree entries — `CGP-E1xx` + +One per rendering template, riding at the start of each entry in a `root cause:` dependency tree. + +- **CGP-E101** — consumer trait impl hop. +- **CGP-E102** — provider trait impl hop. +- **CGP-E103** — non-terminal `HasField` accessor hop. +- **CGP-E104** — redirect-lookup hop (a namespace or `open` `RedirectLookup`). +- **CGP-E105** — a hop through any other trait impl (a user capability, or an ordinary bound restated). +- **CGP-E106** — leaf: a genuinely missing context field. +- **CGP-E107** — leaf: the context wires no provider for a component (or terminates no namespace path). +- **CGP-E108** — leaf: the struct has the field but did not derive `HasField`. +- **CGP-E109** — leaf: a field has the wrong type. +- **CGP-E110** — leaf: a non-context delegation table (aggregate provider, `UseDelegate`/`UseInputDelegate`) is missing a key. +- **CGP-E111** — leaf: a non-provider was wired into a provider slot. + +## Root-cause leads — `CGP-E2xx` + +The `root cause:` line reuses the terminal leaf's `CGP-E1xx` code where it has one; this range exists for the one case that needs its own. + +- **CGP-E201** — the failure bottoms out on an ordinary (non-CGP) trait bound. + +## Where CGP classes cite these + +A class document's *How cargo-cgp presents it* section names the specific code(s) the tool produces for that class and links to the [authoritative catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md). For example, [check-trait-failure](../checks/check-trait-failure.md) is stamped `[CGP-E001]` with a `root cause:` tree whose leaf is `[CGP-E106]` (missing field) or `[CGP-E108]` (underived); the [conflicting-wiring](../wiring/conflicting-wiring.md) family maps to `[CGP-E004]`–`[CGP-E008]`; and [wiring-cycle](../wiring/wiring-cycle.md) maps to `[CGP-E010]`. diff --git a/docs/errors/hidden/unsatisfied-dependency.md b/docs/errors/hidden/unsatisfied-dependency.md index b03f0568..402c6070 100644 --- a/docs/errors/hidden/unsatisfied-dependency.md +++ b/docs/errors/hidden/unsatisfied-dependency.md @@ -46,9 +46,9 @@ fn main() { The same hiding occurs, in a slightly different shape, when the consumer trait is used as a bound — `fn use_it(c: &C)` called on a `Person` — rather than as a direct method call. Both exercise the broken wiring through the consumer trait, and both hide the cause; the difference is only the error code. -## The diagnostic +## The raw diagnostic -This is a **hidden** class: the compiler prints a short diagnostic that names the traits it could not satisfy but never descends to the dependency that failed. Calling the method directly produces an `E0599` — "the method `greet` exists for struct `Person`, but its trait bounds were not satisfied" — whose notes state that `Person` does not satisfy `Person: CanGreet` or `Person: Greeter` and that the trait `Greeter` must be implemented, and then stop. Using the consumer trait as a `where` bound instead produces an `E0277` reporting that `Person: CanGreet` is not satisfied, again without expanding why. In neither shape does the output mention `HasName` or the missing `HasField` — the real cause is absent, not merely buried. +This section describes what plain `cargo check` prints — the fallback when `cargo-cgp` is not on hand; [How cargo-cgp presents it](#how-cargo-cgp-presents-it) below covers the recovered form, which for this class is not a reformat but a genuinely different output. This is a **hidden** class: the compiler prints a short diagnostic that names the traits it could not satisfy but never descends to the dependency that failed. Calling the method directly produces an `E0599` — "the method `greet` exists for struct `Person`, but its trait bounds were not satisfied" — whose notes state that `Person` does not satisfy `Person: CanGreet` or `Person: Greeter` and that the trait `Greeter` must be implemented, and then stop. Using the consumer trait as a `where` bound instead produces an `E0277` reporting that `Person: CanGreet` is not satisfied, again without expanding why. In neither shape does the output mention `HasName` or the missing `HasField` — the real cause is absent, not merely buried. The `E0599` form carries one more piece of misdirection worth naming so a reader discounts it. Alongside the unsatisfied-bound notes, the compiler adds "this is an associated function, not a method," reports `greet` under "found the following associated functions," and suggests rewriting the call as `Person::greet()`. This is an artifact of how `#[cgp_component]` lowers the method: the provider trait's `greet(context: &Context)` has no `self` receiver, so the method probe classifies it as an associated function. The suggestion is wrong — `Person::greet()` drops the required context argument — and it says nothing about the missing dependency; it is noise the class produces, not a clue. @@ -60,6 +60,12 @@ The same `E0599`/`E0277` shape, and the same absence of a cause, also arises whe The root cause is **not present in the output.** A reader or tool must not scan this diagnostic for the failing dependency, because the compiler discarded it before printing. The most specific thing the output names is the unsatisfied provider trait (`Person: Greeter`) or consumer trait (`Person: CanGreet`); everything below that — the missing field, the unmet `HasName`, any deeper transitive bound — is gone. This absence is the defining property of the class and the reason it lives apart from the [surfaced classes](../checks/), whose diagnostics *do* carry the cause. +## How cargo-cgp presents it + +`cargo-cgp` is the reason this class need not stay hidden, and it is the one class where the tool does more than reshape existing text: its next-generation trait solver **recovers the cause the raw `E0599` discarded.** Where plain `cargo check` prints a diagnostic that bottoms out at `Person: Greeter` and names no dependency, `cargo-cgp` keeps the Rust code (`E0599` at a call site, `E0277` at a `where` bound) but rewrites the headline to `[CGP-E001] the consumer trait \`CanGreet\` is not implemented for context \`Person\`` and attaches a `root cause:` note — a `[CGP-E106] missing field \`name\` on \`Person\`` leaf over a compact dependency tree that the raw output never contained at all: `[CGP-E101]` consumer-trait hop → `[CGP-E102]` provider-trait hop → `[CGP-E105]` the `HasName` capability → the `[CGP-E106]` field leaf. The method-probe misdirection — "this is an associated function, not a method" and the wrong `Person::greet()` suggestion — is dropped, so the recovered cause leads instead of the noise. + +The leaf the tree bottoms out on names the actual mistake, and it varies with the suppressed dependency's kind. A genuinely absent field is `[CGP-E106]`; a struct that never derived `HasField` coalesces to `[CGP-E108]`; a component the context never wired, or an [unregistered namespace path](../checks/unregistered-namespace-path.md), is `[CGP-E107] does not contain any delegate entry`. When the buried leaf is an [ordinary Rust trait bound](../checks/ordinary-trait-bound.md) such as `Scalar: Eq`, the tree's terminal passes the bound through in rustc's own words and the `root cause:` lead takes `[CGP-E201]` instead. In every case the recovered result matches, byte for byte, what a [check-trait failure](../checks/check-trait-failure.md) produces for the same mistake — which is exactly how `cargo-cgp` recovers it, by promoting the hidden bound to the direct one the solver will trace. The codes are defined in the [cargo-cgp error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md). + ## Resolving it The underlying fix is ordinary: give the context what the provider needs — add the `name` field to `Person`, or wire the getter component to an existing field — so `Person` satisfies `HasName` and `GreetHello` becomes a valid provider. The difficulty is not the fix but *finding* it, since the error points nowhere useful. @@ -78,12 +84,12 @@ The check asserts `CanUseComponent` for `Person`, which requir ## Notes for tooling -A `cargo-cgp`-style post-processor **cannot extract the root cause from this diagnostic**, because the compiler never computed it into the printed output. Recognizing the class is straightforward: an `E0599` "method exists but its trait bounds were not satisfied" whose unmet bound is a provider trait of the form `Context: SomeProvider`, or an `E0277` on a consumer trait backed by a `…Component` blanket impl, with no note descending past that bound. Recovering the cause requires one of two moves. The tool can **promote and re-compile**: synthesize a `check_components!` (or an equivalent `CanUseComponent` assertion) for the failing component and compile that, then surface the resulting root cause in place of `rustc`'s unhelpful output. Or it can reach into compiler internals through `rustc_driver`, the way Clippy layers on the compiler, to inspect the fulfillment errors the trait solver produced but the diagnostic heuristic suppressed. Either way, the tool's job for this class is not to reformat the output but to *replace* it with the cause the output omits. +The move this class needs — inspecting the fulfillment errors the trait solver produced but the diagnostic heuristic suppressed, rather than reformatting text that never held the cause — is exactly what `cargo-cgp`'s typed resolver already performs (above), which is why this class is *replaced* rather than reshaped. What remains useful to a plainer tool, or to a reader on the raw fallback, is recognizing the class without the cause: an `E0599` "method exists but its trait bounds were not satisfied" whose unmet bound is a provider trait of the form `Context: SomeProvider`, or an `E0277` on a consumer trait backed by a `…Component` blanket impl, with no note descending past that bound. A text-only post-processor cannot recover the cause here — only re-driving the solver (or promoting to a check) can. ## Backing fixtures -- [acceptable/delegate_components/missing_dependency.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/missing_dependency.rs) — `GreetHello` requires `Self: HasName`, `Person` lacks the `name` field, and the method is called directly; its `.stderr` pins the `E0599` shape that names `Person: Greeter` without descending to the missing field. Its checked counterpart, [acceptable/check_components/missing_dependency.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_dependency.rs), pins the surfaced `E0277` for the same mistake and belongs to the [check-trait failure](../checks/check-trait-failure.md) class. -- [acceptable/delegate_components/ordinary_bound_unsatisfied.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/ordinary_bound_unsatisfied.rs) — the same hidden `E0599` shape where the suppressed leaf is an *ordinary* trait bound (`Scalar: Eq`, with `f64` wired) rather than a `HasField`, confirming the heuristic drops the nested bound regardless of its kind. Its surfaced counterpart is the [unsatisfied ordinary trait bound](../checks/ordinary-trait-bound.md) class. +- [`acceptable/use-site/missing_dependency.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/use-site/missing_dependency.rs) — `GreetHello` requires `Self: HasName`, `Person` lacks the `name` field, and the method is called directly; its `.rust.stderr` pins the raw `E0599` that names `Person: Greeter` (plus the method-probe advice) without descending to the missing field, and its `.cgp.stderr` the recovered `[CGP-E001]` headline over a `root cause: [CGP-E106] missing field \`name\`` tree. Its checked counterpart, [`acceptable/fields/missing_dependency.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/fields/missing_dependency.rs), reaches the same mistake through a check and belongs to the [check-trait failure](../checks/check-trait-failure.md) class. +- [`acceptable/use-site/ordinary_bound_unsatisfied.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/use-site/ordinary_bound_unsatisfied.rs) — the same hidden `E0599` where the suppressed leaf is an *ordinary* trait bound (`Scalar: Eq`, with `f64` wired) rather than a `HasField`, confirming the heuristic drops the nested bound regardless of its kind; its `.cgp.stderr` recovers the cause as a `[CGP-E001]` headline over a `root cause: [CGP-E201] the trait bound \`f64: Eq\` is not satisfied` tree. Its surfaced counterpart is the [unsatisfied ordinary trait bound](../checks/ordinary-trait-bound.md) class. ## Related diff --git a/docs/errors/lowering/ill-formed-generated-type.md b/docs/errors/lowering/ill-formed-generated-type.md index 67d1715c..dde709ce 100644 --- a/docs/errors/lowering/ill-formed-generated-type.md +++ b/docs/errors/lowering/ill-formed-generated-type.md @@ -17,9 +17,9 @@ pub trait HasItems { The shared `parse_field_type` applies the `Option<&T>` rule, reading an `Option` field whose `T` is the slice `[u8]`, so the generated `HasField` bound names the unsized `Option<[u8]>`. Because `[u8]` is a dynamically sized type, `Option<[u8]>` is ill-formed, and the compiler rejects it. This boundary is shared by [`#[cgp_auto_getter]`](../../reference/macros/cgp_auto_getter.md), [`#[cgp_getter]`](../../reference/macros/cgp_getter.md), and a [`#[cgp_fn]`](../../reference/macros/cgp_fn.md) [`#[implicit]`](../../guides/reading-context-fields.md) argument, since all three lower field types through `parse_field_type`. CGP is working as designed: the shorthands ease the common single-shape cases, and an unsupported combination is deferred to the compiler rather than given a bespoke rule — an [acceptable failure](../../implementation/AGENTS.md), not a defect. -## The diagnostic +## The raw diagnostic -This is a **surfaced** class, and unusually the root cause is the *primary* error, stated in plain terms. The compiler reports **`E0277`** in its dedicated `Sized` form — "the size for values of type `[u8]` cannot be known at compilation time," with the label "doesn't have a size known at compile-time" and a `help:` note "the trait `Sized` is not implemented for `[u8]`." A following `note: required by an implicit Sized bound in Option` names *where* the size is required: the `T` in `Option` carries an implicit `Sized` bound, and `[u8]` cannot meet it. The caret lands on the macro attribute (`#[cgp_auto_getter]`) because the offending type is synthesized in the expansion, and a closing note attributes the error to that attribute macro. +This section describes what plain `cargo check` prints — the fallback when `cargo-cgp` is not on hand; [How cargo-cgp presents it](#how-cargo-cgp-presents-it) below covers the readable form. This is a **surfaced** class, and unusually the root cause is the *primary* error, stated in plain terms. The compiler reports **`E0277`** in its dedicated `Sized` form — "the size for values of type `[u8]` cannot be known at compilation time," with the label "doesn't have a size known at compile-time" and a `help:` note "the trait `Sized` is not implemented for `[u8]`." A following `note: required by an implicit Sized bound in Option` names *where* the size is required: the `T` in `Option` carries an implicit `Sized` bound, and `[u8]` cannot meet it. The caret lands on the macro attribute (`#[cgp_auto_getter]`) because the offending type is synthesized in the expansion, and a closing note attributes the error to that attribute macro. Recognizing the exact message form matters, because rustc special-cases the missing-`Sized` case rather than printing a generic trait-bound line. It does **not** say "the trait bound `[u8]: Sized` is not satisfied"; it says "the size for values of type `[u8]` cannot be known at compilation time" and names `Sized` only in the `help:` note. The underlying unmet trait is still `Sized` under error code `E0277`, but the headline is the size-specific phrasing. This is the standard `rustc` diagnostic for a [dynamically sized type](https://doc.rust-lang.org/reference/dynamically-sized-types.html) reaching a position that requires a statically known size — a generic type parameter like `Option`'s `T`, which carries an implicit `Sized` bound unless written `T: ?Sized`. @@ -29,17 +29,17 @@ A second error usually follows and is derived noise, not a separate cause. The g The root cause is **present and sits at the top of the output**, in the primary `E0277` and its `help:` note naming `Sized` for the concrete unsized type. This is among the clearest diagnostics in the catalog: the compiler states exactly which type has no known size and where the size is required, so there is no note chain to walk and nothing suppressed. The `E0599` on `as_ref` below it is a derived consequence to skip. What the raw message does not supply is the CGP-specific reading — that the unsized type came from an *unsupported shorthand combination* the macro lowered literally, and that the fix is to change the field type rather than the wiring — because the compiler sees only the synthesized type, not the shorthand the user wrote. -## Resolving it +## How cargo-cgp presents it -Change the field or argument type to a shape a single shorthand supports, so the macro lowers it to a well-formed bound. Use an owned optional container instead of an optional slice — `Option>` (returned by reference, `Option<&Vec>`, or by value) or `Option<&'static [u8]>` where a borrow of static data fits — rather than `Option<&[u8]>`, whose lowering names the unsized `Option<[u8]>`. When neither shorthand fits the shape you need, write the getter or accessor by hand: the macros only save boilerplate over a plain `HasField` impl, so a hand-written trait impl can name whatever well-formed type the field actually has. The rule to carry away is that the shorthands compose only where their single-shape lowering yields a `Sized` type; a combination that would produce a dynamically sized type is out of scope by design. +`cargo-cgp` does not yet rewrite this class — it passes rustc's diagnostic through unchanged. For the fixture, the tool's `.cgp.stderr` is byte-for-byte its raw `.rust.stderr`: the primary `E0277` "the size for values of type `[u8]` cannot be known at compilation time", its `Sized` `help:` note, the "required by an implicit `Sized` bound in `Option`" note, and the trailing `E0599` on `as_ref` — no `[CGP-Exxx]` code stamped, no note suppressed. The pass-through is why the fixture sits in cargo-cgp's `usability/` tier: the cause is present and stated plainly, but two things bury it for a CGP reader. The caret points at the `#[cgp_auto_getter]` attribute rather than at any type the user wrote, because the offending `Option<[u8]>` is synthesized in the expansion; and the derived `E0599` on `as_ref` adds noise below the real error. The improvement left for the tool is translation, not recovery — suppress the derived `E0599`, and read the synthesized `Option<[u8]>` back to its cause, that the field type combined the `Option<&T>` and `&[T]` shorthands into a shape CGP has no rule for. The codes cargo-cgp stamps on the classes it does rewrite are defined in the [cargo-cgp error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md). -## Notes for tooling +## Resolving it -For a `cargo-cgp`-style post-processor, the fact to extract is already the headline — the concrete unsized type and the `Sized` requirement — so the tool's job is **translation, not recovery**: recognize an `E0277` `Sized` error (and its trailing `as_ref`/accessor `E0599`) whose caret is a getter or implicit-argument macro, and report "the field type combines shorthands into the unsized `Option<[u8]>`; CGP has no rule for `Option<&[T]>` — use an owned container or write the accessor by hand." Suppressing the derived `E0599` and pointing at the shorthand rather than the synthesized `Option<[u8]>` is the whole value added; the cause itself needs no compiler-internal introspection. +Change the field or argument type to a shape a single shorthand supports, so the macro lowers it to a well-formed bound. Use an owned optional container instead of an optional slice — `Option>` (returned by reference, `Option<&Vec>`, or by value) or `Option<&'static [u8]>` where a borrow of static data fits — rather than `Option<&[u8]>`, whose lowering names the unsized `Option<[u8]>`. When neither shorthand fits the shape you need, write the getter or accessor by hand: the macros only save boilerplate over a plain `HasField` impl, so a hand-written trait impl can name whatever well-formed type the field actually has. The rule to carry away is that the shorthands compose only where their single-shape lowering yields a `Sized` type; a combination that would produce a dynamically sized type is out of scope by design. ## Backing fixtures -- [acceptable/cgp_auto_getter/option_slice.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_auto_getter/option_slice.rs) — a `#[cgp_auto_getter]` returning `Option<&[u8]>`, lowered to a `HasField` bound over the unsized `Option<[u8]>`; its `.stderr` pins the primary `E0277` "the size for values of type `[u8]` cannot be known at compilation time" with the `Sized` `help:` note, the "required by an implicit `Sized` bound in `Option`" note, and the derived `E0599` on `as_ref`. +- [`usability/lowering/option_slice.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/usability/lowering/option_slice.rs) — a `#[cgp_auto_getter]` returning `Option<&[u8]>`, lowered to a `HasField` bound over the unsized `Option<[u8]>`; its `.rust.stderr` pins the primary `E0277` "the size for values of type `[u8]` cannot be known at compilation time" with the `Sized` `help:` note, the "required by an implicit `Sized` bound in `Option`" note, and the derived `E0599` on `as_ref`. Its `.cgp.stderr` is identical — the pass-through that places the fixture in the `usability/` tier, since cargo-cgp does not yet suppress the derived `E0599` or read the synthesized type back to the shorthand combination. ## Related diff --git a/docs/errors/lowering/unresolved-imported-type.md b/docs/errors/lowering/unresolved-imported-type.md index 05ce2d70..6ad995c4 100644 --- a/docs/errors/lowering/unresolved-imported-type.md +++ b/docs/errors/lowering/unresolved-imported-type.md @@ -23,21 +23,23 @@ pub fn get_scalar(&self) -> Scalr { The import declares the alias `Scalr`, so the bare `Scalr` in the return type is rewritten to `::Scalr`, which names an associated type `HasScalarType` never declares. The same failure arises from a stale name after a trait rename, or from importing an item that lives on a different trait than the one named. CGP is working as designed here: it cannot see the trait's item list during expansion, so it lowers the name literally and defers to the compiler — an [acceptable failure](../../implementation/AGENTS.md), not a defect. -## The diagnostic +## The raw diagnostic -This is a **surfaced** class with the simplest diagnostic in the catalog: a single [`E0576`](../error_codes/e0576.md), "cannot find associated type `Scalr` in trait `HasScalarType`", with the caret on the offending name. Because the substitution copies the *span* of the identifier the user wrote onto the rewritten path, the caret lands on the `Scalr` in the user's signature — the token they actually typed — rather than on the `#[use_type]` attribute or the whole macro block. When a similarly named item exists, `rustc` adds a "similarly named associated type `Scalar` defined here" note pointing at the trait's real declaration and a `help:` that suggests the correct spelling inline. There is no note chain, no CGP scaffolding, and no `IsProviderFor`/`DelegateComponent` frame, because the failure is caught during name resolution, before any bound is evaluated. +This section describes what plain `cargo check` prints — the fallback when `cargo-cgp` is not on hand; [How cargo-cgp presents it](#how-cargo-cgp-presents-it) below covers the readable form. This is a **surfaced** class with the simplest diagnostic in the catalog: a single [`E0576`](../error_codes/e0576.md), "cannot find associated type `Scalr` in trait `HasScalarType`", with the caret on the offending name. Because the substitution copies the *span* of the identifier the user wrote onto the rewritten path, the caret lands on the `Scalr` in the user's signature — the token they actually typed — rather than on the `#[use_type]` attribute or the whole macro block. When a similarly named item exists, `rustc` adds a "similarly named associated type `Scalar` defined here" note pointing at the trait's real declaration and a `help:` that suggests the correct spelling inline. There is no note chain, no CGP scaffolding, and no `IsProviderFor`/`DelegateComponent` frame, because the failure is caught during name resolution, before any bound is evaluated. ## Where the root cause is The root cause is **present and is the entire diagnostic** — the primary `E0576` names both the missing associated type and the trait it was sought in, and the caret points at the exact token to change. This is the opposite of the hidden and cascading classes: nothing is suppressed, nothing is buried, and there is no verbosity to wade through. The `help:` note, when present, even supplies the fix. The only CGP-specific fact the message does not state is that the name came from a `#[use_type]` import — but the caret sits on the alias in the signature, which is the same name written in the attribute, so following it to the attribute is immediate. -## Resolving it +## How cargo-cgp presents it -Correct the imported name so it matches an associated type the trait declares — change `#[use_type(HasScalarType.Scalr)]` and every use of the alias to `Scalar`, or use an `as` clause (`#[use_type(HasScalarType.{Scalar as Scalr})]`) if the short local name was intended. The `help:` note usually names the correct spelling outright. If the type genuinely lives on a different trait, point the import at that trait instead. Because the diagnostic is a plain resolution error at the user's own token, no CGP-specific tracing is needed. +`cargo-cgp` does not rewrite this class — it passes rustc's diagnostic through unchanged, and for the misnamed-type case it does not need to. For the `use_type_unknown_assoc` fixture the tool's `.cgp.stderr` is byte-for-byte its raw `.rust.stderr`: the `E0576`, the "similarly named associated type `Scalar` defined here" note, and the `help:` suggesting the correct spelling — no `[CGP-Exxx]` code stamped, nothing suppressed. That fixture sits in cargo-cgp's `acceptable/` tier precisely because the raw diagnostic already leads with the cause, with the caret on the user's own token and the fix in the `help:`; passing it through is the right outcome, not a gap. -## Notes for tooling +The [unresolved-context sibling](#a-sibling-the-unresolved-context) below is the same pass-through with a different verdict. Its two `E0425` "cannot find type" errors are copied through unchanged too, but its fixture sits in `usability/` rather than `acceptable/`: the caret lands on the bare alias inside the `#[use_type(HasA.A in B, HasB.B in A)]` attribute, and the raw message gives no hint that a `use_type` grounding *cycle* is why the alias never resolved — the CGP-level reading the tool could add but does not yet. The codes cargo-cgp stamps on the classes it does rewrite are defined in the [cargo-cgp error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md). -This class needs no special tool handling beyond recognizing its origin. A `cargo-cgp`-style post-processor should pass the `E0576` through essentially unchanged — the caret and the `help:` already point at the fix — and at most annotate that the name was introduced by a `#[use_type]` import so a user who does not see the connection between the signature alias and the attribute can find it. There is nothing to suppress and no hidden cause to recover. +## Resolving it + +Correct the imported name so it matches an associated type the trait declares — change `#[use_type(HasScalarType.Scalr)]` and every use of the alias to `Scalar`, or use an `as` clause (`#[use_type(HasScalarType.{Scalar as Scalr})]`) if the short local name was intended. The `help:` note usually names the correct spelling outright. If the type genuinely lives on a different trait, point the import at that trait instead. Because the diagnostic is a plain resolution error at the user's own token, no CGP-specific tracing is needed. ## A sibling: the unresolved *context* @@ -45,8 +47,8 @@ The same textual-substitution mechanism produces a related failure when the part ## Backing fixtures -- [acceptable/cgp_fn/use_type_unknown_assoc.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_unknown_assoc.rs) — a `#[cgp_fn]` importing `HasScalarType.Scalr` where the trait declares `Scalar`; its `.stderr` pins the `E0576` with the caret on the `Scalr` in the signature and the "similarly named associated type" `help:`, doubling as a guard that the substitution preserves the user's identifier span. -- [acceptable/cgp_fn/use_type_cyclic_context.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_cyclic_context.rs) — the unresolved-*context* sibling: two `in Context` clauses referencing each other, whose `.stderr` pins the `E0425` "cannot find type" with the caret on the unresolved `in` alias. +- [`acceptable/lowering/use_type_unknown_assoc.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/lowering/use_type_unknown_assoc.rs) — a `#[cgp_fn]` importing `HasScalarType.Scalr` where the trait declares `Scalar`; its `.rust.stderr` pins the `E0576` with the caret on the `Scalr` in the signature and the "similarly named associated type" `help:`, doubling as a guard that the substitution preserves the user's identifier span, and its `.cgp.stderr` is identical — the pass-through that keeps the fixture in the `acceptable/` tier, since the raw error already leads with the cause. +- [`usability/lowering/use_type_cyclic_context.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/usability/lowering/use_type_cyclic_context.rs) — the unresolved-*context* sibling: two `in Context` clauses referencing each other, whose `.rust.stderr` pins two `E0425` "cannot find type" errors with the carets on the unresolved `in` aliases in the attribute. Its `.cgp.stderr` is identical too, but the fixture sits in `usability/` — the pass-through does not reveal that a `use_type` grounding cycle is the cause. ## Related diff --git a/docs/errors/wiring/conflicting-wiring.md b/docs/errors/wiring/conflicting-wiring.md index 2de3b855..9b0cb6b2 100644 --- a/docs/errors/wiring/conflicting-wiring.md +++ b/docs/errors/wiring/conflicting-wiring.md @@ -24,9 +24,9 @@ The same shape appears with an `open` header colliding with an explicit mapping, Two namespace collisions that also produce `E0119` are documented separately, because their mistake is not a duplicate declaration but a namespace usage: two blanket forwardings that overlap (joining two namespaces, or a namespace join plus a bare-key `for` loop) are the [overlapping namespace forwarding](namespace-forwarding-conflict.md) class, and a specific entry that overrides a key a namespace already claims (a context re-wiring a registered path, or a child namespace redefining an inherited entry) is the [namespace override conflict](namespace-override-conflict.md) class. This document covers the plain duplicate-or-overlapping *declaration*. -## The diagnostic +## The raw diagnostic -Two error codes, by whether the collision is between *impls* or between *definitions*. A duplicate key or overlapping generic produces **[`E0119`](../error_codes/e0119.md) conflicting implementations**; a duplicate generated *name* produces **[`E0428`](../error_codes/e0428.md) "the name … is defined multiple times"**. Both point precisely: `E0119` carries two carets — "first implementation here" on the earlier entry and "conflicting implementation for ``" on the later one, aimed at the offending keys rather than the whole block — and `E0428` carries "previous definition here" / "redefined here". +This section describes what plain `cargo check` prints — the fallback when `cargo-cgp` is not on hand; [How cargo-cgp presents it](#how-cargo-cgp-presents-it) below covers the readable form. There are two error codes, by whether the collision is between *impls* or between *definitions*. A duplicate key or overlapping generic produces **[`E0119`](../error_codes/e0119.md) conflicting implementations**; a duplicate generated *name* produces **[`E0428`](../error_codes/e0428.md) "the name … is defined multiple times"**. Both point precisely: `E0119` carries two carets — "first implementation here" on the earlier entry and "conflicting implementation for ``" on the later one, aimed at the offending keys rather than the whole block — and `E0428` carries "previous definition here" / "redefined here". How many `E0119`s one duplicate produces depends on how many impls the entry generates, and knowing this lets a reader treat the pair as one logical conflict rather than two mistakes. A **context-wiring** entry — a `delegate_components!` mapping, or a `namespace`/`open`/`for` header — generates *both* a `DelegateComponent` table impl and an [`IsProviderFor`](../../reference/traits/is_provider_for.md) forwarding impl (so dependency errors stay diagnosable through checks), so a duplicate yields a **pair** of `E0119`s at the same caret, one for each trait. An entry that generates only a single lookup impl yields a **single** `E0119`: a duplicate [`#[default_impl]`](../../reference/traits/default_namespace.md) conflicts on its one `DefaultImpls…` impl, and a duplicate [`cgp_namespace!`](../../reference/macros/cgp_namespace.md) body entry (a `:` mapping or a `=>` redirect) conflicts on its one namespace-trait impl. A duplicated provider *name* is the compound case: `E0428` on the struct, plus the `E0119` pair on the provider's own provider-trait and `IsProviderFor` impls. @@ -36,31 +36,42 @@ Two wrinkles round out the shape, and the second is where Rust's coherence reaso The root cause is **present and precise**: the two carets name the two conflicting entries directly, and the error code names the kind of collision. This is a *structural* class, not a hidden or cascading one, so there is no note chain to walk and no suppressed cause to recover — the diagnostic points at exactly the two lines to reconcile. The only reading skill it demands is ignoring the expanded `PathCons<…>` type on a path-key conflict and trusting the caret. +## How cargo-cgp presents it + +`cargo-cgp` keeps the `E0119` code but rewrites the headline to name the *kind* of collision and de-duplicates the pair into one message. Because this is a structural class, there is no `root cause:` tree: both `rustc` carets are preserved (they are already the answer), the redundant `IsProviderFor` half of a generated pair is suppressed so one duplicate reads as one conflict, and any `@`-path key is resugared to bare `@a.b.*` notation instead of the `PathCons>` spine. Which `[CGP-E00x]` code it stamps distinguishes the four shapes this class produces: + +- A duplicate key or overlapping generic becomes **`[CGP-E004]` duplicate wiring** — `` [CGP-E004] duplicate wiring for component `GreeterComponent` on `Person` `` (or, for a `@`-path, `` duplicate wiring for `@cgp.core.error.ErrorTypeProviderComponent.*` on `App` ``). +- An `open` header colliding with an explicit mapping becomes **`[CGP-E007]` redirect collision** — `` [CGP-E007] component `GreeterComponent` on `Person` is redirected to `@GreeterComponent` `` — carrying a `help:` that names the redirected key to wire the provider under (`` wire the provider `GreetHello` with the key `@GreeterComponent` ``). +- The same key redirected twice — two `=>` mappings on a context, or one `@`-path registered twice inside a `cgp_namespace!` block — becomes **`[CGP-E008]` duplicate redirect**, naming both targets when they differ (`` duplicate redirect for component `FooComponent` on `App`: redirected to both `@app.foo` and `@app.bar` ``). + +Two forms in this class are **pass-throughs** that `cargo-cgp` does not rewrite, so a reader sees the raw diagnostic above. The `E0428` name clashes (`duplicate_component_name`, `duplicate_provider_name`) keep `rustc`'s uncoded `E0428` — for a duplicate provider, the surviving `E0119` on the provider trait is kept too, with its `IsProviderFor` half suppressed — because `E0428` already points precisely at the two definitions. A duplicate `#[default_impl]` key is likewise uncoded: its lone `E0119` on the `DefaultImpls…` impl passes through, a small gap where the `[CGP-E004]` rewrite does not yet reach. The codes are defined in the [cargo-cgp error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md). + ## Resolving it Remove one of the two entries. For a duplicate check-trait name from two tables over one context, add a `#[check_trait(Name)]` to one. The namespace collisions split off above have their own remedies — inheriting rather than joining several namespaces, or overriding only a path the namespace leaves unbound — covered in [overlapping namespace forwarding](namespace-forwarding-conflict.md) and [namespace override conflict](namespace-override-conflict.md). ## Notes for tooling -For a `cargo-cgp`-style post-processor this class needs little beyond faithful relaying: the two carets are already the answer, so the tool's job is to **present the pair of conflicting entries** and, on a path-key conflict, to **collapse the `PathCons>` type back to its readable `@a.b.c` path** so the headline names the duplicated key rather than a screen of type spine. Recognizing the `E0119`-pair (`IsProviderFor` + `DelegateComponent` for the same key) as one logical conflict, not two, also lets the tool report a single "key wired twice" rather than doubling the count. +`cargo-cgp` already does the collapsing and resugaring this class needs (above); what remains is the pass-through set — the `E0428` name clashes and the duplicate `#[default_impl]` key, all kept uncoded. Those are already precise enough to relay verbatim, but folding the `default_impl` `E0119` into `[CGP-E004]` would make the family complete. ## Backing fixtures -The `E0119` conflicts: +The `.rust.stderr` snapshot pins the raw `E0119`/`E0428` shape and the `.cgp.stderr` the reshaped headline. The `E0119` conflicts `cargo-cgp` rewrites: -- [acceptable/delegate_components/duplicate_key.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_key.rs) and [duplicate_key_same_block.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_key_same_block.rs) — the same key mapped twice, across two blocks and within one; pins the per-entry carets. -- [acceptable/delegate_components/overlapping_generic.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/overlapping_generic.rs) — a generic ` Wrapper` table overlapping a specific `Wrapper` table. -- [acceptable/delegate_components/duplicate_open_key.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_open_key.rs) — an `open` header colliding with an explicit mapping, with the "downstream crates may implement" note. -- [acceptable/delegate_components/duplicate_path_key.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_path_key.rs) — a duplicated `@`-path key, whose conflicting trait name expands into the long `PathCons>` type. -- [acceptable/cgp_namespace/duplicate_path_key.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/duplicate_path_key.rs) — a duplicate `cgp_namespace!` `@`-path entry, a *single* `E0119` on the namespace-trait impl. -- [acceptable/cgp_impl/duplicate_default_impl.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_impl/duplicate_default_impl.rs) — two `#[default_impl]` registering the same key, a *single* `E0119` on the one `DefaultImpls1` impl (not a pair). +- [`wiring/duplicate-keys/duplicate_key.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/duplicate-keys/duplicate_key.rs) and [`wiring/duplicate-keys/duplicate_key_same_block.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/duplicate-keys/duplicate_key_same_block.rs) — the same key mapped twice, across two blocks and within one; the `.rust.stderr` pins the `DelegateComponent` + `IsProviderFor` pair of carets, the `.cgp.stderr` the single `[CGP-E004]` headline. +- [`wiring/duplicate-keys/overlapping_generic.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/duplicate-keys/overlapping_generic.rs) — a generic ` Wrapper` table overlapping a specific `Wrapper` table, also `[CGP-E004]`. +- [`wiring/duplicate-keys/duplicate_open_key.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/duplicate-keys/duplicate_open_key.rs) — an `open` header colliding with an explicit mapping; the `.rust.stderr` carries the "downstream crates may implement" note, the `.cgp.stderr` the `[CGP-E007]` redirect-collision headline with its `help:` naming the `@GreeterComponent` key. +- [`wiring/duplicate-keys/duplicate_redirect.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/duplicate-keys/duplicate_redirect.rs) — the same component redirected to two paths on one context, rewritten to `[CGP-E008]` naming both targets. +- [`wiring/namespace-paths/delegate_duplicate_path_key.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/namespace-paths/delegate_duplicate_path_key.rs) — a duplicated `@`-path key whose raw conflicting trait name is a long `PathCons>` type; the `.cgp.stderr` resugars it to the bare `@cgp.core.error.ErrorTypeProviderComponent.*` path under `[CGP-E004]`. +- [`wiring/namespace-paths/namespace_duplicate_path_key.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/namespace-paths/namespace_duplicate_path_key.rs) — the same `@`-path redirected twice inside one `cgp_namespace!` block, a single `E0119` on the namespace trait rewritten to `[CGP-E008]`. The two namespace collisions that are *not* duplicate declarations live with their own classes: `override_registered_path.rs` and `inherited_override_conflict.rs` under [namespace override conflict](namespace-override-conflict.md), and `for_loop_bare_key.rs` and `two_namespaces_joined.rs` under [overlapping namespace forwarding](namespace-forwarding-conflict.md). -The `E0428` name clashes: +The pass-through conflicts `cargo-cgp` leaves uncoded: -- [acceptable/cgp_component/duplicate_component_name.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/duplicate_component_name.rs) — a derived `…Component` marker clashing with a hand-declared type. -- [acceptable/cgp_impl/duplicate_provider_name.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_impl/duplicate_provider_name.rs) — two `#[cgp_impl(new …)]` declaring the same provider struct, adding the `E0119` pair on top of the `E0428`. +- [`wiring/namespace-paths/duplicate_default_impl.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/namespace-paths/duplicate_default_impl.rs) — two `#[default_impl]` registering the same key, a *single* `E0119` on the one `DefaultImpls1` impl; the `.cgp.stderr` matches the `.rust.stderr`, the gap noted above. +- [`wiring/duplicate-keys/duplicate_component_name.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/duplicate-keys/duplicate_component_name.rs) — a derived `…Component` marker clashing with a hand-declared type; `E0428` kept verbatim. +- [`wiring/duplicate-keys/duplicate_provider_name.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/duplicate-keys/duplicate_provider_name.rs) — two `#[cgp_impl(new …)]` declaring the same provider struct; the `.cgp.stderr` keeps the `E0428` plus the surviving `E0119` on the provider trait `Greeter<_>` (its `IsProviderFor` half suppressed), all uncoded. ## Related diff --git a/docs/errors/wiring/namespace-forwarding-conflict.md b/docs/errors/wiring/namespace-forwarding-conflict.md index a73559f6..4e7f1f99 100644 --- a/docs/errors/wiring/namespace-forwarding-conflict.md +++ b/docs/errors/wiring/namespace-forwarding-conflict.md @@ -33,9 +33,9 @@ delegate_components! { Both reduce to the same mistake: two impls that each claim every key. A context can forward through at most one namespace, and a `for` loop's key must sit inside a path (`@app.SomeComponent.Key: Value`) so it keys a *concrete* path rather than every key. CGP lowers each blanket impl faithfully and cannot see from one block that two of them span the same keys, so it defers the overlap to the compiler. -## The diagnostic +## The raw diagnostic -This is a **structural** class reported as a pair of **[`E0119`](../error_codes/e0119.md) conflicting implementations**, one for each trait a namespace join forwards — the `DelegateComponent<_>` table impl and the `IsProviderFor<_, _, _>` forwarding impl that keeps dependency errors diagnosable through [checks](../../reference/macros/check_components.md). Both are printed with fully-generic `DelegateComponent<_>` / `IsProviderFor<_, _, _>` types, and each carries the two-caret shape: "first implementation here" on the first `namespace` header, and "conflicting implementation for `App`" on the second header (or on the loop's `Key: Value` line). The carets land on the entries the user wrote, so the diagnostic points straight at the two lines to reconcile. +This section describes what plain `cargo check` prints — the fallback when `cargo-cgp` is not on hand; [How cargo-cgp presents it](#how-cargo-cgp-presents-it) below covers the readable form. This is a **structural** class reported as a pair of **[`E0119`](../error_codes/e0119.md) conflicting implementations**, one for each trait a namespace join forwards — the `DelegateComponent<_>` table impl and the `IsProviderFor<_, _, _>` forwarding impl that keeps dependency errors diagnosable through [checks](../../reference/macros/check_components.md). Both are printed with fully-generic `DelegateComponent<_>` / `IsProviderFor<_, _, _>` types, and each carries the two-caret shape: "first implementation here" on the first `namespace` header, and "conflicting implementation for `App`" on the second header (or on the loop's `Key: Value` line). The carets land on the entries the user wrote, so the diagnostic points straight at the two lines to reconcile. The signature of this class is that **both** conflicting types are *fully generic* — `DelegateComponent<_>` and `IsProviderFor<_, _, _>`, with no concrete key — and the conflict carries **no** "downstream crates may implement …" note. That is what separates it from the [namespace override conflict](namespace-override-conflict.md), where one side of the overlap names a *specific* key (a path or a component marker). The generic-versus-generic shape follows from how Rust's coherence checker reasons here. Coherence forbids two impls that could both apply to one type, and both impls have the form `impl DelegateComponent for App where Key: SomeNamespace`: the self type is the same local `App`, and the key is a free parameter bounded only by a namespace trait. Nothing prevents a single key type from implementing both namespace traits, so the compiler proves the overlap *directly*, from the impls in front of it — it needs no hypothetical future impl to construct the conflict, which is why no downstream note appears. The [conflicting wiring](conflicting-wiring.md) class carries the full account of `E0119` and the RFC 2451 coherence reasoning this builds on; the point here is only that a note-free `E0119` on generic `DelegateComponent<_>`/`IsProviderFor<_, _, _>` is this class's signature. @@ -43,18 +43,24 @@ The signature of this class is that **both** conflicting types are *fully generi The root cause is **present and precise**: the two carets name the two overlapping entries directly, and the fully-generic trait types confirm the overlap spans every key. This is a structural class with no note chain to walk and nothing suppressed, so a reader trusts the carets and reconciles the two lines. The only reading the diagnostic does not supply is the CGP-specific fix — that a context forwards through one namespace, not several — because the message describes the collision in impl terms rather than in terms of the wiring intent. +## How cargo-cgp presents it + +`cargo-cgp` recognizes this class and rewrites it to state the intent the raw impl-level collision hides. It keeps the `E0119` code, collapses the `DelegateComponent<_>` + `IsProviderFor<_, _, _>` pair into one message (suppressing the redundant `IsProviderFor` half), and stamps **`[CGP-E006]` multiple namespaces** — `` [CGP-E006] only one namespace can be used for each target type in `delegate_components!`, but `App` uses both `NamespaceA` and `NamespaceB` ``. The two `rustc` carets are kept, landing on the two `namespace` headers (or, for the loop form, on the second header and the loop's `Key: Value` line), because they already point at the lines to reconcile; there is no `root cause:` tree, this being a structural class. The bare-key `for` loop form produces the *same* `[CGP-E006]` headline, naming the loop's table (`GreeterTable`) as the second "namespace", which is exactly right — a bare-key loop forwards over every key just as a namespace join does. The code is defined in the [cargo-cgp error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md). + ## Resolving it Emit only one blanket forwarding impl. When two namespaces are joined, keep a single `namespace N;` and fold the other namespace's entries into `N` through *inheritance* — define one namespace that inherits the rest (`cgp_namespace! { new Combined: NamespaceA { … } }`, itself inheriting further) and join that one, per the [namespaces guide](../../guides/namespaces-and-prefixes.md). When a bare-key `for` loop collides with a namespace join, embed the loop's key in a path (`@app.SomeComponent.Key: Value`) so it keys a concrete dispatch path instead of every key — which is also why a `for` loop is the natural tool for a generic-parameter component, whose dispatch path *is* the loop key. Either fix leaves the context with one forwarding impl and the overlap gone. ## Notes for tooling -For a `cargo-cgp`-style post-processor this class needs little beyond faithful relaying, since the two carets are already the answer. The value a tool adds is **recognizing the `E0119` pair (`DelegateComponent<_>` + `IsProviderFor<_, _, _>`) as one logical conflict, not two**, so it reports a single "two blanket forwardings overlap" rather than doubling the count, and **restating the fix in wiring terms**: "a context forwards through one namespace — inherit the others into it, or move a bare `for` key into a path." The absent downstream note is worth keying on to distinguish this from the override conflict and route the user to the right remedy. +`cargo-cgp` already recognizes and rewrites this class in full (above), leading with the `[CGP-E006]` wiring-intent headline rather than the impl-level collision. The one signal it keys on is worth restating for any post-processor: the absent "downstream crates may implement …" note over fully-generic `DelegateComponent<_>`/`IsProviderFor<_, _, _>` types is what distinguishes this class from the [namespace override conflict](namespace-override-conflict.md), and so routes the collision to `[CGP-E006]` rather than the override's `[CGP-E005]`. ## Backing fixtures -- [acceptable/cgp_namespace/two_namespaces_joined.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/two_namespaces_joined.rs) — two `namespace` joins on one context; its `.stderr` pins the `E0119` pair with carets on the two `namespace` lines and no downstream note. -- [acceptable/cgp_namespace/for_loop_bare_key.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/for_loop_bare_key.rs) — a bare-key `for` loop beside a namespace join; its `.stderr` pins the same `E0119` pair with the conflicting caret on the loop's `Key: Value` line. +The `.rust.stderr` snapshot pins the raw generic `E0119` pair and the `.cgp.stderr` the `[CGP-E006]` reshaping. + +- [`wiring/namespace-paths/two_namespaces_joined.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/namespace-paths/two_namespaces_joined.rs) — two `namespace` joins on one context; the `.rust.stderr` pins the `E0119` pair with carets on the two `namespace` lines and no downstream note, the `.cgp.stderr` the single `[CGP-E006]` headline naming both `NamespaceA` and `NamespaceB`. +- [`wiring/duplicate-keys/for_loop_bare_key.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/duplicate-keys/for_loop_bare_key.rs) — a bare-key `for` loop beside a namespace join; the `.rust.stderr` pins the same generic `E0119` pair with the conflicting caret on the loop's `Key: Value` line, the `.cgp.stderr` the `[CGP-E006]` headline naming `DefaultNamespace` and the loop's `GreeterTable`. ## Related diff --git a/docs/errors/wiring/namespace-inheritance-cycle.md b/docs/errors/wiring/namespace-inheritance-cycle.md index 29fe1d86..f91e69c3 100644 --- a/docs/errors/wiring/namespace-inheritance-cycle.md +++ b/docs/errors/wiring/namespace-inheritance-cycle.md @@ -18,9 +18,9 @@ cgp_namespace! { `new NamespaceA: NamespaceB` emits the inheritance blanket impl `impl NamespaceA
for Key where Key: NamespaceB<…>, Key: NamespaceB`, and `new NamespaceB: NamespaceA` emits the mirror. A self-inheriting `new A: A {}` collapses the two into one impl whose bound requires the trait it defines. CGP cannot see the parent chain is circular from one macro invocation — each `cgp_namespace!` knows only its own parent — so it lowers each namespace faithfully and defers the contradiction to the compiler. -## The diagnostic +## The raw diagnostic -The compiler reports **`E0275` "overflow evaluating the requirement"**, and the defining property of this class is *where* it lands: on the `cgp_namespace!` blocks that define the namespaces, with no context or use site required. Evaluating the well-formedness of `NamespaceA`'s inheritance impl means evaluating its `where` bound `__Key__: NamespaceB<…>`, which pulls in `NamespaceB`'s impl and its bound `__Key__: NamespaceA<…>`, which pulls in the first again — an infinite chain. The overflow message names the requirement that recurses (`__Key__: NamespaceA<__NamespaceBComponents>`), a `note:` chain that walks `NamespaceB<…>` → `NamespaceA<…>` and names the loop, a `= note: N redundant requirements hidden` marking the collapsed repetition, and the standard `help: consider increasing the recursion limit`. Each of the two definitions carries its own `E0275`. +This section describes what plain `cargo check` prints — and for this one class it is the *only* place the error appears, because [`cargo-cgp` does not report it at all](#how-cargo-cgp-presents-it). The compiler reports **`E0275` "overflow evaluating the requirement"**, and the defining property of this class is *where* it lands: on the `cgp_namespace!` blocks that define the namespaces, with no context or use site required. Evaluating the well-formedness of `NamespaceA`'s inheritance impl means evaluating its `where` bound `__Key__: NamespaceB<…>`, which pulls in `NamespaceB`'s impl and its bound `__Key__: NamespaceA<…>`, which pulls in the first again — an infinite chain. The overflow message names the requirement that recurses (`__Key__: NamespaceA<__NamespaceBComponents>`), a `note:` chain that walks `NamespaceB<…>` → `NamespaceA<…>` and names the loop, a `= note: N redundant requirements hidden` marking the collapsed repetition, and the standard `help: consider increasing the recursion limit`. Each of the two definitions carries its own `E0275`. This eager, definition-site failure is the sharp contrast with the sibling [wiring cycle](wiring-cycle.md). A [`UseContext`](../../reference/providers/use_context.md) delegation cycle is *lazy*: the wiring is accepted, and the overflow appears only when the wiring is forced through a check (and hides as an [`E0599`](../hidden/unsatisfied-dependency.md) when reached by a plain call). A namespace inheritance cycle is *eager*: the cycle lives in the `where` clause of a generated blanket impl, and the compiler evaluates that clause when it checks the impl, so the overflow fires at the definition before anything uses the namespace. A context that *does* join the cycle and is then checked simply adds more `E0275` blocks — an `App: DelegateComponent` overflow at the check — on top of the definition-site ones; it does not change the cause. @@ -30,17 +30,19 @@ The overflow itself is ordinary `rustc` behavior. The trait solver walks a requi The cause is **present and the diagnostic points straight at it**: the two `E0275` carets land on the two `cgp_namespace!` definitions, and the `note:` chain names both namespaces and the loop between them. Unlike a hidden failure there is nothing to promote and nothing suppressed — the participants are all named. What the diagnostic does not state is the remedy, and it actively misleads on it: the `help: increase the recursion limit` line suggests a fix that cannot work, because the requirement does not terminate at any depth. Read the overflow as "these namespaces' parent chain is circular," not as "the chain is merely deep." -## Resolving it +## How cargo-cgp presents it -Break the cycle so the parent chain is acyclic — a namespace's inheritance must form a tree, not a loop. Remove one direction of the mutual inheritance so one namespace is unambiguously the parent, or, when both namespaces genuinely need a shared set of entries, factor those entries into a third base namespace that both inherit from, rather than having them inherit each other. Raising `#![recursion_limit]` is the wrong move: the chain has no terminating step, so no limit is high enough. +`cargo-cgp` does not report this error at all — under its next-generation trait solver the mutually-inheriting namespaces **compile clean**, with no `E0275` to reshape. This is the one class in the catalog where the tool's output is *empty* rather than a rewrite of the raw diagnostic. It is a next-solver divergence, but the reverse of the usual one: where the caveat about the next solver normally warns that it may *report* something the stable solver does not, here it *omits* an error the stable solver raises — a missing error, not an added one. The next-gen solver's cycle handling terminates the inheritance chain instead of overflowing on it, so the contradiction the current solver treats as a fatal overflow simply resolves. -## Notes for tooling +The practical consequence is that this class surfaces only for a reader on plain `rustc`/`cargo check`; the recommended `cargo-cgp` toolchain will not flag the circular inheritance, and there is no `.cgp.stderr` to consult. That does not make the wiring correct — a self- or mutually-inheriting namespace is still a mistake — only invisible under the tool, which is why the fix in [Resolving it](#resolving-it) still applies even when nothing complains. -A `cargo-cgp`-style post-processor should **recognize the repeating namespace requirement** — the same `Namespace<…>` pair recurring in the overflow's `note:` chain — and report it as an inheritance cycle ("`NamespaceA` inherits `NamespaceB` inherits `NamespaceA`"), naming the two `cgp_namespace!` definitions rather than echoing the raw overflow depth. Crucially, it should **suppress the compiler's `increase the recursion limit` suggestion for this class**, since that advice is actively wrong for a true cycle; the value the tool adds is replacing misleading generic advice with the actual structural fix. +## Resolving it + +Break the cycle so the parent chain is acyclic — a namespace's inheritance must form a tree, not a loop. Remove one direction of the mutual inheritance so one namespace is unambiguously the parent, or, when both namespaces genuinely need a shared set of entries, factor those entries into a third base namespace that both inherit from, rather than having them inherit each other. Raising `#![recursion_limit]` is the wrong move: the chain has no terminating step, so no limit is high enough. ## Backing fixtures -- [acceptable/cgp_namespace/inheritance_cycle.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/inheritance_cycle.rs) — two namespaces that inherit each other, with no context at all; its `.stderr` pins the two `E0275` overflow blocks landing on the two `cgp_namespace!` definitions and the `note:` chain that names the `NamespaceB<…>` → `NamespaceA<…>` loop, confirming the failure is caught eagerly at definition. +This is the **one class in the catalog with no `cargo-cgp` fixture**, because there is no error to snapshot: under `cargo-cgp`'s next-generation solver the mutually-inheriting namespaces compile clean, so a fixture would carry an empty `.cgp.stderr` and misrepresent the class as fixed. The absence is deliberate and is recorded on the `cargo-cgp` side as the single un-migrated class, in its [UI-test README](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/README.md) — which explains it as a *missing* error (the next-solver divergence above) rather than a suppressed cause, and notes it will be reproduced only if a genuinely reproducible case is found. The raw `E0275` described above therefore has no blessed `.rust.stderr` either; it is what plain `cargo check` prints today, verified against the compiler rather than against a committed snapshot. ## Related diff --git a/docs/errors/wiring/namespace-override-conflict.md b/docs/errors/wiring/namespace-override-conflict.md index 40c8e034..b7e12bbf 100644 --- a/docs/errors/wiring/namespace-override-conflict.md +++ b/docs/errors/wiring/namespace-override-conflict.md @@ -40,9 +40,9 @@ cgp_namespace! { Both reduce to "a specific entry for a key the namespace's blanket impl already covers." In the context case the blanket impl is the `namespace N;` forwarding `impl DelegateComponent for App where Key: N`; in the namespace case it is the inheritance forwarding `impl ChildNs
for Key where Key: BaseNs<…>`. Either way, CGP lowers both the blanket impl and the specific entry faithfully and cannot see from one macro invocation that they claim the same key, so it defers the overlap to the compiler. -## The diagnostic +## The raw diagnostic -This is a **structural** class reported as **[`E0119`](../error_codes/e0119.md) conflicting implementations**, with the two-caret shape — "first implementation here" on the namespace's blanket source, and "conflicting implementation" on the specific entry — landing on the entries the user wrote. What the specific side of the overlap *names* is the signature that tells this class from the generic-versus-generic [overlapping namespace forwarding](namespace-forwarding-conflict.md): here one impl is keyed on a *concrete* key. The two shapes differ in the details, and recognizing each on sight tells a reader which override they attempted. +This section describes what plain `cargo check` prints — the fallback when `cargo-cgp` is not on hand; [How cargo-cgp presents it](#how-cargo-cgp-presents-it) below covers the readable form. This is a **structural** class reported as **[`E0119`](../error_codes/e0119.md) conflicting implementations**, with the two-caret shape — "first implementation here" on the namespace's blanket source, and "conflicting implementation" on the specific entry — landing on the entries the user wrote. What the specific side of the overlap *names* is the signature that tells this class from the generic-versus-generic [overlapping namespace forwarding](namespace-forwarding-conflict.md): here one impl is keyed on a *concrete* key. The two shapes differ in the details, and recognizing each on sight tells a reader which override they attempted. The **context-level** shape produces a **pair** of `E0119`s, because a context is wired through both the `DelegateComponent` table and the `IsProviderFor` forwarding: one conflict on `DelegateComponent>` for `App` and one on `IsProviderFor, _, _>` for `App`, the conflicting key expanded into the long `PathCons>` path type (read the caret, not the type). This shape additionally carries a `note: downstream crates may implement trait IsProviderFor, _, _> for type GreetHello` (and for the overriding provider), which is `rustc` making its coherence reasoning explicit: whether the namespace's blanket forwarding and the direct path entry overlap hinges on whether the redirect's delegate provider implements `IsProviderFor` for that path, a bound a downstream crate could add, so the compiler cannot rule the overlap out and cites the hypothetical impl. That future-compatibility (negative-reasoning) rule is the same one [RFC 2451](https://rust-lang.github.io/rfcs/2451-re-rebalancing-coherence.html) formalizes and that the [conflicting wiring](conflicting-wiring.md) class explains in full. @@ -52,6 +52,12 @@ The **namespace-level** shape produces a **single** `E0119` — `conflicting imp The root cause is **present and precise** in both shapes: the two carets name the namespace source and the overriding entry, and the error code names the collision. This is a structural class with no note chain to walk and nothing suppressed. The one reading skill it asks for is to look past the expanded `PathCons>` key type on the context-level shape and trust the caret, and to read the `downstream crates may implement …` note as coherence's *reason*, not a second, separate problem. What neither shape states is the CGP-specific remedy, since the message frames a namespace override decision as a bare coherence conflict. +## How cargo-cgp presents it + +`cargo-cgp` rewrites the two shapes differently, because it recognizes only one of them today. The **context-level** shape is rewritten: keeping `E0119`, it collapses the `DelegateComponent` + `IsProviderFor` pair into one message, suppresses the `downstream crates may implement …` note, resugars the `PathCons>` key to its bare `@app.…` path, and stamps **`[CGP-E005]` overlapping wiring** — `` [CGP-E005] `App` cannot wire `@app.GreeterComponent.*` that is already set through `AppNamespace` ``. That headline names the overriding entry, the path it claims, and the namespace that already binds it, so the reader sees the override decision the raw coherence conflict only implied; the two `rustc` carets are kept, this being a structural class with no `root cause:` tree. + +The **namespace-level** shape is a **pass-through**: `cargo-cgp` does not yet rewrite it, so the raw `` conflicting implementations of trait `ChildNs<_>` for type `GreeterComponent` `` is what the reader sees, uncoded. The tell for the reader is the same as in the raw diagnostic — the self type is a *component marker* and the trait is the child namespace, marking the collision as inside the namespace's own table. The distinction that decides which the tool does is whether the collision is on a context (rewritten) or on a namespace trait (passed through); closing that gap would fold the namespace-level shape into `[CGP-E005]` as well. The codes are defined in the [cargo-cgp error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md). + ## Resolving it The fix depends on which override was attempted, and both follow one rule: **a namespace entry, once bound, cannot be overridden — only a path the namespace leaves unbound is overridable.** @@ -62,12 +68,14 @@ For the **namespace-level** shape, do not bind the key in the base and redefine ## Notes for tooling -For a `cargo-cgp`-style post-processor, the value is translating the coherence conflict into the override intent it actually represents. On the **context-level** shape the tool should **collapse the `PathCons>` key back to its readable `@app.…` path**, recognize the `DelegateComponent` + `IsProviderFor` pair as one logical conflict, and report "the namespace already binds `@app.GreeterComponent`; a context cannot override a path the namespace terminates — route through it and terminate on the context instead." On the **namespace-level** shape, recognizing the self type is a *component marker* and the trait is the child namespace lets the tool report "`ChildNs` cannot override `GreeterComponent`, which it inherits from `BaseNs`; leave it unbound in `BaseNs` to vary it per child." The `downstream crates may implement …` note is coherence scaffolding to suppress once its meaning is folded into the headline. +The **namespace-level** shape is the gap that remains: `cargo-cgp` passes it through uncoded (above), so a post-processor that recognizes the self type is a *component marker* and the trait is the child namespace could fold it into `[CGP-E005]` too and report "`ChildNs` cannot override `GreeterComponent`, which it inherits from `BaseNs`; leave it unbound in `BaseNs` to vary it per child." The context-level shape already gets the full `[CGP-E005]` rewrite, so nothing more is needed there. ## Backing fixtures -- [acceptable/cgp_namespace/override_registered_path.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/override_registered_path.rs) — the context-level shape: a context joining `AppNamespace` overrides a path bound with `#[default_impl]`; its `.stderr` pins the `E0119` pair on `DelegateComponent>` and `IsProviderFor, _, _>` for `App`, with the expanded path type and the `downstream crates may implement` note. -- [acceptable/cgp_namespace/inherited_override_conflict.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/inherited_override_conflict.rs) — the namespace-level shape: a child namespace redefining an inherited key; its `.stderr` pins the single `E0119` on `ChildNs<_>` for `GreeterComponent`, with "first implementation here" on the inherited parent and no downstream note. +The `.rust.stderr` snapshot pins the raw `E0119` shape and the `.cgp.stderr` the reshaped (or passed-through) form. + +- [`wiring/namespace-paths/override_registered_path.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/namespace-paths/override_registered_path.rs) — the context-level shape: a context joining `AppNamespace` overrides a path bound with `#[default_impl]`; the `.rust.stderr` pins the `E0119` pair on `DelegateComponent>` and `IsProviderFor, _, _>` for `App` with the expanded path type and the `downstream crates may implement` note, the `.cgp.stderr` the single `[CGP-E005]` headline with the resugared `@app.GreeterComponent.*` path. +- [`wiring/namespace-paths/inherited_override_conflict.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/namespace-paths/inherited_override_conflict.rs) — the namespace-level shape: a child namespace redefining an inherited key; both snapshots agree — the single `E0119` on `ChildNs<_>` for `GreeterComponent`, with "first implementation here" on the inherited parent and no downstream note, passes through uncoded (the gap noted above). ## Related diff --git a/docs/errors/wiring/orphan-rule.md b/docs/errors/wiring/orphan-rule.md index 59aae4f3..7fdacc5d 100644 --- a/docs/errors/wiring/orphan-rule.md +++ b/docs/errors/wiring/orphan-rule.md @@ -26,9 +26,9 @@ The third is a **`cgp_namespace!` block without `new`** that re-opens a foreign Every form reduces to the same fact: a foreign namespace trait implemented for a foreign key, with no local type to satisfy the orphan rule. This is a whole-program coherence fact CGP cannot see from the macro invocation, so it lowers the impl faithfully and defers to the compiler. The orphan-*safe* counterpart is owning *either* end — a crate may register a *local* component's marker into a foreign namespace (the key is local), or add entries to a namespace whose trait it owns; only when both are foreign does the impl become an orphan. -## The diagnostic +## The raw diagnostic -The compiler reports **`E0210`** — "type parameter `…` must be used as the type parameter for some local type" — naming the impl's uncovered table parameter, with two explanatory notes: "implementing a foreign trait is only possible if at least one of the types for which it is implemented is local," and "only traits defined in the current crate can be implemented for a type parameter." Which parameter is named, and where the caret lands, follows the construct that generated the impl: a `#[default_impl]` names **`__Components__`**, lands the caret on the `#[cgp_impl]` attribute, and attributes the error to the `cgp_impl` macro; a `cgp_namespace!` re-open names **`__Table__`** (the namespace table parameter), lands the caret on the whole `cgp_namespace!` block, and attributes the error to the `cgp_namespace` macro. The parameter differs only because the two constructs name their table parameter differently; the violation is identical. Depending on the exact shape of the generated impl, the sibling orphan error **`E0117`** ("only traits defined in the current crate can be implemented for arbitrary types") can appear instead; both are the orphan rule rejecting the same foreign-trait-for-foreign-type impl. +This section describes what plain `cargo check` prints — and, for this class, what `cargo-cgp` shows too, since the tool passes the error through unchanged (see [How cargo-cgp presents it](#how-cargo-cgp-presents-it)). The compiler reports **`E0210`** — "type parameter `…` must be used as the type parameter for some local type" — naming the impl's uncovered table parameter, with two explanatory notes: "implementing a foreign trait is only possible if at least one of the types for which it is implemented is local," and "only traits defined in the current crate can be implemented for a type parameter." Which parameter is named, and where the caret lands, follows the construct that generated the impl: a `#[default_impl]` names **`__Components__`**, lands the caret on the `#[cgp_impl]` attribute, and attributes the error to the `cgp_impl` macro; a `cgp_namespace!` re-open names **`__Table__`** (the namespace table parameter), lands the caret on the whole `cgp_namespace!` block, and attributes the error to the `cgp_namespace` macro. The parameter differs only because the two constructs name their table parameter differently; the violation is identical. Depending on the exact shape of the generated impl, the sibling orphan error **`E0117`** ("only traits defined in the current crate can be implemented for arbitrary types") can appear instead; both are the orphan rule rejecting the same foreign-trait-for-foreign-type impl. The rule the compiler is enforcing here is coherence, and understanding it explains why the error frames the fix as a matter of *ownership*. Coherence requires that for any trait and type there is at most one impl, and the orphan rule preserves that across crates by forbidding an impl of a foreign trait unless a local type is *covered* by it — appears before any uncovered type parameter. Were an orphan impl allowed, two unrelated crates could each implement the foreign trait for the foreign type in incompatible ways, and adding a dependency could silently break a build; the orphan rule rejects the impl up front precisely so that can never happen. `E0210` is the specific form of this rule for an impl whose only "type" in the covering position is a bare type parameter (`__Components__`), which no local type covers. The current rule and its wording come from [RFC 2451 (re-rebalancing coherence)](https://rust-lang.github.io/rfcs/2451-re-rebalancing-coherence.html); the [`E0210`](../error_codes/e0210.md) reference summarizes it, alongside its sibling [`E0117`](../error_codes/e0117.md). @@ -36,21 +36,27 @@ The rule the compiler is enforcing here is coherence, and understanding it expla The mechanical cause is **present** — the error names the foreign trait and the offending type parameter — but the *actionable* cause is CGP-specific and the diagnostic does not state it. What the compiler cannot say is that the impl is foreign because a namespace registration landed on a namespace and key the crate does not own, nor that the remedy is a matter of crate ownership. So while this is not a hidden class, reading the raw `E0210` leaves a user who does not know CGP's namespace mechanics without the fix; the gap is knowledge, not information the compiler withheld. +## How cargo-cgp presents it + +`cargo-cgp` does **not** rewrite this class today: it passes `rustc`'s `E0210` (or `E0117`) through unchanged, uncoded, so the reader sees exactly the raw diagnostic above. This is why the three fixtures sit under `usability/wiring/orphan/` rather than `acceptable/` — the cause is *present* in the output (the error names the foreign trait and the uncovered parameter), but the *actionable* remedy is CGP-specific and the tool does not yet supply it, so each fixture's `.cgp.stderr` matches its `.rust.stderr` byte for byte. The gap is a recognized one, not an oversight: the shape is distinctive enough to key on — an `E0210`/`E0117` whose trait is a namespace trait and whose `Self` is a foreign component marker or a `PathCons>` spine — so it is a candidate for a future rewrite into the ownership-framed remedy the [Resolving it](#resolving-it) section states. Until then, no `[CGP-Exxx]` code is stamped; the [cargo-cgp error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md) covers the classes it does rewrite. + ## Resolving it Own one end of the impl. Register the default from the crate that owns the namespace, or key it on a **local** component whose marker the registering crate owns (`#[default_impl(LocalComponent in Namespace)]`), which satisfies the orphan rule because a local type is present. When the wiring genuinely must live downstream of the namespace, use a namespace *body* entry rather than a per-component `#[default_impl]`, per the [namespaces guide](../../guides/namespaces-and-prefixes.md). To *extend* a foreign namespace, do not re-open it with a bare `cgp_namespace!` block — define a **new local namespace that inherits it** (`cgp_namespace! { new Local: ForeignNamespace { … } }`), which is orphan-safe because every emitted impl is for the local namespace trait. See [`DefaultNamespace`](../../reference/traits/default_namespace.md) for the orphan-safe registration patterns. ## Notes for tooling -A `cargo-cgp`-style post-processor should recognize the shape — an `E0210`/`E0117` whose trait is a namespace trait (a `#[cgp_namespace]` trait, `DefaultNamespace`, or a `DefaultImpls…`) and whose `Self` type is a foreign component marker or a `PathCons>` spine — and translate the generic orphan message into the CGP remedy: "registering into a foreign namespace needs the crate to own the namespace or a local key; register from the namespace's own crate, key on a local component, use a namespace body entry, or inherit the namespace into a new local one." The raw diagnostic is accurate but frames a CGP wiring decision as a bare coherence rule, so the value a tool adds is the translation, not the extraction. +This is the remaining gap: `cargo-cgp` passes the orphan `E0210`/`E0117` through untranslated (above), so a post-processor that recognizes the shape — a namespace trait (a `#[cgp_namespace]` trait, `DefaultNamespace`, or a `DefaultImpls…`) implemented for a foreign component marker or a `PathCons>` spine — could translate the generic orphan message into the CGP remedy: "registering into a foreign namespace needs the crate to own the namespace or a local key; register from the namespace's own crate, key on a local component, use a namespace body entry, or inherit the namespace into a new local one." The raw diagnostic is accurate but frames a CGP wiring decision as a bare coherence rule, so the value the rewrite would add is the translation, not the extraction — which is why the fixtures live in `usability/` until it lands. ## Backing fixtures -- [acceptable/cgp_namespace/default_impl_foreign_prefix_path.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_prefix_path.rs) — a downstream crate registering a `#[default_impl]` for an upstream *prefixed* component into the upstream namespace, so the key is a foreign `PathCons>` path; its `.stderr` pins the `E0210` on `__Components__` and the "implementing a foreign trait" notes. -- [acceptable/cgp_namespace/default_impl_foreign_component.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_component.rs) — the same `#[default_impl]` orphan keyed on a foreign *unprefixed* component marker rather than a path, showing the restriction is not specific to prefixes; its `.stderr` pins the `E0210` on `__Components__`. -- [acceptable/cgp_namespace/reopen_foreign_namespace.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/reopen_foreign_namespace.rs) — a `cgp_namespace!` block without `new` re-opening a foreign namespace; its `.stderr` pins the `E0210` on `__Table__` with the caret on the whole `cgp_namespace!` block, attributed to the `cgp_namespace` macro. +Each fixture sits under `usability/wiring/orphan/` because `cargo-cgp` does not yet rewrite the class, so its `.cgp.stderr` and `.rust.stderr` are identical — both pin the raw `E0210`. + +- [`usability/wiring/orphan/default_impl_foreign_prefix_path.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/usability/wiring/orphan/default_impl_foreign_prefix_path.rs) — a downstream crate registering a `#[default_impl]` for an upstream *prefixed* component into the upstream namespace, so the key is a foreign `PathCons>` path; the snapshots pin the `E0210` on `__Components__`, the caret on the `#[cgp_impl]` attribute, and the "implementing a foreign trait" notes. +- [`usability/wiring/orphan/default_impl_foreign_component.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/usability/wiring/orphan/default_impl_foreign_component.rs) — the same `#[default_impl]` orphan keyed on a foreign *unprefixed* component marker rather than a path, showing the restriction is not specific to prefixes; the snapshots pin the `E0210` on `__Components__`. +- [`usability/wiring/orphan/reopen_foreign_namespace.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/usability/wiring/orphan/reopen_foreign_namespace.rs) — a `cgp_namespace!` block without `new` re-opening a foreign namespace; the snapshots pin the `E0210` on `__Table__` with the caret on the whole `cgp_namespace!` block, attributed to the `cgp_namespace` macro. -The orphan-*safe* counterparts — a *local* component key registered into the foreign `AppNamespace`, and a local context wiring a foreign component — are exercised in the cross-crate test packages (`cgp-test-crate-b`). +The orphan-*safe* counterpart is the positive fixture [`ok/cross_crate_wiring.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/ok/cross_crate_wiring.rs), which compiles clean: its auxiliary crates (`cgp-test-crate-a` upstream, `cgp-test-crate-b` downstream) wire a foreign component to a foreign provider, join an upstream namespace, and register a *local* component into the upstream namespace with `#[default_impl]` — every cross-crate impl coherent because one end is always local. ## Related diff --git a/docs/errors/wiring/unconstrained-generic.md b/docs/errors/wiring/unconstrained-generic.md index 881535f6..fe859454 100644 --- a/docs/errors/wiring/unconstrained-generic.md +++ b/docs/errors/wiring/unconstrained-generic.md @@ -20,9 +20,9 @@ impl DelegateComponent for Person { The same shape arises when a *generic* provider is registered as a per-type default, since the provider's parameter lands only in the `Delegate` associated-type position. -## The diagnostic +## The raw diagnostic -The compiler reports a single **`E0207`** — "the type parameter `T` is not constrained by the impl trait, self type, or predicates" — with the caret on the `` the user wrote in the entry. It is one clean, well-localized error, with no note chain and no cascade. +This section describes what plain `cargo check` prints — the fallback when `cargo-cgp` is not on hand; [How cargo-cgp presents it](#how-cargo-cgp-presents-it) below covers the readable form. The compiler reports **`E0207`** — "the type parameter `T` is not constrained by the impl trait, self type, or predicates" — with the caret on the `` the user wrote in the entry. The class is well-localized rather than cascading, but it is not quite a single line: the entry lowers into more than one generated position carrying `T`, so the same `E0207` prints twice, each with a different `help:` — one offering to move `T` onto the `Person` type, one to remove the unused parameter. Both carets land on the same `` and name the same cause, so there is no note chain and nothing to trace. The rule behind it is that an impl parameter must be *determined* by the impl, and knowing why makes the fix obvious. Rust requires every generic parameter on an impl to appear in the implemented trait, in the self type, or in a `where`-clause predicate that pins it as an associated type — one of the three "constrained" positions the message names — because otherwise, given a trait reference, the compiler could not decide which `T` the impl is for. Here `T` reaches only the `Delegate` *value* (`GreetWith`), an associated-type position on the right of the `=`, which does not constrain the impl: `DelegateComponent` names no `T`, so any `T` would satisfy the header equally. Permitting that would also break coherence, since a downstream crate adding another `GreetWith` would make the choice ambiguous. This is the rule introduced by [RFC 447](https://github.com/rust-lang/rfcs/pull/447) ("prohibit unused type parameters in impls"); the [`E0207`](../error_codes/e0207.md) reference summarizes it. It is the same negative-reasoning concern that underlies the [orphan rule](orphan-rule.md) and coherence conflicts — an impl must be resolvable no matter what impls other crates add later. @@ -30,17 +30,17 @@ The rule behind it is that an impl parameter must be *determined* by the impl, a The root cause is **present and precise**: the caret sits on the offending generic parameter, and the message states exactly why it is rejected. This is the most localized class in the catalog — the diagnostic needs no tracing and hides nothing. The only thing the raw message lacks is the CGP-specific remedy, since it describes the constraint in impl terms rather than in terms of the wiring entry. -## Resolving it +## How cargo-cgp presents it -Make the generic reach the key so it is bound — introduce it on a key that carries it (` SomeKey: …`) rather than only on the value — or, when the intent was a single concrete wiring, register a concrete provider with no per-entry generic at all. For a generic provider registered as a per-type default, register a concrete provider instead, since the default position cannot bind the provider's parameter. +`cargo-cgp` does not yet rewrite this class — it passes rustc's diagnostic through unchanged. For the fixture, the tool's `.cgp.stderr` is byte-for-byte its raw `.rust.stderr`: both `E0207` errors, both `help:` notes, no `[CGP-Exxx]` code stamped. That pass-through is why the fixture sits in cargo-cgp's `usability/` tier rather than `acceptable/`. The cause is already pinpoint-accurate at the caret, so nothing is buried — but the message still speaks in impl terms ("the type parameter `T` is not constrained"), where the actionable reading is a wiring one: the generic must reach the component *key* (` SomeKey: …`), not only the provider *value*. Restating the fix that way is the one improvement left for the tool; there is nothing to recover, only to translate. The codes it stamps on the classes it does rewrite are defined in the [cargo-cgp error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md). -## Notes for tooling +## Resolving it -This class needs the least tool intervention: the `E0207` is already pinpoint-accurate, so a `cargo-cgp`-style post-processor only needs to **restate the fix in wiring terms** — "the generic `T` on this entry must appear in the component key, not only in the provider" — rather than reformat or recover anything. It is worth recognizing precisely so a tool does *not* treat it like the hidden or cascading classes; there is nothing buried here. +Make the generic reach the key so it is bound — introduce it on a key that carries it (` SomeKey: …`) rather than only on the value — or, when the intent was a single concrete wiring, register a concrete provider with no per-entry generic at all. For a generic provider registered as a per-type default, register a concrete provider instead, since the default position cannot bind the provider's parameter. ## Backing fixtures -- [acceptable/delegate_components/unconstrained_generic.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/unconstrained_generic.rs) — a per-entry generic (` GreeterComponent: GreetWith`) that appears only in the value, lowering to an impl with an unconstrained `T`; its `.stderr` pins the `E0207` caret on the ``. +- [`usability/wiring/constraints/unconstrained_generic.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/usability/wiring/constraints/unconstrained_generic.rs) — a per-entry generic (` GreeterComponent: GreetWith`) that appears only in the value, lowering to an impl with an unconstrained `T`; its `.rust.stderr` pins the `E0207` carets on the `` (the error printed twice), and its `.cgp.stderr` is identical — the pass-through that places the fixture in the `usability/` tier, since cargo-cgp does not yet restate this class in wiring terms. ## Related diff --git a/docs/errors/wiring/wiring-cycle.md b/docs/errors/wiring/wiring-cycle.md index e2f92968..744af30b 100644 --- a/docs/errors/wiring/wiring-cycle.md +++ b/docs/errors/wiring/wiring-cycle.md @@ -26,9 +26,9 @@ delegate_components! { CGP lowers the wiring faithfully and cannot see that the delegation is self-referential without a whole-program view, so it accepts the entry and defers the failure to the compiler. A mutual cycle between two components that each delegate through the other fails the same way. -## The diagnostic +## The raw diagnostic -How the cycle surfaces depends on how it is exercised, and the two shapes are very different. Forcing the wiring through a [`check_components!`](../../reference/macros/check_components.md) drives the solver directly into the loop, and it overflows with **`E0275`** — "overflow evaluating the requirement `Person: IsProviderFor`" — followed by a note chain that *names the cycle*: `Person` to implement `Greeter`, then `CanGreet`, then `UseContext` to implement `IsProviderFor`, then back into `CanUseComponent`. The loop is visible in the chain. +This section describes what plain `cargo check` prints — the fallback when `cargo-cgp` is not on hand; [How cargo-cgp presents it](#how-cargo-cgp-presents-it) below covers the readable form. How the cycle surfaces depends on how it is exercised, and the two shapes are very different. Forcing the wiring through a [`check_components!`](../../reference/macros/check_components.md) drives the solver directly into the loop, and it overflows with **`E0275`** — "overflow evaluating the requirement `Person: IsProviderFor`" — followed by a note chain that *names the cycle*: `Person` to implement `Greeter`, then `CanGreet`, then `UseContext` to implement `IsProviderFor`, then back into `CanUseComponent`. The loop is visible in the chain. Exercised instead by a plain method call on the context, the same cycle does *not* overflow: the method probe treats the unresolvable requirement as simply unsatisfied and reports the [hidden `E0599`](../hidden/unsatisfied-dependency.md) — "method exists but its trait bounds were not satisfied" — with no hint that a cycle is the reason. So a wiring cycle is `E0275` when checked and a hidden `E0599` when called, and only the checked form actually reveals the cause. @@ -38,17 +38,20 @@ The [`E0275`](../error_codes/e0275.md) itself is ordinary `rustc` behavior. The The cause — the cycle — is **present in the checked (`E0275`) form** and readable from the note chain: the requirement that overflows and the intervening `Greeter` / `CanGreet` / `UseContext` notes together trace the loop, so the participants (the component, the consumer trait, and `UseContext`) are all named. What the diagnostic does *not* state is the remedy. In the hidden (`E0599`) form the cause is absent, exactly as for any [hidden unsatisfied dependency](../hidden/unsatisfied-dependency.md) — which is itself a reason to reach for a check when a `UseContext` wiring is suspect. -## Resolving it +## How cargo-cgp presents it -Break the cycle by wiring the component to a concrete provider that terminates the lookup rather than routing back to the context. `UseContext` is only safe as the *inner* provider of a higher-order provider or where the context supplies the component through some other impl; delegating a component's sole implementation to `UseContext` is always a cycle. When the overflow is genuinely a depth problem rather than a true cycle, raising `#![recursion_limit = "…"]` is the wrong fix here — the requirement never terminates, so no limit is high enough. +`cargo-cgp` recognizes the checked overflow and rewrites it into a statement of what actually went wrong. It keeps the `E0275` code but replaces the headline with `[CGP-E010] the wiring for the consumer trait \`CanGreet\` on context \`Person\` never resolves — the lookup recurses without terminating`, drops the `Greeter` / `CanGreet` / `UseContext` / `__CheckPerson` note chain entirely, and leaves the caret on the `GreeterComponent` entry inside the check block. A `help:` note names the usual cause — a component delegated to `UseContext` with no direct consumer-trait impl — and the two fixes: wire a real provider, or implement the consumer trait on the context. Where raw `rustc` hands you an overflow depth and a chain to decode, the tool states that the wiring recurses and why. -## Notes for tooling +The tool does more than restate the overflow when a cycle is tangled with a recoverable cause, and this is where its cycle guard earns its keep. In `mutual_cycle_with_cause` — `ProviderA` depends on `CanB`, `ProviderB` depends back on `CanA`, walked alongside a genuinely missing `width` field — the resolver cuts the `CanA → CanB → CanA` loop and follows the other branch to the leaf, leading with `[CGP-E002] the provider trait \`ProviderA\` with context \`App\` is not implemented for provider \`DoA\`` over a `root cause: [CGP-E106] missing field \`width\` on \`App\`` dependency tree (`[CGP-E101]` consumer-impl hop → `[CGP-E102]` provider-impl hop → `[CGP-E105]` `HasWidth` hop → the leaf). The cycle is not the headline; the concrete cause reachable down the non-cyclic branch is. Because the same mistake hides as `E0599` when reached by a method call, the tool promotes it the way it promotes any [hidden dependency](../hidden/unsatisfied-dependency.md) — synthesizing a check — which turns the hidden form into the `E0275` it then rewrites to `[CGP-E010]`. The codes are defined in the [cargo-cgp error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md). -A `cargo-cgp`-style post-processor should **detect the repeating requirement** — the same component/provider pair recurring in the `required for` chain — and report it as a *cycle* ("`GreeterComponent` on `Person` delegates to `UseContext`, which routes back to `GreeterComponent`") rather than surfacing the raw overflow depth, which tells the user nothing. Because the same mistake hides as `E0599` when called, a tool that already promotes hidden errors by synthesizing a check (see the [hidden class](../hidden/unsatisfied-dependency.md)) will convert this cycle from its hidden `E0599` form into the `E0275` form whose chain it can then analyze for the loop. +## Resolving it + +Break the cycle by wiring the component to a concrete provider that terminates the lookup rather than routing back to the context. `UseContext` is only safe as the *inner* provider of a higher-order provider or where the context supplies the component through some other impl; delegating a component's sole implementation to `UseContext` is always a cycle. When the overflow is genuinely a depth problem rather than a true cycle, raising `#![recursion_limit = "…"]` is the wrong fix here — the requirement never terminates, so no limit is high enough. ## Backing fixtures -- [acceptable/delegate_components/use_context_cycle.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/use_context_cycle.rs) — a component wired to `UseContext` with no terminating provider, forced through a `check_components!`; its `.stderr` pins the `E0275` overflow and the note chain that names the `Greeter` → `CanGreet` → `UseContext` loop. +- [`acceptable/wiring/constraints/use_context_cycle.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/constraints/use_context_cycle.rs) — a component wired to `UseContext` with no terminating provider, forced through a `check_components!`; its `.rust.stderr` pins the raw `E0275` overflow and the `Greeter` → `CanGreet` → `UseContext` note chain, and its `.cgp.stderr` the `[CGP-E010]` "never resolves" rewrite with the chain dropped and the caret held on the `GreeterComponent` entry. +- [`acceptable/wiring/constraints/mutual_cycle_with_cause.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/constraints/mutual_cycle_with_cause.rs) — a two-component cycle (`ProviderA` ↔ `ProviderB`) walked alongside a genuinely missing `width` field; its `.rust.stderr` is the raw overflow, and its `.cgp.stderr` shows the resolver's cycle guard cutting the loop and leading with `[CGP-E002]` over a `root cause: [CGP-E106] missing field \`width\`` tree — the cause down the non-cyclic branch rather than the cycle. ## Related diff --git a/docs/guides/README.md b/docs/guides/README.md index 728fe177..e86d2979 100644 --- a/docs/guides/README.md +++ b/docs/guides/README.md @@ -19,7 +19,7 @@ The authoring rules for these documents live in [../AGENTS.md](../AGENTS.md). Ea - [Adding capability supertraits](capability-supertraits.md) — `#[extend]` instead of native `:` supertrait syntax. - [Dispatching a component per type](dispatching-per-type.md) — the `open` statement or a namespace instead of a `UseDelegate` table. - [Organizing wiring with namespaces and prefixes](namespaces-and-prefixes.md) — keeping a growing `delegate_components!` table short with path prefixes, namespaces, and per-type defaults, worked as a refactoring of a real application. -- [Debugging CGP compile errors](debugging.md) — tracing a wiring failure back to its cause: reading the error's shape, moving the error to the wiring site with checks, reducing to a minimal reproduction, inspecting the macro expansion, and a decoder for the errors you actually see. +- [Debugging CGP compile errors](debugging.md) — reaching for [`cargo-cgp`](../reference/cargo-cgp.md) first (it traces the cause for you), then tracing a wiring failure by hand as the fallback: reading the error's shape, moving the error to the wiring site with checks, reducing to a minimal reproduction, inspecting the macro expansion, and a decoder for the errors you actually see. ## Summary @@ -40,4 +40,4 @@ This section condenses every guide above into one quick reference. Read it for t **Keep a growing wiring table short with namespaces and prefixes** ([guide](namespaces-and-prefixes.md)). As component counts rise, group components under path prefixes with `#[prefix(@path in DefaultNamespace)]`, lift a backend's provider choices into a reusable namespace (via `#[default_impl]` or namespace body entries) so a context joins it with one `namespace N;` line, and pull a separate concern's table in with a `for … in` loop. Two rules bite: a context can only override a path its namespace routes *to* but does not itself register, and a prefixed component's `#[default_impl]` must live in the namespace's own crate. -**When a wiring fails to compile, trace it, don't stare at it** ([guide](debugging.md)). CGP wiring is lazy, so one broken link surfaces as many errors on distant lines naming generated types; read the failing *trait* (an unmet `IsProviderFor` means a dependency is missing, a failed `DelegateComponent` means the lookup has no entry), move the error to the wiring site with `check_components!`, and when a large program puzzles you, reduce it to the smallest reproduction — or snapshot the expansion — rather than reasoning about coherence on paper. +**When a wiring fails to compile, run [`cargo-cgp`](../reference/cargo-cgp.md) first — it traces the cause for you — and trace by hand only as a fallback** ([guide](debugging.md)). CGP wiring is lazy, so one broken link surfaces as many errors on distant lines naming generated types; when you do read the raw output yourself, read the failing *trait* (an unmet `IsProviderFor` means a dependency is missing, a failed `DelegateComponent` means the lookup has no entry), move the error to the wiring site with `check_components!`, and when a large program puzzles you, reduce it to the smallest reproduction — or snapshot the expansion — rather than reasoning about coherence on paper. diff --git a/docs/guides/debugging.md b/docs/guides/debugging.md index 921cd733..da560588 100644 --- a/docs/guides/debugging.md +++ b/docs/guides/debugging.md @@ -1,6 +1,6 @@ # Debugging CGP compile errors -A broken CGP wiring rarely fails where it is wrong — it fails wherever the wiring is finally used, often as a wall of errors naming deeply nested types; this guide is the playbook for tracing such an error back to its cause. +A broken CGP wiring rarely fails where it is wrong — it fails wherever the wiring is finally used, often as a wall of errors naming deeply nested types; this guide is the playbook for tracing such an error back to its cause. The fastest path, though, is usually not to trace it by hand at all: **run [`cargo-cgp`](../reference/cargo-cgp.md) first** — it does this tracing for you — and treat the hand techniques in this guide as the fallback for when it is unavailable or leaves an error unrewritten. ## Why CGP errors are shaped the way they are @@ -8,6 +8,12 @@ The first thing to internalize is that CGP wiring is resolved *lazily*, so a sin The second thing is that the errors quote *generated* code, so they name types you never wrote. A component's wiring runs through [`IsProviderFor`](../reference/traits/is_provider_for.md), `DelegateComponent`, [`CanUseComponent`](../reference/traits/can_use_component.md), `RedirectLookup`, and the type-level `PathCons`/`Symbol`/`Chars` spines, and a failure prints these in full. The nested types are noise until you learn to read past them to the one trait and the one context that actually failed. The techniques below are all ways to cut through that noise: read the error's shape, move the error to where the mistake is, or shrink the program until the mistake is the only thing left. +## Reach for cargo-cgp first + +Before applying any technique in this guide, reach for [`cargo-cgp`](../reference/cargo-cgp.md) — CGP's first-class error toolchain — because it automates by machine exactly what the rest of this guide does by hand. Run `cargo cgp check` in place of `cargo check`: it turns on the next-generation trait solver, which *un-hides* the buried dependency errors the default solver suppresses, and it rewrites the classes it recognizes into a compact, root-cause-first form — a `[CGP-Exxx]` headline over a `cargo tree`-style dependency chain that names the real cause. For the common wiring mistakes — a missing field, an unwired component, an unmet dependency — the cause you would otherwise trace through a cascade is already at the top of the output. Install and run it per the [cargo-cgp reference](../reference/cargo-cgp.md). + +**The rest of this guide is the fallback.** Reach for the hand techniques below when `cargo-cgp` is not available on the machine, when it leaves an error largely as `rustc` wrote it — it is an early pre-release (v0.1.0-alpha) that reshapes the core classes but not yet every one, so an orphan-rule error, for instance, still comes through raw — or when you want to understand the raw diagnostic behind a reshaped one. In every such case the shape-reading, grepping, check-promotion, and reduction moves that follow still apply to the underlying compiler output, which is the same output `cargo-cgp` starts from. + ## Read the error's shape before its contents Most CGP errors are one of a few shapes, and recognizing the shape tells you what kind of mistake you are looking for before you decode a single nested type. Learn to read the *trait* in the error and ignore the type arguments on a first pass. @@ -91,6 +97,7 @@ A handful of specific compiler errors recur in CGP code, and each maps to a smal ## Related documentation +- [cargo-cgp](../reference/cargo-cgp.md) — CGP's error toolchain, the recommended first move this guide defers to; install and run it, and fall back to the hand techniques here only when it cannot help. - [Error catalog](../errors/README.md) — the reference companion to this guide: one document per class of post-codegen error, recording the diagnostic's shape, whether the root cause is present, and where it sits. Where this guide is the tracing playbook, the catalog is the per-class reference it indexes into — including the [hidden unsatisfied-dependency](../errors/hidden/unsatisfied-dependency.md) class, whose diagnostic omits the root cause entirely. - [Check traits](../concepts/check-traits.md) — why wiring is lazy and how checks force a readable error at the wiring site. - [`check_components!`](../reference/macros/check_components.md) — the full checking surface, including `#[check_providers]` and checking generic components with concrete parameters. diff --git a/docs/implementation/AGENTS.md b/docs/implementation/AGENTS.md index b0a74d4c..b6934687 100644 --- a/docs/implementation/AGENTS.md +++ b/docs/implementation/AGENTS.md @@ -54,7 +54,7 @@ The **Tests** and **Source** sections are always bullet lists, never flowing par ## The Tests and Snapshots sections -Every document links its related tests in a **Tests** section, stating the behavior each test pins in the document's own words. List the behavioral tests in [crates/tests/cgp-tests](../../crates/tests/cgp-tests), the rejection cases in [crates/tests/cgp-macro-tests](../../crates/tests/cgp-macro-tests), and the compile-fail fixtures in [crates/tests/cgp-compile-fail-tests](../../crates/tests/cgp-compile-fail-tests) that exercise the construct, each as a link to the file with a one-line description of what it verifies. Because the reference documents no longer point at tests, this section is the canonical index of a construct's coverage — write it so an agent can see at a glance what behavior is guarded and, by omission, what is not. +Every document links its related tests in a **Tests** section, stating the behavior each test pins in the document's own words. List the behavioral tests in [crates/tests/cgp-tests](../../crates/tests/cgp-tests) and the rejection cases in [crates/tests/cgp-macro-tests](../../crates/tests/cgp-macro-tests) that exercise the construct, each as a link to the file with a one-line description of what it verifies. The post-codegen compile-fail cases no longer live in this repository: they are UI fixtures in `cargo-cgp` (linked by GitHub URL) and cataloged by class in the [error catalog](../errors/README.md), so a `## Failure modes` section links out to the catalog class and its backing `cargo-cgp` fixture rather than to a now-removed local fixture. Because the reference documents no longer point at tests, this section is the canonical index of a construct's coverage — write it so an agent can see at a glance what behavior is guarded and, by omission, what is not. Every entrypoint document additionally carries a **Snapshots** section that indexes the macro-expansion snapshots for that macro and calls out which variants are missing. Macro snapshots are pinned by the `snapshot_*!` macros in [crates/macros/cgp-macro-test-util](../../crates/macros/cgp-macro-test-util) and, per [crates/tests/AGENTS.md](../../crates/tests/AGENTS.md), each macro's canonical full-expansion snapshot plus its genuinely distinct variants live only in the concept target that owns the macro's feature. The Snapshots section is the central place to find them: link each snapshot file, describe which expansion variant it captures (the plain case, a supertrait-and-default-method case, a lifetime-and-type-parameter case, a namespace-prefix case, and so on), and then state plainly which variants have *no* snapshot yet, so a gap in coverage is visible rather than silently absent. When you add a snapshot, register it here; when a variant you know matters is untested, record it as a missing snapshot rather than leaving the section to imply full coverage. @@ -64,12 +64,12 @@ Record limitations, surprising behavior, and confirmed bugs in the implementatio ## Documenting how a macro's expansion can fail to compile -Because a CGP macro expands to ordinary Rust that the compiler checks afterward, a full picture of a macro includes the ways its *accepted* input can still fail to compile downstream — a dimension distinct from the input a macro rejects at parse time. Enumerate these compile-failure modes as part of documenting a macro, and sort each into one of two kinds, mirroring the split in [crates/tests/cgp-compile-fail-tests](../../crates/tests/cgp-compile-fail-tests): +Because a CGP macro expands to ordinary Rust that the compiler checks afterward, a full picture of a macro includes the ways its *accepted* input can still fail to compile downstream — a dimension distinct from the input a macro rejects at parse time. Enumerate these compile-failure modes as part of documenting a macro, and sort each into one of two kinds. This acceptable-versus-problematic split is a *documentation* distinction — whether the failure is a defect — and no longer a directory split: `cargo-cgp` files its UI fixtures by the *quality of the output* it renders (`ok`/`acceptable`/`usability`), while the acceptable/problematic distinction here decides whether a failure is *also* recorded as a defect in the macro's `## Known issues`: -- An **acceptable** failure is one the macro **intentionally delegates to the Rust compiler** because it lacks the whole-program view the check needs — two `delegate_components!` blocks that map the same key into overlapping impls, a lazily-wired provider whose impl-side dependency the context does not satisfy, or an ill-formed per-entry generic the compiler rejects as unconstrained. This is correct behavior, not a bug, so it does *not* belong under Known issues. Record it in a dedicated **`## Failure modes`** section, kept separate precisely so a reader never mistakes it for a defect: describe *what the compiler rejects and why the macro cannot (or should not) catch it earlier*, show a short code snippet of the failing input, and index the covering fixture from the Tests section. [entrypoints/delegate_components.md](entrypoints/delegate_components.md) is the model. +- An **acceptable** failure is one the macro **intentionally delegates to the Rust compiler** because it lacks the whole-program view the check needs — two `delegate_components!` blocks that map the same key into overlapping impls, a lazily-wired provider whose impl-side dependency the context does not satisfy, or an ill-formed per-entry generic the compiler rejects as unconstrained. This is correct behavior, not a bug, so it does *not* belong under Known issues. Record it in a dedicated **`## Failure modes`** section, kept separate precisely so a reader never mistakes it for a defect: describe *what the compiler rejects and why the macro cannot (or should not) catch it earlier*, show a short code snippet of the failing input, and link the covering class in the [error catalog](../errors/README.md) and its backing `cargo-cgp` UI fixture (as a GitHub URL) rather than a now-removed local fixture. [entrypoints/delegate_components.md](entrypoints/delegate_components.md) is the model. - A **problematic** failure is a **defect**: input the macro should have rejected with a spanned error, or an expansion that emits invalid Rust. Record it under **`## Known issues`**, alongside the construct's other bugs, with the current (wrong) behavior, the correct behavior, and a short code snippet of the offending input; note the user-visible consequence in the reference document's Known issues and cross-link; and index the pinning fixture from the Tests section. Remove the note when the defect is fixed, per the synchronization rule. -Give every documented case, in either section, a short illustrative snippet of the CGP code that triggers it (and the offending expansion fragment when it clarifies the failure), following the code-snippet guidance above. Whenever you add, move, or reclassify a compile-fail fixture in `cgp-compile-fail-tests`, update the owning macro's implementation document in the same change so its Failure modes, Known issues, and Tests sections stay the canonical index of how that macro can fail to compile. This is the "enumerate every way the output can expand" review concern (see the macro review workflow in [../../AGENTS.md](../../AGENTS.md)) written down as documentation, so a later agent inherits the failure map rather than rediscovering it. +Give every documented case, in either section, a short illustrative snippet of the CGP code that triggers it (and the offending expansion fragment when it clarifies the failure), following the code-snippet guidance above. Whenever you add or bless a `cargo-cgp` UI fixture for one of these classes — following [cargo-cgp's UI-test guide](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/README.md) — or reclassify a failure, update the [error catalog](../errors/README.md) class and the owning macro's implementation document in the same change so its Failure modes, Known issues, and Tests sections stay the canonical index of how that macro can fail to compile. This is the "enumerate every way the output can expand" review concern (see the macro review workflow in [../../AGENTS.md](../../AGENTS.md)) written down as documentation, so a later agent inherits the failure map rather than rediscovering it. The reader-facing documentation of these post-codegen failures — organized by the *kind* of error a user sees rather than by macro — is being consolidated into the [error catalog](../errors/README.md) under `docs/errors/`, which is becoming the canonical home for the acceptable-failure prose that currently lives in these documents' `## Failure modes` sections. The migration proceeds class by class (the catalog's README tracks status). As a class moves, its `## Failure modes` prose is replaced by a cross-link to the catalog, and its fixtures cross-link to the catalog rather than here; the implementation document keeps the *mechanics* (the error-span behavior, the pipeline detail) and, for a **problematic** defect, retains the `## Known issues` explanation of why it is a bug and what the fix would be, cross-linked to the catalog's entry for the observable error. Macro-*rejection* cases (the `assert_macro_rejects` cases in `cgp-macro-tests`) are unaffected and stay here in full. diff --git a/docs/implementation/asts/attributes/default_impl.md b/docs/implementation/asts/attributes/default_impl.md index 209bc98f..a5757da2 100644 --- a/docs/implementation/asts/attributes/default_impl.md +++ b/docs/implementation/asts/attributes/default_impl.md @@ -37,7 +37,7 @@ The behavioral and snapshot tests exercise the emitted impl, the wiring it enabl - [namespaces/default_impls.rs](../../../../crates/tests/cgp-tests/tests/namespaces/default_impls.rs) pins the emitted namespace-default impl (`snapshot_cgp_impl!`), and [namespaces/default_impls_wiring.rs](../../../../crates/tests/cgp-tests/tests/namespaces/default_impls_wiring.rs) checks a context picks up the default. - [namespaces/default_impl_use_type.rs](../../../../crates/tests/cgp-tests/tests/namespaces/default_impl_use_type.rs) pins that the registration impl carries no `where` clause when the provider has a `#[use_type]` dependency, and resolves such a provider through a context that joins the namespace. -- The cross-crate orphan restriction on a default is pinned in [acceptable/cgp_namespace/default_impl_foreign_prefix_path.rs](../../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_prefix_path.rs) (a *prefixed* component's foreign path key) and [acceptable/cgp_namespace/default_impl_foreign_component.rs](../../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_component.rs) (a foreign *unprefixed* component marker key), both the [orphan-rule violation](../../../errors/wiring/orphan-rule.md) class. +- The cross-crate orphan restriction on a default is pinned by the `cargo-cgp` UI fixtures [`usability/wiring/orphan/default_impl_foreign_prefix_path.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/usability/wiring/orphan/default_impl_foreign_prefix_path.rs) (a *prefixed* component's foreign path key) and [`usability/wiring/orphan/default_impl_foreign_component.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/usability/wiring/orphan/default_impl_foreign_component.rs) (a foreign *unprefixed* component marker key), both the [orphan-rule violation](../../../errors/wiring/orphan-rule.md) class. The duplicate-key conflict `#[cgp_impl]` defers to the compiler is covered on the host's own page — see [Failure modes in entrypoints/cgp_impl.md](../../entrypoints/cgp_impl.md#failure-modes). diff --git a/docs/implementation/asts/attributes/use_type.md b/docs/implementation/asts/attributes/use_type.md index 965984b7..a7822f6c 100644 --- a/docs/implementation/asts/attributes/use_type.md +++ b/docs/implementation/asts/attributes/use_type.md @@ -74,11 +74,11 @@ The behavioral tests span every host and every form the attribute accepts: The rejection cases are pinned in [parser_rejections/use_type.rs](../../../../crates/tests/cgp-macro-tests/tests/parser_rejections/use_type.rs): a `=` equality on a component, and a duplicate identifier or alias across specs, within one braced list, and on a component. -Four acceptable compile-fail fixtures pin the post-codegen failures the attribute defers to the compiler: +Four `cargo-cgp` UI fixtures pin the post-codegen failures the attribute defers to the compiler (`cargo-cgp` files each by the quality of the output it renders, so the cyclic-context case sits under `usability/`): -- [acceptable/cgp_component/use_type_foreign_unsatisfied.rs](../../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/use_type_foreign_unsatisfied.rs) (a foreign `in Types` bound *enforced* on the trait rather than dropped) and [acceptable/cgp_fn/use_type_nested_unsatisfied.rs](../../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs) (the same at a nested two-hop depth) are both the [check-trait-failure](../../../errors/checks/check-trait-failure.md) class. -- [acceptable/cgp_fn/use_type_unknown_assoc.rs](../../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_unknown_assoc.rs) (a misnamed imported associated type lowered into an unresolvable path, whose caret confirms the substitution preserves the user's identifier span) is the [unresolved-imported-type](../../../errors/lowering/unresolved-imported-type.md) class. -- [acceptable/cgp_fn/use_type_cyclic_context.rs](../../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_cyclic_context.rs) (two `in Context` clauses that reference each other, so grounding never resolves either context and leaves the bare aliases in type position) surfaces as `E0425` "cannot find type" with the caret on the unresolved `in` alias — the sibling of the unresolved-imported-type class, but on the *context* rather than the associated-type name. +- [`acceptable/use-type/use_type_foreign_unsatisfied.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/use-type/use_type_foreign_unsatisfied.rs) (a foreign `in Types` bound *enforced* on the trait rather than dropped) and [`acceptable/use-type/use_type_nested_unsatisfied.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/use-type/use_type_nested_unsatisfied.rs) (the same at a nested two-hop depth) are both the [check-trait-failure](../../../errors/checks/check-trait-failure.md) class. +- [`acceptable/lowering/use_type_unknown_assoc.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/lowering/use_type_unknown_assoc.rs) (a misnamed imported associated type lowered into an unresolvable path, whose caret confirms the substitution preserves the user's identifier span) is the [unresolved-imported-type](../../../errors/lowering/unresolved-imported-type.md) class. +- [`usability/lowering/use_type_cyclic_context.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/usability/lowering/use_type_cyclic_context.rs) (two `in Context` clauses that reference each other, so grounding never resolves either context and leaves the bare aliases in type position) surfaces as `E0425` "cannot find type" with the caret on the unresolved `in` alias — the sibling of the unresolved-imported-type class, but on the *context* rather than the associated-type name. ## Source diff --git a/docs/implementation/entrypoints/cgp_auto_getter.md b/docs/implementation/entrypoints/cgp_auto_getter.md index ec8f56e8..1e694278 100644 --- a/docs/implementation/entrypoints/cgp_auto_getter.md +++ b/docs/implementation/entrypoints/cgp_auto_getter.md @@ -56,7 +56,7 @@ The conversions are exactly those `#[cgp_getter]` uses — `&str` reads a `Strin **A getter can read a field of another type.** A method with a typed receiver (`fn foo_bar(foo: &Self::Foo) -> &Self::Bar`) reads the field out of that receiver type; `Self` in the receiver is rewritten to the context, and the `HasField` bound lands on the receiver type rather than the context. -**Unsupported field-type combinations are deferred to the compiler.** The `Option` and slice shorthands each cover a single field shape; a combination CGP provides no rule for — most notably `Option<&[T]>` — is lowered literally rather than given a bespoke rule, so it reaches the compiler as invalid Rust rather than a macro-time rejection. `parse_field_type` applies the `Option<&T>` rule to `Option<&[T]>` and emits a `HasField` bound over the unsized `Option<[T]>`, which `rustc` rejects. This is a deliberate boundary shared by `#[cgp_getter]` and `#[cgp_fn]` implicits (all three call `parse_field_type`), pinned as an acceptable failure by [acceptable/cgp_auto_getter/option_slice.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_auto_getter/option_slice.rs). +**Unsupported field-type combinations are deferred to the compiler.** The `Option` and slice shorthands each cover a single field shape; a combination CGP provides no rule for — most notably `Option<&[T]>` — is lowered literally rather than given a bespoke rule, so it reaches the compiler as invalid Rust rather than a macro-time rejection. `parse_field_type` applies the `Option<&T>` rule to `Option<&[T]>` and emits a `HasField` bound over the unsized `Option<[T]>`, which `rustc` rejects. This is a deliberate boundary shared by `#[cgp_getter]` and `#[cgp_fn]` implicits (all three call `parse_field_type`), pinned as an acceptable failure by the `cargo-cgp` UI fixture [`usability/lowering/option_slice.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/usability/lowering/option_slice.rs) — filed under `usability/` because cargo-cgp passes rustc's `Sized` error through rather than rewriting it. ## Snapshots diff --git a/docs/implementation/entrypoints/cgp_component.md b/docs/implementation/entrypoints/cgp_component.md index 6897213d..d2026a1f 100644 --- a/docs/implementation/entrypoints/cgp_component.md +++ b/docs/implementation/entrypoints/cgp_component.md @@ -66,7 +66,7 @@ The **marker struct's name span** is the provider identifier's span, not `Span:: ## Failure modes -A **name clash on the derived marker** — a module that declares its own `GreeterComponent` alongside a `#[cgp_component(Greeter)]` that derives the same marker — defines the name twice and fails with `E0428`, the [conflicting wiring](../../errors/wiring/conflicting-wiring.md) error class. What this document pins is the **span**: the `E0428` "previous definition here" note lands on the `Greeter` provider name inside `#[cgp_component(..)]`, not on the whole attribute, per the marker-span behavior above. [acceptable/cgp_component/duplicate_component_name.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/duplicate_component_name.rs) is the regression test for that span, since a regression to `call_site` would move the note onto the attribute. +A **name clash on the derived marker** — a module that declares its own `GreeterComponent` alongside a `#[cgp_component(Greeter)]` that derives the same marker — defines the name twice and fails with `E0428`, the [conflicting wiring](../../errors/wiring/conflicting-wiring.md) error class. What this document pins is the **span**: the `E0428` "previous definition here" note lands on the `Greeter` provider name inside `#[cgp_component(..)]`, not on the whole attribute, per the marker-span behavior above. The `cargo-cgp` UI fixture [`acceptable/wiring/duplicate-keys/duplicate_component_name.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/duplicate-keys/duplicate_component_name.rs) is the regression test for that span, since a regression to `call_site` would move the note onto the attribute. ## Snapshots @@ -88,7 +88,7 @@ The behavioral tests confirm the generated wiring works: - [generic_components/component_type_param.rs](../../../crates/tests/cgp-tests/tests/generic_components/component_type_param.rs) wires a single-type-parameter component to one provider, passes `check_components!` for a concrete type argument, and computes an area at run time. - [generic_components/component_lifetime.rs](../../../crates/tests/cgp-tests/tests/generic_components/component_lifetime.rs) wires the lifetime-carrying component and passes `check_components!`. - [cgp-macro-tests/tests/parser_rejections/cgp_component.rs](../../../crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_component.rs) asserts the macro rejects a non-trait item and a trait carrying a const generic parameter. -- [cgp-compile-fail-tests acceptable/cgp_component/duplicate_component_name.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/duplicate_component_name.rs) pins that the derived marker's `E0428` "previous definition" note falls on the provider name rather than the whole attribute — a regression test for the marker-name span (see [Failure modes](#failure-modes)). +- The `cargo-cgp` UI fixture [`acceptable/wiring/duplicate-keys/duplicate_component_name.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/duplicate-keys/duplicate_component_name.rs) pins that the derived marker's `E0428` "previous definition" note falls on the provider name rather than the whole attribute — a regression test for the marker-name span (see [Failure modes](#failure-modes)). Its error class is [conflicting wiring](../../errors/wiring/conflicting-wiring.md). ## Source diff --git a/docs/implementation/entrypoints/cgp_impl.md b/docs/implementation/entrypoints/cgp_impl.md index 0cf96df3..f687076e 100644 --- a/docs/implementation/entrypoints/cgp_impl.md +++ b/docs/implementation/entrypoints/cgp_impl.md @@ -68,11 +68,11 @@ The one wholly-synthesized item is each `#[default_impl]` delegation impl, built ## Failure modes -Both failures below are ones `#[cgp_impl]` defers to the compiler, and both are the [conflicting wiring](../../errors/wiring/conflicting-wiring.md) error class; what this document pins is where each caret lands. Each is covered by a fixture under [acceptable/cgp_impl/](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_impl) in `cgp-compile-fail-tests`. +Both failures below are ones `#[cgp_impl]` defers to the compiler, and both are the [conflicting wiring](../../errors/wiring/conflicting-wiring.md) error class; what this document pins is where each caret lands. Each is covered by a `cargo-cgp` UI fixture, linked per case below. -A **duplicate provider name** — two `#[cgp_impl(new Foo)]` blocks — declares `pub struct Foo;` twice (`E0428`) and emits two conflicting provider impls (`E0119`). The `E0428` carets land on the `Foo` inside each `#[cgp_impl(new …)]` and the `E0119` carets on each provider `impl` block, per [Error spans](#error-spans). Pinned by [acceptable/cgp_impl/duplicate_provider_name.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_impl/duplicate_provider_name.rs). +A **duplicate provider name** — two `#[cgp_impl(new Foo)]` blocks — declares `pub struct Foo;` twice (`E0428`) and emits two conflicting provider impls (`E0119`). The `E0428` carets land on the `Foo` inside each `#[cgp_impl(new …)]` and the `E0119` carets on each provider `impl` block, per [Error spans](#error-spans). Pinned by [`acceptable/wiring/duplicate-keys/duplicate_provider_name.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/duplicate-keys/duplicate_provider_name.rs). -A **duplicate `#[default_impl]` key** — two `#[cgp_impl]` blocks each registering the same key as a per-type default in the same namespace — emits two conflicting delegation impls and fails with `E0119`. The carets land on the `Key` inside each `#[default_impl(Key in …)]` per [Error spans](#error-spans). Pinned by [acceptable/cgp_impl/duplicate_default_impl.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_impl/duplicate_default_impl.rs). +A **duplicate `#[default_impl]` key** — two `#[cgp_impl]` blocks each registering the same key as a per-type default in the same namespace — emits two conflicting delegation impls and fails with `E0119`. The carets land on the `Key` inside each `#[default_impl(Key in …)]` per [Error spans](#error-spans). Pinned by [`acceptable/wiring/namespace-paths/duplicate_default_impl.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/namespace-paths/duplicate_default_impl.rs). ## Known issues diff --git a/docs/implementation/entrypoints/cgp_namespace.md b/docs/implementation/entrypoints/cgp_namespace.md index fe67026d..7370cfde 100644 --- a/docs/implementation/entrypoints/cgp_namespace.md +++ b/docs/implementation/entrypoints/cgp_namespace.md @@ -109,26 +109,27 @@ The behavioral tests confirm the generated namespaces wire correctly: - [namespaces/for_where_clause.rs](../../../crates/tests/cgp-tests/tests/namespaces/for_where_clause.rs) pins the `for <..> in .. where ..` loop's `where` clause landing on each generated impl (a `snapshot_delegate_components!`, since `delegate_components!` owns the `for`-loop form). - [namespaces/default_impl_use_type.rs](../../../crates/tests/cgp-tests/tests/namespaces/default_impl_use_type.rs) registers a provider with a `#[use_type]` abstract-type dependency into a namespace via `#[default_impl]` and resolves it through a context that joins the namespace. Its `snapshot_cgp_impl!` pins that the namespace-registration impl carries no `where` clause — the provider's impl-side bounds must not leak onto the registration impl's `Self` (the path key), or the abstract-type dependency would be demanded of the path rather than the context. See [asts/attributes/default_impl.md](../asts/attributes/default_impl.md) for the mechanism. -The cross-crate coverage in `cgp-test-crate-b` confirms the orphan-safe direction of the default-impl coherence rule: +The cross-crate coverage confirming the orphan-safe direction of the default-impl coherence rule now lives in `cargo-cgp` (its auxiliary crates `cgp-test-crate-a`/`-b`, migrated out of this repository): -- [cgp-test-crate-b/src/lib.rs](../../../crates/tests/cgp-test-crate-b/src/lib.rs) registers a *local* component into `cgp-test-crate-a`'s foreign `AppNamespace` with `#[default_impl(FarewellComponent in AppNamespace)]` — legal because the crate owns the component key — and resolves it through a local context that joins the namespace. +- [`ok/cross_crate_wiring.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/ok/cross_crate_wiring.rs) builds `cgp-test-crate-b`, which registers a *local* component into `cgp-test-crate-a`'s foreign `AppNamespace` with `#[default_impl(FarewellComponent in AppNamespace)]` — legal because the crate owns the component key — and resolves it through a local context that joins the namespace. Its failing orphan-rule counterparts are the `usability/wiring/orphan/` fixtures cataloged under [orphan-rule](../../errors/wiring/orphan-rule.md). The rejection cases in `cgp-macro-tests` pin the attribute rejection: - [parser_rejections/cgp_namespace.rs](../../../crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_namespace.rs) asserts the macro rejects an attribute on a `:` mapping key, on a `=>` redirect key, and on a key inside a `for` loop. -The compile-fail fixtures in `cgp-compile-fail-tests` pin accepted expansions that fail to compile — **acceptable** failures deferred to the compiler, documented in the [error catalog](../../errors/README.md) (the [Failure modes](#failure-modes) section links each to its class): - -- [acceptable/cgp_namespace/duplicate_path_key.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/duplicate_path_key.rs) — two identical `@`-path prefix-rewrite entries conflict (`E0119`); its `.stderr` pins the [error span](#error-spans) landing on the duplicated path leaf rather than the whole block, even though the key lowers to a synthesized `PathCons<..>` type. -- [acceptable/cgp_namespace/override_registered_path.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/override_registered_path.rs) — a context joins a namespace that registers a path (via `#[default_impl]`) and also wires that path directly, so the `namespace` blanket impl and the direct entry both implement `DelegateComponent` for the path (`E0119`, with the `downstream crates may implement` note); the context-level shape of the [namespace override conflict](../../errors/wiring/namespace-override-conflict.md) class. -- [acceptable/cgp_namespace/inherited_override_conflict.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/inherited_override_conflict.rs) — a child namespace that redefines a key its parent binds, so the inheritance blanket impl and the child's specific entry overlap (a *single* `E0119` on `ChildNs<_>` for the component marker, no note); the namespace-level shape of the [namespace override conflict](../../errors/wiring/namespace-override-conflict.md) class. -- [acceptable/cgp_namespace/for_loop_bare_key.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/for_loop_bare_key.rs) — a bare-key `for` loop alongside a `namespace` join, whose two blanket `DelegateComponent`/`IsProviderFor` impls overlap (`E0119`, fully-generic carets, no downstream note); the [overlapping namespace forwarding](../../errors/wiring/namespace-forwarding-conflict.md) class. -- [acceptable/cgp_namespace/two_namespaces_joined.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/two_namespaces_joined.rs) — two `namespace` joins on one context, each emitting a blanket forwarding impl over every key, which overlap (`E0119`, fully-generic carets); the same [overlapping namespace forwarding](../../errors/wiring/namespace-forwarding-conflict.md) class. -- [acceptable/cgp_namespace/default_impl_foreign_prefix_path.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_prefix_path.rs) — a downstream crate registers a default for an upstream *prefixed* component into the upstream namespace, whose `impl Namespace<_> for PathCons<..>` is wholly foreign and violates the orphan rule (`E0210` on `__Components__`); the [orphan-rule violation](../../errors/wiring/orphan-rule.md) class. Depends on `cgp-test-crate-a` for the upstream component and namespace. -- [acceptable/cgp_namespace/default_impl_foreign_component.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_component.rs) — the same orphan keyed on a foreign *unprefixed* component marker rather than a path (`E0210` on `__Components__`); shows the restriction is not specific to prefixes. Same [orphan-rule violation](../../errors/wiring/orphan-rule.md) class. -- [acceptable/cgp_namespace/reopen_foreign_namespace.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/reopen_foreign_namespace.rs) — a `cgp_namespace!` block without `new` re-opening a foreign namespace, whose entry impl is foreign (`E0210` on `__Table__`, caret on the whole block); same [orphan-rule violation](../../errors/wiring/orphan-rule.md) class. -- [acceptable/cgp_namespace/unregistered_prefix_path.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/unregistered_prefix_path.rs) — a `#[prefix]`-ed component joined through its namespace with no provider bound at its path, so a `check_components!` reports the redirect target's `DefaultNamespace`/`DelegateComponent` lookup as unsatisfied (`E0277`); the [unregistered namespace path](../../errors/checks/unregistered-namespace-path.md) class. -- [acceptable/cgp_namespace/inheritance_cycle.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/inheritance_cycle.rs) — two mutually-inheriting namespaces whose inheritance impls' `where` clauses loop, overflowing (`E0275`) eagerly at both `cgp_namespace!` definitions; the [namespace inheritance cycle](../../errors/wiring/namespace-inheritance-cycle.md) class. +The post-codegen compile-fail cases are now `cargo-cgp` UI fixtures that pin accepted expansions failing to compile — all **acceptable** failures (deferred to the compiler by design, none a defect), documented in the [error catalog](../../errors/README.md); the [Failure modes](#failure-modes) section links each to its class. `cargo-cgp` files each fixture by the *quality of the output* it renders (`acceptable/` when it already leads with the cause, `usability/` when the cause is present but buried), which is why the orphan-rule cases below sit under `usability/` even though none is a defect: + +- [`acceptable/wiring/namespace-paths/namespace_duplicate_path_key.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/namespace-paths/namespace_duplicate_path_key.rs) — two identical `@`-path prefix-rewrite entries conflict (`E0119`); its `.rust.stderr` pins the [error span](#error-spans) landing on the duplicated path leaf rather than the whole block, even though the key lowers to a synthesized `PathCons<..>` type. +- [`acceptable/wiring/namespace-paths/override_registered_path.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/namespace-paths/override_registered_path.rs) — a context joins a namespace that registers a path (via `#[default_impl]`) and also wires that path directly, so the `namespace` blanket impl and the direct entry both implement `DelegateComponent` for the path (`E0119`, with the `downstream crates may implement` note); the context-level shape of the [namespace override conflict](../../errors/wiring/namespace-override-conflict.md) class. +- [`acceptable/wiring/namespace-paths/inherited_override_conflict.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/namespace-paths/inherited_override_conflict.rs) — a child namespace that redefines a key its parent binds, so the inheritance blanket impl and the child's specific entry overlap (a *single* `E0119` on `ChildNs<_>` for the component marker, no note); the namespace-level shape of the [namespace override conflict](../../errors/wiring/namespace-override-conflict.md) class. +- [`acceptable/wiring/duplicate-keys/for_loop_bare_key.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/duplicate-keys/for_loop_bare_key.rs) — a bare-key `for` loop alongside a `namespace` join, whose two blanket `DelegateComponent`/`IsProviderFor` impls overlap (`E0119`, fully-generic carets, no downstream note); the [overlapping namespace forwarding](../../errors/wiring/namespace-forwarding-conflict.md) class. +- [`acceptable/wiring/namespace-paths/two_namespaces_joined.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/namespace-paths/two_namespaces_joined.rs) — two `namespace` joins on one context, each emitting a blanket forwarding impl over every key, which overlap (`E0119`, fully-generic carets); the same [overlapping namespace forwarding](../../errors/wiring/namespace-forwarding-conflict.md) class. +- [`usability/wiring/orphan/default_impl_foreign_prefix_path.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/usability/wiring/orphan/default_impl_foreign_prefix_path.rs) — a downstream crate registers a default for an upstream *prefixed* component into the upstream namespace, whose `impl Namespace<_> for PathCons<..>` is wholly foreign and violates the orphan rule (`E0210` on `__Components__`); the [orphan-rule violation](../../errors/wiring/orphan-rule.md) class. A cross-crate case: an `//@aux-build` upstream crate supplies the component and namespace. +- [`usability/wiring/orphan/default_impl_foreign_component.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/usability/wiring/orphan/default_impl_foreign_component.rs) — the same orphan keyed on a foreign *unprefixed* component marker rather than a path (`E0210` on `__Components__`); shows the restriction is not specific to prefixes. Same [orphan-rule violation](../../errors/wiring/orphan-rule.md) class. +- [`usability/wiring/orphan/reopen_foreign_namespace.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/usability/wiring/orphan/reopen_foreign_namespace.rs) — a `cgp_namespace!` block without `new` re-opening a foreign namespace, whose entry impl is foreign (`E0210` on `__Table__`, caret on the whole block); same [orphan-rule violation](../../errors/wiring/orphan-rule.md) class. +- [`acceptable/resolution/unregistered_prefix_path.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/resolution/unregistered_prefix_path.rs) — a `#[prefix]`-ed component joined through its namespace with no provider bound at its path, so a `check_components!` reports the redirect target's `DefaultNamespace`/`DelegateComponent` lookup as unsatisfied (`E0277`); the [unregistered namespace path](../../errors/checks/unregistered-namespace-path.md) class. + +The **namespace inheritance cycle** is the one class with no `cargo-cgp` fixture: under cargo-cgp's next-generation trait solver the two mutually-inheriting namespaces compile clean, with no `E0275` left to snapshot — a *missing*-error divergence (the reverse of the usual next-solver caveat) that makes this the single un-migrated class, recorded as such in [cargo-cgp's tests/README.md](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/README.md). The eager `E0275` overflow at both `cgp_namespace!` definitions remains a plain-`rustc` behavior, described in the [namespace inheritance cycle](../../errors/wiring/namespace-inheritance-cycle.md) class. ## Source diff --git a/docs/implementation/entrypoints/check_components.md b/docs/implementation/entrypoints/check_components.md index c247e7a0..fbc618cf 100644 --- a/docs/implementation/entrypoints/check_components.md +++ b/docs/implementation/entrypoints/check_components.md @@ -50,7 +50,7 @@ A component with parameters places them in the `__Params__` slot — a single pa A few `check_components!` inputs are accepted by the macro but rejected — or silently do nothing — downstream, each intended behavior rather than a bug. Unlike the empty `#[check_providers()]` the parser rejects outright, these are left to the compiler or to the user because catching them would require second-guessing a legitimate, if degenerate, request. -An **unsatisfied dependency** is the central intended failure and the reason the macro exists: a checked component whose provider has an impl-side dependency the context cannot meet fails at the check site rather than lazily at the eventual call site. Its error anatomy — the `E0277` that names the missing bound through `IsProviderFor`, and the contrast with the hidden call-site error the unchecked wiring produces — is documented as the [check-trait failure](../../errors/checks/check-trait-failure.md) error class in the catalog. What *this* document pins is the **span**: the unsatisfied-bound caret lands on the component inside the `check_components!` block, not on the shared context type, because the check impl re-spans that one context token onto each listed component in turn with `override_span`. [acceptable/check_components/missing_dependency.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_dependency.rs) is the regression test for that re-span; the [`delegate_components!` counterpart](delegate_components.md) leaves the same wiring unchecked to show the contrasting lazy error. +An **unsatisfied dependency** is the central intended failure and the reason the macro exists: a checked component whose provider has an impl-side dependency the context cannot meet fails at the check site rather than lazily at the eventual call site. Its error anatomy — the `E0277` that names the missing bound through `IsProviderFor`, and the contrast with the hidden call-site error the unchecked wiring produces — is documented as the [check-trait failure](../../errors/checks/check-trait-failure.md) error class in the catalog. What *this* document pins is the **span**: the unsatisfied-bound caret lands on the component inside the `check_components!` block, not on the shared context type, because the check impl re-spans that one context token onto each listed component in turn with `override_span`. The `cargo-cgp` UI fixture [`acceptable/fields/missing_dependency.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/fields/missing_dependency.rs) is the regression test for that re-span; the [`delegate_components!` counterpart](delegate_components.md) leaves the same wiring unchecked to show the contrasting lazy error. An **empty check table** (`Context { }`, or every entry checking nothing) emits the check trait with no impls, so it compiles and verifies nothing. This is not rejected because a table trimmed down to nothing during editing is indistinguishable from a deliberately empty one, and an empty table causes no harm — it simply asserts nothing. @@ -75,8 +75,8 @@ The behavioral coverage for `check_components!` is the compile-time assertion it - The files listed under Snapshots are compile-only tests, so a successful build is the passing check. Each pins both the expansion (via the snapshot) and the fact that the asserted wiring resolves. - [parser_rejections/check_components.rs](../../../crates/tests/cgp-macro-tests/tests/parser_rejections/check_components.rs) — the table-level attribute rejections: an empty `#[check_providers()]`, a repeated `#[check_providers]`, a repeated `#[check_trait]`, and an unknown attribute each fail to parse. -- [cgp-compile-fail-tests acceptable/check_components/missing_dependency.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_dependency.rs) pins that an unsatisfied impl-side dependency is reported with its `E0277` caret on the checked component inside the block — a regression test for the `override_span` re-span (see [Failure modes](#failure-modes)). Its error anatomy is the [check-trait failure](../../errors/checks/check-trait-failure.md) class. -- [cgp-compile-fail-tests acceptable/check_components/ordinary_bound_unsatisfied.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/ordinary_bound_unsatisfied.rs) and [acceptable/check_components/generic_context_ordinary_bound.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/generic_context_ordinary_bound.rs) pin a check surfacing an *ordinary* trait bound (`Scalar: Eq`, unmet by a wired `f64`) as the primary `E0277`, from a concrete context and from a generic ` Wrapper` table checked at `Wrapper` respectively. Their error anatomy is the [unsatisfied ordinary trait bound](../../errors/checks/ordinary-trait-bound.md) class. +- The `cargo-cgp` UI fixture [`acceptable/fields/missing_dependency.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/fields/missing_dependency.rs) pins that an unsatisfied impl-side dependency is reported with its `E0277` caret on the checked component inside the block — a regression test for the `override_span` re-span (see [Failure modes](#failure-modes)). Its error anatomy is the [check-trait failure](../../errors/checks/check-trait-failure.md) class. +- The `cargo-cgp` UI fixtures [`acceptable/resolution/ordinary_bound_unsatisfied.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/resolution/ordinary_bound_unsatisfied.rs) and [`acceptable/generic/generic_context_ordinary_bound.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/generic/generic_context_ordinary_bound.rs) pin a check surfacing an *ordinary* trait bound (`Scalar: Eq`, unmet by a wired `f64`) as the primary `E0277`, from a concrete context and from a generic ` Wrapper` table checked at `Wrapper` respectively. Their error anatomy is the [unsatisfied ordinary trait bound](../../errors/checks/ordinary-trait-bound.md) class. ## Source diff --git a/docs/implementation/entrypoints/delegate_components.md b/docs/implementation/entrypoints/delegate_components.md index d4a4d0d0..919bcc6f 100644 --- a/docs/implementation/entrypoints/delegate_components.md +++ b/docs/implementation/entrypoints/delegate_components.md @@ -125,16 +125,16 @@ The failure cases in `cgp-macro-tests` pin the attribute rejection: - [parser_rejections/delegate_components.rs](../../../crates/tests/cgp-macro-tests/tests/parser_rejections/delegate_components.rs) asserts the macro rejects an attribute on the table, on a key, and on a key nested inside a `UseDelegate` value (the last confirms the validator recurses through mapping values rather than dropping the attribute), and that a braceless `open` header listing more than one component is rejected (the braceless form opens exactly one). -The compile-fail fixtures in `cgp-compile-fail-tests` pin the expansions that fail to compile. All are **acceptable** failures — deferred to the compiler by design — and their anatomy is documented in the [error catalog](../../errors/README.md) (the [Failure modes](#failure-modes) section links each to its class): - -- [acceptable/delegate_components/duplicate_key.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_key.rs) — two blocks mapping the same key expand to conflicting `DelegateComponent` impls (`E0119`). -- [acceptable/delegate_components/duplicate_key_same_block.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_key_same_block.rs) — the same conflict from two entries in one block; its `.stderr` pins the per-entry [error spans](#error-spans), each caret landing on its own `GreeterComponent` key rather than the whole block. -- [acceptable/delegate_components/duplicate_path_key.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_path_key.rs) — the `@`-path analogue: two identical `@cgp.core.error.ErrorTypeProviderComponent` entries under a `namespace` header conflict, and its `.stderr` pins the [error span](#error-spans) landing on the duplicated path leaf rather than the whole block, even though the key lowers to a synthesized `PathCons<..>` type. -- [acceptable/delegate_components/duplicate_open_key.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_open_key.rs) — an `open` header colliding with an explicit mapping for the same component; its `.stderr` pins the [error span](#error-spans) of the `open`-header entry, whose span comes from the opened component (a source distinct from the plain key path). -- [acceptable/delegate_components/overlapping_generic.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/overlapping_generic.rs) — a generic ` Wrapper` entry overlaps a specific `Wrapper` entry at the same key (`E0119`). -- [acceptable/delegate_components/missing_dependency.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/missing_dependency.rs) — a lazily-wired provider whose `Self: HasName` dependency the context does not satisfy; the unmet bound surfaces at the call site (`E0599`). Its anatomy is the [hidden unsatisfied-dependency](../../errors/hidden/unsatisfied-dependency.md) error class. -- [acceptable/delegate_components/ordinary_bound_unsatisfied.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/ordinary_bound_unsatisfied.rs) — the same hidden `E0599` where the suppressed dependency is an *ordinary* trait bound (`Scalar: Eq`, unmet by a wired `f64`) rather than a `HasField`, called rather than checked. Same [hidden unsatisfied-dependency](../../errors/hidden/unsatisfied-dependency.md) class; its surfaced counterpart is the [unsatisfied ordinary trait bound](../../errors/checks/ordinary-trait-bound.md) class. -- [acceptable/delegate_components/unconstrained_generic.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/unconstrained_generic.rs) — a per-entry generic that appears only in the value (` GreeterComponent: GreetWith`) lowers to an impl with an unconstrained `T` (`E0207`), which the compiler rejects as it would a hand-written impl. +The post-codegen compile-fail cases are now `cargo-cgp` UI fixtures that pin the expansions that fail to compile. All are **acceptable** failures — deferred to the compiler by design, none a defect — and their anatomy is documented in the [error catalog](../../errors/README.md); the [Failure modes](#failure-modes) section links each to its class. `cargo-cgp` files each fixture by the *quality of the output* it renders (`acceptable/` when the tool already leads with the cause, `usability/` when the cause is present but buried), which is why the unconstrained-generic case below sits under `usability/` even though it is not a defect: + +- [`acceptable/wiring/duplicate-keys/duplicate_key.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/duplicate-keys/duplicate_key.rs) — two blocks mapping the same key expand to conflicting `DelegateComponent` impls (`E0119`). +- [`acceptable/wiring/duplicate-keys/duplicate_key_same_block.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/duplicate-keys/duplicate_key_same_block.rs) — the same conflict from two entries in one block; its `.rust.stderr` pins the per-entry [error spans](#error-spans), each caret landing on its own `GreeterComponent` key rather than the whole block. +- [`acceptable/wiring/namespace-paths/delegate_duplicate_path_key.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/namespace-paths/delegate_duplicate_path_key.rs) — the `@`-path analogue: two identical `@cgp.core.error.ErrorTypeProviderComponent` entries under a `namespace` header conflict, and its `.rust.stderr` pins the [error span](#error-spans) landing on the duplicated path leaf rather than the whole block, even though the key lowers to a synthesized `PathCons<..>` type. +- [`acceptable/wiring/duplicate-keys/duplicate_open_key.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/duplicate-keys/duplicate_open_key.rs) — an `open` header colliding with an explicit mapping for the same component; its `.rust.stderr` pins the [error span](#error-spans) of the `open`-header entry, whose span comes from the opened component (a source distinct from the plain key path). +- [`acceptable/wiring/duplicate-keys/overlapping_generic.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/wiring/duplicate-keys/overlapping_generic.rs) — a generic ` Wrapper` entry overlaps a specific `Wrapper` entry at the same key (`E0119`). +- [`acceptable/use-site/missing_dependency.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/use-site/missing_dependency.rs) — a lazily-wired provider whose `Self: HasName` dependency the context does not satisfy; the unmet bound surfaces at the call site (`E0599`). Its anatomy is the [hidden unsatisfied-dependency](../../errors/hidden/unsatisfied-dependency.md) error class. +- [`acceptable/use-site/ordinary_bound_unsatisfied.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/acceptable/use-site/ordinary_bound_unsatisfied.rs) — the same hidden `E0599` where the suppressed dependency is an *ordinary* trait bound (`Scalar: Eq`, unmet by a wired `f64`) rather than a `HasField`, called rather than checked. Same [hidden unsatisfied-dependency](../../errors/hidden/unsatisfied-dependency.md) class; its surfaced counterpart is the [unsatisfied ordinary trait bound](../../errors/checks/ordinary-trait-bound.md) class. +- [`usability/wiring/constraints/unconstrained_generic.rs`](https://github.com/contextgeneric/cargo-cgp/blob/main/tests/ui/usability/wiring/constraints/unconstrained_generic.rs) — a per-entry generic that appears only in the value (` GreeterComponent: GreetWith`) lowers to an impl with an unconstrained `T` (`E0207`), which the compiler rejects as it would a hand-written impl. ## Source diff --git a/docs/reference/README.md b/docs/reference/README.md index c5317a42..6bf0b2c5 100644 --- a/docs/reference/README.md +++ b/docs/reference/README.md @@ -62,6 +62,12 @@ The distinction between [components/](components/) and [traits/](traits/) is whe This index is the catalog of constructs. When you add, remove, or rename a construct, update both its document and this index in the same change. Because documents live in different subdirectories, a cross-link between two of them is a relative path — a sibling in the same directory is `name.md`, and a document in another directory is `../that-dir/name.md`. +## Tooling — [cargo-cgp.md](cargo-cgp.md) + +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. + ## Component definition macros — [macros/](macros/) These macros define CGP components and the providers that implement them — the core act of writing CGP code. diff --git a/docs/reference/cargo-cgp.md b/docs/reference/cargo-cgp.md new file mode 100644 index 00000000..98f66fb0 --- /dev/null +++ b/docs/reference/cargo-cgp.md @@ -0,0 +1,92 @@ +# 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. + +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. + +## Why it exists + +A CGP macro expands to ordinary Rust, so the compiler type-checks the *generated* code, and a small wiring mistake surfaces as a wall of errors naming types the programmer never wrote — with the real cause buried under machinery like `IsProviderFor` and `CanUseComponent`, encoded as a nested `Symbol<…>` spine, or hidden from the output entirely (the [hidden-versus-surfaced axis](../errors/README.md#the-central-axis-hidden-versus-surfaced) the catalog is built around). `cargo-cgp` reads those diagnostics inside the compiler and re-presents them: it names the real cause, renders the dependency chain that leads to it as a `cargo tree`-style tree, and tags each rewritten message with a `[CGP-Exxx]` code — much as Clippy layers its own analysis on top of `rustc`. + +Two mechanisms do the work, and both matter when reading its output. It compiles through a `rustc` wrapper that turns on the **next-generation trait solver** (`-Znext-solver`), which surfaces dependency errors the default solver hides — this alone recovers the root cause of the [hidden unsatisfied-dependency](../errors/hidden/unsatisfied-dependency.md) class that plain `cargo check` suppresses. On top of that it **rewrites the classes it recognizes** into the coded, root-cause-first form. Classes it does not yet rewrite pass through as the compiler wrote them. + +## Installing + +`cargo-cgp` is two binaries — the `cargo-cgp` front-end and a `cargo-cgp-driver` that links the compiler internals — and it requires an exact Rust nightly (the one the driver is built against). That nightly is installed by `cargo cgp setup` (or built by the Nix flake), and cargo-cgp forces it only for its own check, so **your `cgp` project keeps its own toolchain** — the pinned stable in [`rust-toolchain.toml`](../../rust-toolchain.toml) — for its ordinary builds. + +With **cargo** (rustup present): + +```sh +cargo install cargo-cgp # the front-end; builds on any toolchain +cargo cgp setup # provisions the pinned nightly + driver, in lockstep +``` + +With **Nix** (pinning the pre-release tag for a reproducible install): + +```sh +nix profile install github:contextgeneric/cargo-cgp/v0.1.0-alpha +``` + +The full matrix — installing from source, updating, and uninstalling — is in cargo-cgp's [installation guide](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/reference/installation.md). + +## Running it without installing + +To check a project through the tool without installing anything — the quickest way to try it on this repository's examples or a scratch reproduction — run the flake's default app from the project directory, pinning the tag: + +```sh +cd /path/to/a/cgp/project +nix run github:contextgeneric/cargo-cgp/v0.1.0-alpha -- check +``` + +Everything after `--` is forwarded to `cargo check`. This needs no rustup and leaves the project's toolchain and `target/` untouched. + +## Using it + +Run it wherever you would run `cargo check`: + +```sh +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. + +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: + +```text +error[E0277]: [CGP-E001] the consumer trait `CanCalculateArea` is not implemented for context `Rectangle` + = note: root cause: [CGP-E106] missing field `height` on `Rectangle` + this is required through the dependency chain: + [CGP-E101] consumer trait impl `CanCalculateArea` for context `Rectangle` + └─ [CGP-E102] provider trait impl `AreaCalculator` with context `Rectangle` for provider `RectangleArea` + └─ [CGP-E106] missing field `height` on `Rectangle` +``` + +The diagnostic keeps its own Rust code (`E0277`), so `rustc --explain` still works; the `[CGP-Exxx]` tag rides inside the message. The codes are catalogued in cargo-cgp's [error-code catalog](https://github.com/contextgeneric/cargo-cgp/blob/main/docs/error-code.md), and each class in this repository's [error catalog](../errors/README.md) records which code it maps to under its *How cargo-cgp presents it* section. + +To use it as **Rust Analyzer's** on-save check backend so the reshaped errors appear inline, wire it through `check.overrideCommand` (it is a two-word command and must emit JSON): + +```jsonc +"rust-analyzer.check.overrideCommand": [ + "cargo", "cgp", "check", "--workspace", "--all-targets", "--message-format=json" +] +``` + +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. + +## 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. + +## Version compatibility + +This documentation is written for **`cgp` v0.8.0** and **`cargo-cgp` v0.1.0-alpha**. The two version independently: `cargo-cgp` reads `cgp`'s stable, macro-generated surface (the consumer/provider traits, `DelegateComponent`, `HasField`, and the rest), so a newer `cargo-cgp` works against this `cgp`, and a newer `cgp` generally works under this `cargo-cgp`. If the tool reports a version far behind this one, recommend updating it (`cargo cgp update`, or refresh the Nix flake); the `/cgp` skill records the versions it was written against and how to reconcile a mismatch. + +## Further reading + +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. +- [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 fc9732dd..812b870b 100644 --- a/docs/skills/cgp/SKILL.md +++ b/docs/skills/cgp/SKILL.md @@ -45,6 +45,76 @@ 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 + +**Before debugging any CGP compile error, use `cargo-cgp` if it is available.** `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 +hidden entirely; `cargo-cgp` names the cause and renders the dependency chain that leads to it. +Recommend it to the user for building and checking CGP code, and prefer it over plain `cargo check` +whenever a wiring failure needs diagnosing. + +**Check whether it is installed** with `cargo cgp --version` (or just run `cargo cgp check` in a CGP +project). If the subcommand is missing, it is not installed. + +**If it is not installed, recommend installing it** — tell the user it makes CGP errors far more +readable — and **install it on their behalf only with their approval**, since it provisions a nightly +toolchain and builds a compiler-linked driver (heavy). + +**Prefer the cargo path**, which is the default for almost every machine: `cargo install cargo-cgp` +then `cargo cgp setup` (the first installs the small front-end on whatever toolchain is present; +`setup` provisions the pinned nightly and builds the matching driver in lockstep). It needs rustup. + +**Reach for Nix only if the host actually has it** — a `nix` command on `PATH` or a `flake.nix` in the +project — or if the user explicitly asks for it; otherwise do not bring Nix up at all. When Nix *is* +present but cargo-cgp is not installed, the lightest move is to **not install** and instead run the +tool through the flake from the project directory: +`nix run github:contextgeneric/cargo-cgp/v0.1.0-alpha -- check` (arguments after `--` go to +`cargo check`). To install into a Nix profile instead, use +`nix profile install github:contextgeneric/cargo-cgp/v0.1.0-alpha`. + +cargo-cgp requires a specific pinned Rust nightly, installed by `cargo cgp setup` (or built by the +Nix flake), and forces it only for its own check, so the user's project keeps its own toolchain +untouched. Full instructions: +. + +**Using it:** run it wherever you would run `cargo check` (arguments after `check` are forwarded): +`cargo cgp check`, `cargo cgp check --workspace`. It keeps rustc's own error code and adds a +`[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. + +`cargo-cgp` can also be Rust Analyzer's on-save check backend, via +`rust-analyzer.check.overrideCommand` set to +`["cargo","cgp","check","--workspace","--all-targets","--message-format=json"]`. But **never modify +the user's Rust Analyzer settings implicitly** — mention this option and let them opt in, and only +edit their editor configuration when they explicitly ask you to. + +**When cargo-cgp is not available, or leaves an error largely unrewritten**, fall back to reading the +raw compiler output by hand — and only then load the [error-extraction sub-skill](references/error-extraction.md), +the technique for reducing a raw CGP cascade to its root cause. When cargo-cgp *has* reshaped the +error, its `[CGP-Exxx]` headline and root-cause tree are already the compact summary that sub-skill +would produce, so you do not need it. + +### Versions and keeping this skill current + +This skill is written for **CGP v0.8.0** and **cargo-cgp v0.1.0-alpha**. Check both on the host and +act on a mismatch: + +- Read the user's `cgp` version (its `Cargo.toml`/`Cargo.lock` entry) and run `cargo cgp --version` + for the tool. +- **If either is older than recorded here**, recommend updating it — `cargo update -p cgp` for the + library, `cargo cgp update` (or a Nix flake refresh) for the tool — so its behavior matches this + skill. +- **If the host's `cgp` is *newer* than v0.8.0**, this skill is behind the library: tell the user the + **skill should be upgraded** to match their `cgp`, rather than changing their code to fit an older + skill. Treat the newer `cgp` as authoritative and flag the gap instead of guessing. + ## The problem CGP solves Rust's coherence rules permit at most one implementation of a trait for a given type, and forbid @@ -174,7 +244,9 @@ that uses CGP: use cgp::prelude::*; ``` -This skill describes CGP **v0.8.0**. A few names are intentionally *not* in the prelude and must be +This skill describes CGP **v0.8.0** (and cargo-cgp **v0.1.0-alpha** — see +[Tooling](#tooling-use-cargo-cgp-for-readable-errors) for checking both versions on the host and +reconciling a mismatch). A few names are intentionally *not* in the prelude and must be imported from their module — most notably the error-handling wiring keys and backends (see Error handling below). Inside documentation code blocks you may omit the prelude import for brevity. @@ -872,7 +944,7 @@ The remaining sub-skills each own one construct family: - **[references/components.md](references/components.md)** — `#[cgp_component]` and the full expansion (consumer/provider traits, the two blanket impls, the `…Component` marker), why `IsProviderFor` exists, and the three provider-writing macros (`#[cgp_impl]`, `#[cgp_provider]`, `#[cgp_new_provider]`). *Without it* you will misjudge what `self`/`Self` mean inside a provider and how the blanket impls route a call. Load it before writing any component or provider. - **[references/wiring.md](references/wiring.md)** — `DelegateComponent`, every `delegate_components!` form (arrays, `new`, generic tables), `open` per-type dispatch, direct consumer-trait impls, `UseContext` and its circular-dependency trap, the legacy `UseDelegate` tables, and the other providers you see in tables (`WithProvider` and its `WithField`/`WithType`/`WithContext` aliases, `UseDefault`). *Without it* you will not know when a hand-written impl collides with the table, why `UseContext` overflows, or what a `WithField<…>` entry means. Load it before wiring any context. - **[references/checking.md](references/checking.md)** — why wiring is lazy, how check traits and `CanUseComponent` force readable errors, every `check_components!` / `delegate_and_check_components!` option (`#[check_trait]`, `#[check_providers]`, `#[check_params]`, `#[skip_check]`), and a debugging playbook. *Without it* you cannot localize a broken wiring or read the error it throws. Load it whenever a wiring fails to compile. -- **[references/error-extraction.md](references/error-extraction.md)** — how to reduce a long CGP compile error to a compact, root-cause-first summary, the hidden-versus-surfaced distinction that decides whether the root cause is even present in the output, how to confirm a suspected cause by grepping the error output for one signature line instead of reading it all, and how to delegate the reading to a sub-agent so a wall of generated-type errors does not consume your context. *Without it* you will read a cascade inline, chase a cause a hidden error does not contain, or hand back raw output instead of the few facts that matter. Load it whenever a CGP error is long enough that a sub-agent should read it — in an ordinary debugging session, not only when authoring the error catalog. +- **[references/error-extraction.md](references/error-extraction.md)** — the **fallback for when `cargo-cgp` is not available, or leaves an error largely unrewritten** (see [Tooling](#tooling-use-cargo-cgp-for-readable-errors) first — `cargo-cgp`'s `[CGP-Exxx]` headline and root-cause tree already are the compact summary this sub-skill produces, so reach for the sub-skill only when the tool is absent or passes the error through). It covers how to reduce a long raw CGP compile error to a compact, root-cause-first summary, the hidden-versus-surfaced distinction that decides whether the root cause is even present in the output, how to confirm a suspected cause by grepping for one signature line, and how to delegate the reading to a sub-agent so a wall of generated-type errors does not consume your context. *Without it* you will read a cascade inline, chase a cause a hidden error does not contain, or hand back raw output instead of the few facts that matter. - **[references/functions-and-getters.md](references/functions-and-getters.md)** — `HasField`/`#[derive(HasField)]`, `#[cgp_fn]`, `#[implicit]` and its access rules, `#[uses]`/`#[extend]`/`#[extend_where]`/`#[impl_generics]`, the getters `#[cgp_auto_getter]`/`#[cgp_getter]`/`UseField`/`WithField`, and `ChainGetters` for nested-context fields. *Without it* you will reach for a getter trait where an implicit argument is idiomatic, or misapply the `.clone()`/`.as_str()`/`&mut` field-access rules. Load it for the ergonomic day-to-day surface. - **[references/abstract-types.md](references/abstract-types.md)** — `#[cgp_type]`, the built-in `HasType`/`TypeProvider`, wiring with `UseType` (and `UseDelegatedType` for table-chosen types), importing types with the `#[use_type]` attribute (distinct from the `UseType` provider), and the `WithType`/`WithDelegatedType` adapters. *Without it* you will confuse the provider and the attribute and write `Self::` paths by hand. Load it for any associated-type abstraction. - **[references/higher-order-providers.md](references/higher-order-providers.md)** — providers parameterized by other providers, the stray `` on the inner bound, `#[use_provider]`, `UseContext` defaults, generic-parameter components, and cross-context dependencies. *Without it* you will call the inner provider as a method instead of `Provider::method(self)` and misplace the context slot. Load it before composing providers. diff --git a/docs/skills/cgp/references/error-extraction.md b/docs/skills/cgp/references/error-extraction.md index 840b589b..83d6b1f2 100644 --- a/docs/skills/cgp/references/error-extraction.md +++ b/docs/skills/cgp/references/error-extraction.md @@ -2,6 +2,8 @@ How to turn a CGP compile error — often a wall of repeated failures naming generated types — into a compact, root-cause-first summary. This skill serves two roles, and which one applies depends on your task: an agent **extracting** an error reads raw compiler output and reduces it to a few facts, while an agent **delegating** hands a long error to a sub-agent so it never touches the main context. Both roles rely on the same shape knowledge, so read [The two shapes](#the-two-shapes-to-recognize) first, then jump to [Role 1 — extracting](#role-1--extracting-an-error-yourself) or [Role 2 — delegating](#role-2--delegating-the-extraction-to-a-sub-agent). +**Prefer `cargo-cgp` first — this sub-skill is the fallback.** When [`cargo-cgp`](https://github.com/contextgeneric/cargo-cgp) is available, it has already done this extraction: it rewrites a recognized CGP error into a `[CGP-Exxx]` headline over a root-cause dependency tree, which *is* the compact summary below. Reach for the manual technique here only when the tool is not installed, or when it leaves an error largely as `rustc` wrote it (a class it does not yet rewrite). The main [SKILL.md tooling section](../SKILL.md) covers installing and running the tool; everything below is for reading raw compiler output by hand. + ## Why extraction is a skill of its own CGP error output is disproportionately large and disproportionately misleading, so reading it well is a distinct task from fixing the code. Wiring is resolved [lazily](checking.md), so one broken link surfaces at every place that transitively needs it, and each failure quotes *generated* code — `IsProviderFor`, `DelegateComponent`, `CanUseComponent`, and the type-level `Symbol`/`Chars`/`PathCons` spines you never wrote. A single missing field can print screens of near-identical errors. Worse, some of that output is actively deceptive: a whole class of CGP errors reports that a trait's bounds are unsatisfied while *hiding* the dependency that actually failed. Reading such output inline, in the agent trying to fix the code, wastes context on noise and risks chasing a cause the output does not contain. diff --git a/rust-toolchain.toml b/rust-toolchain.toml index f3db9c05..10001973 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,12 +1,3 @@ [toolchain] channel = "1.96.1" profile = "default" - -# Pin rust-src so compiler diagnostics are deterministic across environments. When -# rust-src is present, rustc resolves the remapped `/rustc//...` std paths to -# on-disk source and renders standard-library snippets in errors (e.g. the `Option` -# definition); without it, the snippet is omitted. The cgp-compile-fail-tests -# trybuild `.stderr` snapshots (option_slice.stderr) are blessed with those -# snippets, so CI — which installs the toolchain from this file — must have rust-src -# too, or it mismatches. The `default` profile does not include rust-src on its own. -components = ["rust-src"]