diff --git a/crates/tests/AGENTS.md b/crates/tests/AGENTS.md index 7ee88f64..4155824e 100644 --- a/crates/tests/AGENTS.md +++ b/crates/tests/AGENTS.md @@ -173,7 +173,9 @@ implementation document, so the fixture tree mirrors [docs/implementation/entryp 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. The driver [tests/compile_fail_tests.rs](cgp-compile-fail-tests/tests/compile_fail_tests.rs) +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 @@ -192,6 +194,13 @@ capture the expanded code as an `insta` inline string snapshot in the 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* 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 index c513745a..86382e50 100644 --- 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 @@ -15,7 +15,7 @@ //! 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/implementation/entrypoints/cgp_component.md (Failure modes). +//! See docs/errors/wiring/conflicting-wiring.md. use cgp::prelude::*; 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 index b971a70f..24f7ad61 100644 --- 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 @@ -12,7 +12,7 @@ //! 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/implementation/entrypoints/cgp_impl.md (Failure modes). +//! See docs/errors/wiring/conflicting-wiring.md. use cgp::core::component::DefaultImpls1; use cgp::prelude::*; 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 index 477d37e8..2b114f37 100644 --- 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 @@ -12,7 +12,7 @@ //! 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/implementation/entrypoints/cgp_impl.md (Failure modes). +//! See docs/errors/wiring/conflicting-wiring.md. use cgp::prelude::*; 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 index 00611806..8aa516f4 100644 --- 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 @@ -7,7 +7,7 @@ //! `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 (E0117). A per-component default keyed on +//! 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 @@ -17,7 +17,7 @@ //! crate and why the guide recommends namespace *body* entries for wiring that //! must live downstream of the namespace. //! -//! See docs/implementation/entrypoints/cgp_namespace.md (Failure modes). +//! See docs/errors/wiring/orphan-rule.md. use cgp::prelude::*; use cgp_test_crate_a::{Announcer, AnnouncerComponent, AppNamespace, HasName}; 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 index 99f85125..20a040e2 100644 --- 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 @@ -12,7 +12,7 @@ //! `build_namespace_impl` (`mapping/eval.rs`) regresses, the caret snaps back to //! the block and this `.stderr` changes. //! -//! See docs/implementation/entrypoints/cgp_namespace.md (Error spans). +//! See docs/errors/wiring/conflicting-wiring.md; error-span mechanics in docs/implementation/entrypoints/cgp_namespace.md. use cgp::prelude::*; 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 new file mode 100644 index 00000000..42e903d2 --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/for_loop_bare_key.rs @@ -0,0 +1,54 @@ +//! 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 conflicting-wiring class; contrast +//! the specific-key override in override_registered_path.rs. +//! +//! See docs/errors/wiring/conflicting-wiring.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 new file mode 100644 index 00000000..c9f2e845 --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/for_loop_bare_key.stderr @@ -0,0 +1,17 @@ +error[E0119]: conflicting implementations of trait `IsProviderFor<_, _, _>` for type `App` + --> tests/acceptable/cgp_namespace/for_loop_bare_key.rs:49:13 + | +46 | namespace DefaultNamespace; + | ---------------- first implementation here +... +49 | 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:49:13 + | +46 | namespace DefaultNamespace; + | ---------------- first implementation here +... +49 | 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 new file mode 100644 index 00000000..bf9bdd26 --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/inheritance_cycle.rs @@ -0,0 +1,29 @@ +//! 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 new file mode 100644 index 00000000..a29c16d8 --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/inheritance_cycle.stderr @@ -0,0 +1,53 @@ +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/override_registered_path.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/override_registered_path.rs index e18701c8..78b7368a 100644 --- 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 @@ -15,7 +15,7 @@ //! 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. //! -//! See docs/implementation/entrypoints/cgp_namespace.md (Failure modes). +//! See docs/errors/wiring/conflicting-wiring.md. use cgp::prelude::*; 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 new file mode 100644 index 00000000..2d3562ce --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/unregistered_prefix_path.rs @@ -0,0 +1,52 @@ +//! 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 new file mode 100644 index 00000000..ab028b3f --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/unregistered_prefix_path.stderr @@ -0,0 +1,43 @@ +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 new file mode 100644 index 00000000..0ddb7b26 --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/dependency_cascade.rs @@ -0,0 +1,90 @@ +//! 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 new file mode 100644 index 00000000..0f2e2cd8 --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/dependency_cascade.stderr @@ -0,0 +1,333 @@ +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/missing_dependency.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_dependency.rs index 8f3a2da2..0e9f772f 100644 --- 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 @@ -14,7 +14,7 @@ //! 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/implementation/entrypoints/check_components.md (Failure modes). +//! See docs/errors/checks/check-trait-failure.md; span mechanics: check_components.md. use cgp::prelude::*; 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 index b7ae42fd..cf5f1aab 100644 --- 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 @@ -5,7 +5,7 @@ //! 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/implementation/entrypoints/delegate_components.md (Failure modes). +//! See docs/errors/wiring/conflicting-wiring.md. use cgp::prelude::*; 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 index f8bc7016..58282ee5 100644 --- 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 @@ -11,7 +11,7 @@ //! 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/implementation/entrypoints/delegate_components.md (Error spans). +//! See docs/errors/wiring/conflicting-wiring.md; error-span mechanics in docs/implementation/entrypoints/delegate_components.md. use cgp::prelude::*; 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 index cccecb8b..cf3f7373 100644 --- 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 @@ -12,7 +12,7 @@ //! `GreeterComponent` inside `open …;`, and the conflict caret on the explicit //! mapping's key. //! -//! See docs/implementation/entrypoints/delegate_components.md (Error spans). +//! See docs/errors/wiring/conflicting-wiring.md; error-span mechanics in docs/implementation/entrypoints/delegate_components.md. use cgp::prelude::*; 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 index d9cee975..5d8b4799 100644 --- 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 @@ -11,7 +11,7 @@ //! threading in `key/path.rs` regresses, the caret snaps back to the block and //! this `.stderr` changes. //! -//! See docs/implementation/entrypoints/delegate_components.md (Error spans). +//! 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::*; 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 index 36353cb9..7d39c88d 100644 --- 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 @@ -8,8 +8,8 @@ //! site; deferring it to the use site is the intended lazy behavior, not a //! macro defect. //! -//! See docs/implementation/entrypoints/delegate_components.md (Failure modes) and -//! docs/implementation/entrypoints/check_components.md. +//! See docs/errors/hidden/unsatisfied-dependency.md; its surfaced counterpart is +//! docs/errors/checks/check-trait-failure.md. use cgp::prelude::*; 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 index 8268ff36..ebe3b845 100644 --- 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 @@ -6,7 +6,7 @@ //! defers the overlap check to the compiler, the same as two overlapping //! hand-written generic impls. //! -//! See docs/implementation/entrypoints/delegate_components.md (Failure modes). +//! See docs/errors/wiring/conflicting-wiring.md. use cgp::prelude::*; 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 index 81c5514e..3d82c93b 100644 --- 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 @@ -13,7 +13,7 @@ //! hand-written `impl` with an unused parameter. Deferring this to the //! compiler is the intended behavior, not a macro defect. //! -//! See docs/implementation/entrypoints/delegate_components.md (Failure modes). +//! See docs/errors/wiring/unconstrained-generic.md. use core::marker::PhantomData; 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 new file mode 100644 index 00000000..a5810611 --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/use_context_cycle.rs @@ -0,0 +1,44 @@ +//! 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 new file mode 100644 index 00000000..171533c1 --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/use_context_cycle.stderr @@ -0,0 +1,36 @@ +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/docs/AGENTS.md b/docs/AGENTS.md index 58aead70..92ce3067 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -42,6 +42,10 @@ The [guides/](guides/) directory holds prescriptive guides to *writing* CGP — A guide leans on the other sections rather than restating them. Link to the reference for the exact syntax of every construct a guide recommends, to the concepts for the mechanism behind a recommendation, and to the examples for a fuller worked scenario; keep the guide itself focused on the decision and the migration path. When a guide starts explaining what a construct *is* at length, move that explanation into the reference or a concept and link to it. Guides are bound by the synchronization rule like everything else: a recommendation that names a syntax, expansion, or default the code no longer has is a bug in the change that made it stale, and every code snippet a guide shows must compile against current CGP — verify it against the source the same way you would a reference document's Expansion section. Write guides in the dual-reader prose style, and prefer the vocabulary and running scenarios the rest of the knowledge base already uses. +## 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 skills directory The [skills/](skills/README.md) directory holds the agent skills built from this knowledge base — currently the `cgp` skill, the authoritative orientation an agent loads before reading or writing CGP code. The skill is a distilled, self-contained synthesis of the reference, concepts, and examples: a top-level `SKILL.md` carrying the mental model and a router, plus a `references/` set of sub-skills, one per topic area, each giving enough detail and worked examples to make an agent proficient in that area without further lookup. It is deliberately lighter than the reference documents — it teaches how to read and write CGP, not every corner case — and it is the skill that `/cgp` resolves to, replacing any earlier version. diff --git a/docs/README.md b/docs/README.md index db19e9e6..ab9c35bc 100644 --- a/docs/README.md +++ b/docs/README.md @@ -10,7 +10,7 @@ The knowledge base also serves as a contract. When an agent changes how a macro ## How it is organized -The knowledge base is divided into four top-level sections, and will grow to contain more as the need arises. +The knowledge base is divided into five top-level sections, and will grow to contain more as the need arises. The [reference/](reference/README.md) directory holds one document per CGP construct — one for `cgp_component`, one for `cgp_impl`, one for `delegate_components`, and so on. Each document is self-contained and explains a single construct completely: its purpose, its accepted syntax, the exact code it desugars to, worked examples, and links to the constructs it relates to. The [reference index](reference/README.md) lists every construct and tracks which ones are documented. @@ -20,6 +20,8 @@ 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. + ## How to use it An agent working on CGP should read the relevant reference document before changing a construct, and should consult the `/cgp` skill for the conceptual framing that ties constructs together. The reference documents assume familiarity with the vocabulary the `/cgp` skill establishes — consumer traits, provider traits, providers, wiring, and so on — and focus on precise per-construct semantics rather than re-teaching the paradigm. Read the two together: the skill for the shape of the forest, the reference for the individual trees. diff --git a/docs/errors/AGENTS.md b/docs/errors/AGENTS.md new file mode 100644 index 00000000..8d27843d --- /dev/null +++ b/docs/errors/AGENTS.md @@ -0,0 +1,53 @@ +# AGENTS.md — the CGP error catalog + +This directory catalogs the compiler errors CGP produces *after* codegen, organized by the kind of error. Read [README.md](README.md) for what the catalog is and the hidden-versus-surfaced axis it is built around. The rules below govern how to keep these documents correct and useful, and they assume you have read the knowledge-base-wide rules in [../AGENTS.md](../AGENTS.md); this file adds the rules specific to the error catalog. + +Invoke the `/cgp` skill before writing or revising any document here, and write in the dual-reader prose style (the `/dual-reader-prose` skill). Each document is read both by an agent scanning for one error class and by a tool author reading a class start to finish, so every section opens with a self-contained topic sentence and frames any list with a sentence before and after. + +## What these documents are for + +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 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. + +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. + +## Never record verbatim error output + +The defining rule of this catalog is that **a document summarizes the kind of error a class produces; it never pastes the verbatim output.** CGP errors are long — a single mistake in a deep dependency graph can print one failure per transitively dependent provider — and a pasted cascade would bloat the document and rot the instant a diagnostic shifts. Reproduce only the short *fragment* that carries a point (the one line naming the failing trait, or the shape of the near-contradiction between an error and its `help:` note), the way an implementation document quotes a slice of an expansion rather than the whole file. + +In place of the output, every document must record three facts, because these are what a tool and a debugging agent actually need: + +- **The kind of diagnostic** — the error code or codes, the trait or traits named (`IsProviderFor`, `DelegateComponent`, `CanUseComponent`, a consumer trait), and the recognizable shape (a lone `E0599` "method exists but bounds unsatisfied", an `E0277` note chain, an error immediately contradicted by a `help:` note, a repeated per-provider cascade). +- **Whether the root cause is present** — surfaced somewhere in the output, or hidden by the compiler's heuristics and absent entirely. This is the field that decides which subdirectory the class lives in and whether a reader should even look for a cause. +- **Where the root cause sits when present** — which note carries it, and its position in the output. Record when it is near the last or second-to-last message of a cascade, when it hides behind an elided `...` whose full form is written to the `long-type-….txt` file the compiler names, and any reliable landmark that lets a reader or tool jump to it. + +## Document structure + +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). +- **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. +- **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`. + +## Backing every class with a fixture, and organizing the fixtures + +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. + +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. + +## 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). + +## Gathering an error with a sub-agent + +Writing or verifying a document here means reading real compiler output, and that output is often long enough to waste the context of the agent doing the writing. Delegate the reading. The [error-extraction sub-skill](../skills/cgp/references/error-extraction.md) defines how a sub-agent compiles a fixture or a scratch reproduction, captures the diagnostic, and returns only the compact anatomy this catalog records — the class, whether the root cause is present, and its position — rather than the raw dump. Spawn a sub-agent with that skill to gather the facts for a class, then write the document from its summary. The same delegation applies in an ordinary debugging session: when a CGP error is too long to read inline, hand it to a sub-agent and act on the summary. Keeping the extraction technique in the skill and the extracted facts in the catalog means the two describe the same anatomy from two directions. diff --git a/docs/errors/README.md b/docs/errors/README.md new file mode 100644 index 00000000..4a15e13a --- /dev/null +++ b/docs/errors/README.md @@ -0,0 +1,71 @@ +# CGP Error Catalog + +This directory is a catalog of the compiler errors a programmer encounters when using CGP, organized by the *kind* of error rather than by the macro that produced it. Each document describes one class of failure: the mistake that triggers it, the shape of the diagnostic the Rust compiler prints, whether that diagnostic contains the real root cause, and — when it does — where in the output to find it. The catalog is the fifth section of the knowledge base, alongside [reference/](../reference/README.md), [concepts/](../concepts/README.md), [examples/](../examples/README.md), and [guides/](../guides/README.md). + +## Why this exists + +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 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. The [debugging guide](../guides/debugging.md) is the playbook; this catalog is the reference the playbook indexes into. + +The third reader is a **sub-agent extracting an error message on the main agent's behalf**. CGP error output is frequently long enough that reading it wastes a main agent's context window, so the [error-extraction sub-skill](https://github.com/contextgeneric/cgp/blob/main/docs/skills/cgp/references/error-extraction.md) delegates the reading to a sub-agent that returns a compact summary. That summary is only useful if it reports the same facts every document here records — the class, whether the root cause is present, and its position — so the catalog and the sub-skill describe the same anatomy from two directions. + +## 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. + +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. + +## The central axis: hidden versus surfaced + +The single most important distinction in this catalog is whether the compiler **surfaces** or **hides** the root cause of an unsatisfied dependency, because the two produce completely different diagnostics from the very same mistake. CGP wiring is resolved lazily, so a provider whose impl-side dependency the context cannot meet is wired without complaint and only fails when the wiring is exercised. *How* it is exercised decides what the user sees. + +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. + +## The verbosity problem: many errors, one cause + +A second recurring difficulty is volume: a single mistake deep in a dependency graph surfaces at *every* provider that transitively needs it, so the compiler prints one error per affected provider even though there is only one thing to fix. If a `FooProvider` needs a `foo` field, a `BarProvider` depends on `Foo`, and a `BazProvider` depends on `Bar`, then a missing `foo` field produces three separate failures naming `FooProvider`, `BarProvider`, and `BazProvider`. The count of errors reflects the depth of the graph, not the number of mistakes. + +Because of this, **the documents here never record verbatim error output.** Reproducing a multi-screen cascade would bloat the catalog and rot the moment a diagnostic shifts. Instead each document summarizes the *kind* of message a class produces, states whether the root cause appears anywhere in it, and pins the *position* where it appears when it does — very often near the last or second-to-last error in a cascade, since the innermost failing bound is reported after the outer ones. Recording the position is what lets a tool, or an agent, jump to the cause instead of reading the noise. The [authoring conventions](AGENTS.md) make this rule precise. + +## Organization + +The catalog is divided into three subdirectories by the axis above, so a reader lands in the right class before decoding any type. Each document is registered in the catalog below in the same change that adds it. + +The [hidden/](hidden/) directory holds the classes where the compiler **suppresses** the root cause — the errors a user meets by exercising broken wiring through a consumer trait rather than a check. These are isolated precisely because their diagnostics report nothing about the true cause, so a reader must know not to look for one. + +The [checks/](checks/) directory holds the classes where a check trait **surfaces** the root cause through `IsProviderFor`, and the classes that are dominated by the *volume* of a surfaced cascade rather than by any single message. + +The [wiring/](wiring/) directory holds the whole-program **structural** failures — coherence conflicts, orphan-rule violations, wiring cycles, and unconstrained generics — where the compiler reports a definite error code (`E0119`, `E0210`, `E0275`, `E0207`) and the difficulty is mapping that code back to the wiring mistake rather than a hidden or cascading cause. + +## Catalog + +This section indexes every error document, grouped by the subdirectory it lives in. + +Hidden-cause errors — [hidden/](hidden/): + +- [Unsatisfied dependency (hidden)](hidden/unsatisfied-dependency.md) — a provider's impl-side dependency is unmet, and the failure is triggered by a direct consumer-trait method call, so the compiler reports only that the method's bounds are unsatisfied (`E0599`/`E0277`) and hides the missing dependency. + +Surfaced and cascading errors — [checks/](checks/): + +- [Check-trait failure (surfaced)](checks/check-trait-failure.md) — the same unmet dependency forced through `check_components!`, where `IsProviderFor` surfaces the concrete missing bound at the wiring site. +- [Verbose dependency cascade](checks/verbose-cascade.md) — one deep mistake reported at every transitively dependent provider, and how to locate the single root cause among the repeats. +- [Unregistered namespace path](checks/unregistered-namespace-path.md) — a component routed through a joined namespace to a path that no entry ever binds, so the *lookup* finds no delegate; a check surfaces it as an `E0277` on the path-keyed `DefaultNamespace`/`DelegateComponent` bound. + +Structural wiring errors — [wiring/](wiring/): + +- [Conflicting wiring](wiring/conflicting-wiring.md) — the same key or name wired twice, producing coherence (`E0119`) or duplicate-definition (`E0428`) errors. +- [Orphan-rule violation](wiring/orphan-rule.md) — a generated impl for a foreign trait and a fully foreign type (`E0210`, or `E0117`), as when a prefixed `#[default_impl]` is registered from the wrong crate. +- [Wiring cycle](wiring/wiring-cycle.md) — a delegation that chases its own tail: an `E0275` overflow when forced through a check, but the hidden `E0599` when reached by a plain method call. +- [Namespace inheritance cycle](wiring/namespace-inheritance-cycle.md) — namespaces whose parent chain loops (`A: B`, `B: A`, or `A: A`), an `E0275` overflow caught *eagerly at the `cgp_namespace!` definitions* rather than lazily at a use site. +- [Unconstrained generic](wiring/unconstrained-generic.md) — a per-entry generic that never reaches the key, leaving an impl parameter unconstrained (`E0207`). + +## 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). diff --git a/docs/errors/checks/check-trait-failure.md b/docs/errors/checks/check-trait-failure.md new file mode 100644 index 00000000..6bd67abb --- /dev/null +++ b/docs/errors/checks/check-trait-failure.md @@ -0,0 +1,78 @@ +# Check-trait failure (surfaced) + +A check forces an unsatisfied impl-side dependency through `IsProviderFor`, so the compiler names the real missing bound (`E0277`) at the wiring site — the surfaced counterpart of the [hidden unsatisfied dependency](../hidden/unsatisfied-dependency.md), produced from the very same mistake. + +## What triggers it + +This class arises from exactly the mistake behind the [hidden class](../hidden/unsatisfied-dependency.md) — a provider wired onto a context that cannot meet the provider's impl-side dependency — but exercised through a [`check_components!`](../../reference/macros/check_components.md) assertion (or the fused `delegate_and_check_components!`) instead of a direct method call. The check is what changes the outcome: it asserts `CanUseComponent` for each listed component, and that assertion requires `IsProviderFor` as a *direct* bound, which forces the compiler to evaluate the provider's `where` clause rather than suppressing it. + +```rust +#[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, // impl-side dependency +{ + fn greet(&self) { + let _ = self.name(); + } +} + +#[derive(HasField)] +pub struct Person { + pub age: u8, // no `name` field +} + +delegate_components! { + Person { + GreeterComponent: GreetHello, + } +} + +// Forces the failure here, at the wiring, instead of at a later call site. +check_components! { + Person { + GreeterComponent, + } +} +``` + +## The 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. + +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. + +What makes the cause visible is that the check produces a *direct* trait obligation, and this is where the class differs mechanically from its hidden twin. A `check_components!` asserts `Person: CanUseComponent` as a bound on the generated `__CheckPerson` trait, so the solver must prove that bound outright — and proving it means discharging the whole `where`-clause chain down to the leaf and reporting the first bound that cannot be met. The `required for …` notes are simply `rustc`'s ordinary `E0277` obligation-tracing output for that proof. It is exactly the path the [hidden class](../hidden/unsatisfied-dependency.md) never takes: a method call lets the solver abandon an inapplicable blanket impl at the top instead of proving a direct bound, so it never descends to the leaf. [`IsProviderFor`](../../reference/traits/is_provider_for.md) is the supertrait that carries the provider's own `where` clause into this chain, which is why the leaf is named here and suppressed there. The `help:` note's "but trait `HasField` is implemented for it" is a second piece of standard machinery — `rustc`'s "a similar impl exists" hint, pointing at the nearest impl of the same trait to show the context has *a* field, just not the expected one. + +## Where the root cause is + +The root cause is **present**, and it is near the top — in the compiler's `help:` note, not at the end of the output. This is the opposite of what a reader might expect from a long note chain: the concrete unmet bound (`HasField`) is stated early, and the `required for …` notes that follow build *outward* from it toward the check trait, rather than drilling down to it. The caret's position is the other half of the value — it sits on the wiring entry the user controls, so the error points at the fix site rather than at a distant call. When many providers depend transitively on one leaf, the output multiplies into a [verbose cascade](verbose-cascade.md), and the position guidance shifts to *which block* carries the actionable cause; for a single checked component it is simply the `help:` note. + +## 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. + +## 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. + +## Related + +- [Unsatisfied dependency (hidden)](../hidden/unsatisfied-dependency.md) — the hidden counterpart; the two are the two halves of one phenomenon, and promoting the hidden one yields this class. +- [Verbose dependency cascade](verbose-cascade.md) — this diagnostic multiplied when many providers depend transitively on one leaf. +- [`check_components!`](../../reference/macros/check_components.md), [`CanUseComponent`](../../reference/traits/can_use_component.md), and [`IsProviderFor`](../../reference/traits/is_provider_for.md) — the macro and traits this class is expressed through. +- [Debugging CGP compile errors](../../guides/debugging.md) and the [check-traits concept](../../concepts/check-traits.md) — why the check moves the error here and how to read it. diff --git a/docs/errors/checks/unregistered-namespace-path.md b/docs/errors/checks/unregistered-namespace-path.md new file mode 100644 index 00000000..51c9e8ff --- /dev/null +++ b/docs/errors/checks/unregistered-namespace-path.md @@ -0,0 +1,75 @@ +# Unregistered namespace path + +A context joins a namespace that *routes* a component to a path but never *binds* a provider at that path, so the lookup finds no delegate — and a check surfaces the failure as an `E0277` naming the path-keyed `DefaultNamespace` (or `DelegateComponent`) bound that has no impl. + +## What triggers it + +This class is a *lookup* failure rather than a *dependency* failure: no provider is found for the component at all, as opposed to a provider being found whose `where` clause is unmet. It arises when a component is registered into a [namespace](../../reference/macros/cgp_namespace.md) under a path — through [`#[prefix]`](../../reference/macros/cgp_namespace.md) — and a context joins that namespace, but nothing ever terminates the path with a concrete provider. The prefix registers the *routing* (the namespace resolves the component to a `RedirectLookup` along the path), yet the path itself stays empty because the author forgot the `#[default_impl]`, the namespace body entry, or the direct wiring that would place a provider there. + +```rust +#[cgp_component(Greeter)] +#[prefix(@app in DefaultNamespace)] // routes GreeterComponent to @app.GreeterComponent +pub trait CanGreet { + fn greet(&self) -> String; +} + +#[cgp_impl(new GreetHello)] +impl Greeter { + fn greet(&self) -> String { "Hello".to_owned() } +} + +pub struct App; + +// App joins the namespace, so GreeterComponent follows the @app.GreeterComponent +// redirect — but nothing (no #[default_impl], no body entry, no direct line) ever +// binds GreetHello, or anything, at that path. +delegate_components! { + App { + namespace DefaultNamespace; + } +} + +check_components! { + App { + GreeterComponent, + } +} +``` + +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 + +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. + +One landmark reads at first like a contradiction and is in fact diagnostic. Beneath the headline the compiler prints `help: the following other types implement trait DefaultNamespace:` and lists the component *markers* that the namespace does resolve — including the bare `GreeterComponent` marker. That the marker implements `DefaultNamespace` while the *path* `PathCons` does not tells you the `#[prefix]` registration succeeded — the component is in the namespace — but the path it routes to was never filled. The problem is a missing binding at the leaf, not a missing prefix. + +## Where the root cause is + +The root cause is **present and sits at the top of the output**, in the primary `E0277` line and its first `required for … DelegateComponent>` note. This is the opposite position from a [check-trait failure](check-trait-failure.md), whose concrete leaf lives in a `help:` note *below* the primary error; here the unsatisfied lookup bound *is* the primary error, and the `required for` notes build outward from it toward the check. Reading the `PathCons<…>` key of that bound tells you which path is unbound — decode it with the [`Symbol!`](../../reference/macros/symbol.md) segments, or read the `long-type-….txt` file the compiler names when the path is elided as `...`. + +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. + +## 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. + +A related variant resolves to a delegate that exists but is not a provider for the component: a path wired to the wrong provider produces an `E0277` where the `RedirectLookup`'s delegate — a named provider — fails to implement the component's provider trait, rather than the `DelegateComponent` lookup failing outright. The remedy there is to wire the path to a provider that actually implements the component. + +## 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. + +## 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. + +## Related + +- [Check-trait failure (surfaced)](check-trait-failure.md) — the sibling surfaced class, where a provider *is* found but its dependency is unmet; contrast the position of the cause (a `help:` note there, the primary error here) and the failing bound (a concrete `HasField` there, a `PathCons` lookup here). +- [Unsatisfied dependency (hidden)](../hidden/unsatisfied-dependency.md) — the `E0599` shape this class takes when exercised by a method call instead of a check; a missing wiring and an unmet dependency are indistinguishable in that hidden form. +- [Verbose dependency cascade](verbose-cascade.md) — when several components route through the same empty path, this diagnostic multiplies the same way. +- [`#[cgp_namespace]`](../../reference/macros/cgp_namespace.md), [`RedirectLookup`](../../reference/providers/redirect_lookup.md), [`DefaultNamespace`](../../reference/traits/default_namespace.md), and the [namespaces guide](../../guides/namespaces-and-prefixes.md) — the routing mechanics and the wiring that fills a path. +- [Debugging CGP compile errors](../../guides/debugging.md) — the `DelegateComponent`/`Namespace` "lookup failed" entry in the decoder. diff --git a/docs/errors/checks/verbose-cascade.md b/docs/errors/checks/verbose-cascade.md new file mode 100644 index 00000000..7186a414 --- /dev/null +++ b/docs/errors/checks/verbose-cascade.md @@ -0,0 +1,62 @@ +# Verbose dependency cascade + +One mistake deep in a dependency graph surfaces at every provider that transitively needs it, so the compiler prints far more error blocks than there are mistakes — and the reader's task is to find the one root cause among the repeats. + +## What triggers it + +This class is the *volume* form of a [surfaced check-trait failure](check-trait-failure.md): it arises whenever several components share a transitive dependency and that dependency is unmet. A chain of providers — `ProvideBaz` depends on `CanBar`, `ProvideBar` on `CanFoo`, `ProvideFoo` on a `name` field — is wired onto a context missing that field, and all three components are checked together. + +```rust +#[cgp_impl(new ProvideFoo)] +impl Foo where Self: HasName { /* needs the `name` field */ } + +#[cgp_impl(new ProvideBar)] +#[uses(CanFoo)] // depends on Foo +impl Bar { /* … */ } + +#[cgp_impl(new ProvideBaz)] +#[uses(CanBar)] // depends on Bar +impl Baz { /* … */ } + +#[derive(HasField)] +pub struct App { pub age: u8 } // no `name` field + +delegate_components! { + App { FooComponent: ProvideFoo, BarComponent: ProvideBar, BazComponent: ProvideBaz } +} + +check_components! { + App { BazComponent, BarComponent, FooComponent } +} +``` + +## The 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. + +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. + +The hidden analogue exists too: exercising the same chain through direct consumer-trait *calls* rather than a check produces one `E0599` per call, none of which reaches the cause — the [hidden](../hidden/unsatisfied-dependency.md) failure, multiplied. This document covers the surfaced (checked) cascade, where the cause is present but buried in volume. + +## Where the root cause is + +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. + +## 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. + +## 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. + +## Related + +- [Check-trait failure (surfaced)](check-trait-failure.md) — the single-cause diagnostic this class multiplies. +- [Unsatisfied dependency (hidden)](../hidden/unsatisfied-dependency.md) — the same root cause seen through consumer-trait calls, where each block hides the cause instead of surfacing it. +- [Debugging CGP compile errors](../../guides/debugging.md) — why the error count tells you nothing about the number of mistakes, and how to isolate the one link. +- [`check_components!`](../../reference/macros/check_components.md) and [`IsProviderFor`](../../reference/traits/is_provider_for.md). diff --git a/docs/errors/hidden/unsatisfied-dependency.md b/docs/errors/hidden/unsatisfied-dependency.md new file mode 100644 index 00000000..edc93db7 --- /dev/null +++ b/docs/errors/hidden/unsatisfied-dependency.md @@ -0,0 +1,92 @@ +# Unsatisfied dependency (hidden) + +A provider is wired onto a context that cannot satisfy the provider's impl-side dependency, and the failure is triggered by calling the consumer-trait method directly — so the compiler reports only that the method's trait bounds are unsatisfied (`E0599`, or `E0277` when the consumer trait is used as a bound) and hides the dependency that actually failed. + +## What triggers it + +This class arises whenever [wiring](../../reference/macros/delegate_components.md) is lazy and the mistake is exercised through the consumer trait rather than a check. A provider carries an impl-side dependency in its `where` clause; a context is wired to that provider but does not meet the dependency; and because `delegate_components!` records the entry without verifying the provider's transitive requirements, the wiring is accepted. The failure appears only when the context's consumer-trait method is finally called. + +```rust +#[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, // impl-side dependency +{ + fn greet(&self) { + let _ = self.name(); + } +} + +#[derive(HasField)] +pub struct Person { + pub age: u8, // no `name` field, so Person does not implement HasName +} + +// Accepted even though Person cannot satisfy GreetHello's `Self: HasName`. +delegate_components! { + Person { + GreeterComponent: GreetHello, + } +} + +fn main() { + Person { age: 0 }.greet(); // fails here, at the call site +} +``` + +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 + +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. + +The reason the cause is absent is a deliberate compiler heuristic, and it is the whole point of isolating this class. `#[cgp_component]` emits a blanket impl of the consumer trait for any context that is its own provider, so when the compiler checks `Person: CanGreet` it finds that blanket impl (alongside every other candidate impl in scope), determines it does not apply because `Person: Greeter` is unmet, and declines to expand the nested `where` bound that made it inapplicable. `rustc` does not, in general, report *which* `where`-clause bound of an inapplicable blanket impl failed — it reports only that the top-level trait is unimplemented and stops — because with a blanket impl and multiple candidates present, expanding every candidate's unmet sub-bound would usually be noise. This is a long-standing, acknowledged limitation of the `E0599` diagnostic, tracked upstream in [rust-lang/rust#61661](https://github.com/rust-lang/rust/issues/61661) and [#75222](https://github.com/rust-lang/rust/issues/75222), not a quirk of CGP. The suppression is exactly what CGP's [`IsProviderFor`](../../reference/traits/is_provider_for.md) supertrait exists to defeat — but only a check that requires `IsProviderFor` *directly* forces the compiler past the heuristic; the lazy consumer-trait path never does. + +The same `E0599`/`E0277` shape, and the same absence of a cause, also arises when the wiring is not merely *unmet* but *missing* — a component never wired on the context, or an [unregistered namespace path](../checks/unregistered-namespace-path.md) whose redirect lands on an empty table slot. Whether a provider was found but its dependency failed, or no provider was found at all, the consumer-trait path hides it identically; the two are told apart only by promoting the error into a surfaced one. + +## Where the root cause is + +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. + +## 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. + +To make the cause visible, **promote the hidden error into a surfaced one** by forcing the check at the wiring site with [`check_components!`](../../reference/macros/check_components.md): + +```rust +check_components! { + Person { + GreeterComponent, + } +} +``` + +The check asserts `CanUseComponent` for `Person`, which requires `GreetHello: IsProviderFor` as a *direct* bound. That defeats the suppression heuristic, and the compiler now reports the full `E0277` note chain ending at the missing `HasField`. That surfaced form is the [check-trait failure](../checks/check-trait-failure.md) class, and moving between the two is the standard technique the [debugging guide](../../guides/debugging.md) prescribes for a hidden dependency. + +## 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. + +## 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. + +## Related + +- [Check-trait failure (surfaced)](../checks/check-trait-failure.md) — the same unmet dependency forced through a check, where the cause *is* reported; the two are the two halves of one phenomenon. +- [Debugging CGP compile errors](../../guides/debugging.md) — the playbook: read the error's shape, then move the error to the wiring site with a check to surface a hidden cause. +- [`IsProviderFor`](../../reference/traits/is_provider_for.md) and [`DelegateComponent`](../../reference/traits/delegate_component.md) — the traits every wiring error is ultimately about; `IsProviderFor` is the supertrait a check uses to defeat the suppression heuristic. +- [`check_components!`](../../reference/macros/check_components.md) and the [check-traits concept](../../concepts/check-traits.md) — why wiring is lazy and how a check forces a readable error at the wiring site. diff --git a/docs/errors/wiring/conflicting-wiring.md b/docs/errors/wiring/conflicting-wiring.md new file mode 100644 index 00000000..a10f35c6 --- /dev/null +++ b/docs/errors/wiring/conflicting-wiring.md @@ -0,0 +1,66 @@ +# Conflicting wiring + +The same component key or generated name is wired or declared twice, so the expansion emits two overlapping impls and the compiler rejects them with a coherence error (`E0119`) or a duplicate-definition error (`E0428`). + +## What triggers it + +CGP lowers each wiring block independently, with no view of any other block or of the surrounding module, so a collision that only a whole-program view could catch is left to the compiler. The mistake takes several forms, all reducing to "one key or name defined twice": + +```rust +// Duplicate key — the same component mapped twice (E0119). +delegate_components! { Person { GreeterComponent: GreetHello } } +delegate_components! { Person { GreeterComponent: GreetGoodbye } } + +// Overlapping generic — a generic table and a specific one collide (E0119). +delegate_components! { Wrapper { GreeterComponent: GreetHello } } +delegate_components! { Wrapper { GreeterComponent: GreetHello } } + +// Duplicate generated name — struct declared twice (E0428, plus E0119 on its impls). +#[cgp_impl(new GreetHello)] impl Greeter { /* … */ } +#[cgp_impl(new GreetHello)] impl Greeter { /* … */ } +``` + +The same shape appears with an `open` header colliding with an explicit mapping, a `@`-path duplicated under a `namespace` header, a context that joins a namespace and *also* directly wires a path the namespace registers, a `for … in` loop that wires a *bare* key alongside a `namespace` join (both generate a blanket `DelegateComponent` impl over every key), a duplicate `cgp_namespace!` entry, a `#[cgp_component]`-derived marker clashing with a hand-declared type, a duplicate `#[default_impl]` key, or a duplicate `check_components!` entry. + +## The diagnostic + +Two error codes, by whether the collision is between *impls* or between *definitions*. A duplicate key or overlapping generic produces **`E0119` conflicting implementations**; a duplicate generated *name* produces **`E0428` "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. + +Two wrinkles round out the shape, and the second is where Rust's coherence reasoning shows through. When the key is a `@`-path, the conflicting trait's name expands into a long `PathCons>` type that dominates the message — the caret still lands on the path leaf, so read the caret, not the type. And a collision in which one impl is a *blanket* — an `open` header, a `namespace` join, or a bare-key `for` loop, each lowering to `impl for Ctx` over every key — often carries an extra "downstream crates may implement …" note. That note is `rustc` making its coherence rule explicit, not a second, separate problem. Coherence forbids two impls that *could* both apply to some type, and it reasons about types a downstream crate might add, not only the types in scope; because the blanket's applicability to a given key hinges on a trait bound a downstream crate could later satisfy, the compiler cannot prove the two impls will never overlap, so it rejects them and cites the hypothetical future impl as the reason. This future-compatibility (negative-reasoning) rule is what [RFC 2451](https://rust-lang.github.io/rfcs/2451-re-rebalancing-coherence.html) formalizes. A pure blanket-versus-blanket overlap — two `for`/`namespace` headers each keyed over every key — needs no such reasoning and prints the bare conflict with fully-generic `DelegateComponent<_>` / `IsProviderFor<_, _, _>` carets and no downstream note. + +## Where the root cause is + +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. + +## Resolving it + +Remove one of the two entries. The one case with a subtler fix is the context that joins a namespace and also wires a path the namespace itself registers: there, keep the override by targeting a path the namespace *routes to* but does not itself terminate, so the context supplies the leaf without overlapping the namespace's own impl (see [`#[cgp_namespace]`](../../reference/macros/cgp_namespace.md) and its Known issues). For a duplicate check-trait name from two tables over one context, add a `#[check_trait(Name)]` to one. + +## 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. + +## Backing fixtures + +The `E0119` conflicts: + +- [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) and [override_registered_path.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/override_registered_path.rs) — a duplicate `cgp_namespace!` entry (a *single* `E0119` on the namespace-trait impl), and a context overriding a path its joined namespace already registers (the specific-versus-blanket case, with the "downstream crates may implement" 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 … in` loop alongside a `namespace` join, the pure blanket-versus-blanket case; its `.stderr` pins the fully-generic `DelegateComponent<_>` / `IsProviderFor<_, _, _>` carets with no downstream note. +- [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). + +The `E0428` name clashes: + +- [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`. + +## Related + +- [Orphan-rule violation](orphan-rule.md), [Wiring cycle](wiring-cycle.md), [Unconstrained generic](unconstrained-generic.md) — the sibling structural classes. +- [`delegate_components!`](../../reference/macros/delegate_components.md), [`cgp_namespace!`](../../reference/macros/cgp_namespace.md), and [`DelegateComponent`](../../reference/traits/delegate_component.md). +- [Debugging CGP compile errors](../../guides/debugging.md) — the `E0119`/`E0428` entries in the decoder. diff --git a/docs/errors/wiring/namespace-inheritance-cycle.md b/docs/errors/wiring/namespace-inheritance-cycle.md new file mode 100644 index 00000000..0c38d90d --- /dev/null +++ b/docs/errors/wiring/namespace-inheritance-cycle.md @@ -0,0 +1,50 @@ +# Namespace inheritance cycle + +Two namespaces that inherit from each other — or one that inherits itself — make resolving any key chase the inheritance chain forever, so the trait solver overflows with `E0275`, reported *at the `cgp_namespace!` definitions themselves*. + +## What triggers it + +The mistake is a circular parent chain among namespaces: `A` names `B` as its parent while `B` names `A`, or a namespace names itself. A [namespace](../../reference/macros/cgp_namespace.md) inherits its parent through a blanket impl whose `where` clause requires the parent's lookup trait, so a parent chain that loops back on itself produces a `where` clause that can never be discharged. + +```rust +cgp_namespace! { + new NamespaceA: NamespaceB {} // A inherits B +} + +cgp_namespace! { + new NamespaceB: NamespaceA {} // B inherits A — the chain loops +} +``` + +`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 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. + +The overflow itself is ordinary `rustc` behavior. The trait solver walks a requirement's supporting bounds to a bounded depth — the default `recursion_limit` is 128 — and reports `E0275` when it exceeds that depth without terminating. A genuine cycle never terminates, so the limit is only ever reached, never cleared; the `help:` suggestion to raise it is generic advice the compiler prints for every overflow and does not apply here. + +## Where the root cause is + +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 + +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. + +## Notes for tooling + +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. + +## 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. + +## Related + +- [Wiring cycle](wiring-cycle.md) — the sibling cycle class through [`UseContext`](../../reference/providers/use_context.md); both overflow with `E0275`, but that one is lazy (surfaces only when checked, hides as `E0599` when called) while this one is eager (caught at the namespace definitions). +- [Conflicting wiring](conflicting-wiring.md), [Orphan-rule violation](orphan-rule.md), [Unconstrained generic](unconstrained-generic.md) — the sibling structural classes. +- [`#[cgp_namespace]`](../../reference/macros/cgp_namespace.md) and [`DefaultNamespace`](../../reference/traits/default_namespace.md) — the inheritance blanket impl whose `where` clause the cycle poisons. +- [Debugging CGP compile errors](../../guides/debugging.md) — the `E0275`/overflow entry in the decoder. diff --git a/docs/errors/wiring/orphan-rule.md b/docs/errors/wiring/orphan-rule.md new file mode 100644 index 00000000..cc425bdc --- /dev/null +++ b/docs/errors/wiring/orphan-rule.md @@ -0,0 +1,49 @@ +# Orphan-rule violation + +A generated impl targets a foreign trait and a type built entirely from foreign pieces, which Rust's orphan rule forbids, so the expansion fails with `E0210` (or `E0117`) — most often when a prefixed `#[default_impl]` is registered from a crate that owns neither the namespace nor the path. + +## What triggers it + +The mistake is registering a per-type default for a *prefixed* component into a namespace from a downstream crate that does not own it. When a component carries `#[prefix(@app in …)]`, its namespace key is a path, so a `#[default_impl(@path in Namespace)]` for it expands to an impl of the foreign namespace trait for a `Self` type built from the `cgp`-owned `PathCons`/`Symbol` spine and the upstream component marker — every element foreign to the registering crate. + +```rust +// In a downstream crate, for an upstream prefixed component `Announcer`: +#[cgp_impl(new AnnounceQuietly)] +#[default_impl(@app.AnnouncerComponent in AppNamespace)] // AppNamespace + path all foreign +impl Announcer +where + Self: HasName, +{ + fn announce(&self) -> String { format!("(psst, {})", self.name()) } +} +``` + +Rust accepts a foreign-trait impl only when at least one type in it is local, and here none is, so the orphan rule rejects it. 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 diagnostic + +The compiler reports **`E0210`** — "type parameter `__Components__` must be used as the type parameter for some local type" — with the caret on the `#[cgp_impl(new …)]` attribute that generated the impl, and 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." A final note attributes the error to the `cgp_impl` attribute macro. 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-index entry](https://doc.rust-lang.org/error_codes/E0210.html) is its reference description. + +## Where the root cause is + +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 the component is *prefixed* and the namespace lives *upstream*, 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. + +## Resolving it + +Register the default from the crate that owns the namespace, or key the default on a **local** component whose marker is a type the registering crate owns (`#[default_impl(LocalComponent in Namespace)]`), which satisfies the orphan rule because a local type is present in the impl. 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). 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 `Self` type is a `PathCons>` spine and whose trait is a namespace trait — and translate the generic orphan message into the CGP remedy: "a prefixed `#[default_impl]` for a foreign namespace must live in the namespace's own crate; use a local component key or a namespace body entry to register it downstream." 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. + +## 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; its `.stderr` pins the `E0210` and the "implementing a foreign trait" notes. The orphan-safe counterpart — a *local* component key registered into a foreign namespace — is exercised in the cross-crate test packages. + +## Related + +- [Conflicting wiring](conflicting-wiring.md), [Wiring cycle](wiring-cycle.md), [Unconstrained generic](unconstrained-generic.md) — the sibling structural classes. +- [`DefaultNamespace`](../../reference/traits/default_namespace.md) and [`#[cgp_namespace]`](../../reference/macros/cgp_namespace.md) — the namespace mechanics behind the restriction. +- [Debugging CGP compile errors](../../guides/debugging.md) — the `E0210`/`E0117` entry in the decoder. diff --git a/docs/errors/wiring/unconstrained-generic.md b/docs/errors/wiring/unconstrained-generic.md new file mode 100644 index 00000000..ecfb0518 --- /dev/null +++ b/docs/errors/wiring/unconstrained-generic.md @@ -0,0 +1,49 @@ +# Unconstrained generic + +A per-entry generic parameter appears only in the provider value and never reaches the key, so the generated impl leaves the parameter unconstrained and the compiler rejects it with `E0207`. + +## What triggers it + +A `delegate_components!` entry may introduce its own generic parameters, but a per-entry generic is well-formed only when it reaches the *key* — where `DelegateComponent>` binds it. Writing one that appears only in the provider value never binds it, and CGP lowers the entry faithfully rather than second-guessing it, so the compiler rejects the free parameter exactly as it would a hand-written impl with an unused parameter. + +```rust +delegate_components! { + Person { + GreeterComponent: GreetWith, // T is in the value, never the key + } +} +// lowers to an impl with an unconstrained parameter: +impl DelegateComponent for Person { + type Delegate = GreetWith; // T constrains nothing +} +``` + +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 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. + +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-index entry](https://doc.rust-lang.org/error_codes/E0207.html) is its reference description. 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. + +## Where the root cause is + +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 + +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. + +## Notes for tooling + +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. + +## 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 ``. + +## Related + +- [Conflicting wiring](conflicting-wiring.md), [Orphan-rule violation](orphan-rule.md), [Wiring cycle](wiring-cycle.md) — the sibling structural classes. +- [`delegate_components!`](../../reference/macros/delegate_components.md) and [`DelegateComponent`](../../reference/traits/delegate_component.md). +- [Debugging CGP compile errors](../../guides/debugging.md) — the `E0207` entry in the decoder. diff --git a/docs/errors/wiring/wiring-cycle.md b/docs/errors/wiring/wiring-cycle.md new file mode 100644 index 00000000..f145a6c2 --- /dev/null +++ b/docs/errors/wiring/wiring-cycle.md @@ -0,0 +1,59 @@ +# Wiring cycle + +A delegation chases its own tail — a component wired to a provider whose resolution routes back to that same component — so when the wiring is forced through a check the trait solver overflows with `E0275`. + +## What triggers it + +The classic cycle is delegating a component to [`UseContext`](../../reference/providers/use_context.md) when the context's only implementation of that component *is* that delegation. `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 the provider trait requires resolving the consumer trait, which requires the provider trait again. + +```rust +#[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 with no terminating provider. +delegate_components! { + Person { + GreeterComponent: UseContext, + } +} +``` + +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 + +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. + +The `E0275` itself is ordinary `rustc` behavior. The trait solver follows a requirement's supporting bounds only to a bounded depth — the default `recursion_limit` is 128 — and raises `E0275` when it exceeds that depth without resolving, appending its standard `help: consider increasing the recursion limit`. That advice suits a merely *deep* obligation, but a true cycle never terminates at any depth, so the limit here is only ever hit, never cleared; the suggestion does not apply and should be read past. This is why the cycle must be exercised through a check to surface at all: the overflow is computed only when the solver is driven into the loop, and `#[cgp_component]`'s blanket impl otherwise lets the method probe abandon the requirement as unsatisfied without ever entering it. + +## Where the root cause is + +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 + +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. + +## Notes for tooling + +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. + +## 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. + +## Related + +- [Unsatisfied dependency (hidden)](../hidden/unsatisfied-dependency.md) — the `E0599` shape the same cycle takes when exercised by a method call instead of a check. +- [Namespace inheritance cycle](namespace-inheritance-cycle.md) — the sibling `E0275` cycle through circular namespace inheritance rather than `UseContext` delegation; that one is caught *eagerly* at the namespace definitions, where this one is lazy. +- [Conflicting wiring](conflicting-wiring.md), [Orphan-rule violation](orphan-rule.md), [Unconstrained generic](unconstrained-generic.md) — the sibling structural classes. +- [`UseContext`](../../reference/providers/use_context.md) — the provider whose misuse most often causes the cycle. +- [Debugging CGP compile errors](../../guides/debugging.md) — the `E0275` entry in the decoder. diff --git a/docs/guides/debugging.md b/docs/guides/debugging.md index cd896bd9..2ebc073d 100644 --- a/docs/guides/debugging.md +++ b/docs/guides/debugging.md @@ -16,7 +16,7 @@ An unsatisfied **`IsProviderFor`** bound means "this An unsatisfied **`DelegateComponent`** or **`Namespace
`** bound means the *lookup* failed: the context (or namespace) has no entry for that key. A `CanUseComponent` failure is the same thing one level up — the check assertion that a context can actually use a component — and its cause is always further down the note chain, at the `IsProviderFor` or `DelegateComponent` that really failed. -One error shape is especially worth recognizing: **"the trait `X` is not implemented for `T`" immediately followed by a `help:` note saying "the trait `X` *is* implemented for `T`"** (often with a slightly different generic parameter name). This near-contradiction means an impl exists but a nested requirement inside it is unmet, or two candidate impls make the choice ambiguous — the compiler found the impl but could not commit to it. Do not trust the `help:`; the real cause is a bound the impl carries that does not hold. During this project that exact shape flagged a namespace-registration impl whose leaked `where` clause demanded `PathCons<..>: HasErrorType`, a bound that could never be satisfied. +One error shape is especially worth recognizing: **"the trait `X` is not implemented for `T`" immediately followed by a `help:` note saying "the trait `X` *is* implemented for `T`"** (often with a slightly different generic parameter name). This near-contradiction means an impl exists but a nested requirement inside it is unmet, or two candidate impls make the choice ambiguous — the compiler found the impl but could not commit to it. Do not trust the `help:`; the real cause is a bound the impl carries that does not hold. That exact shape can flag, for instance, a namespace-registration impl whose leaked `where` clause demands `PathCons<..>: HasErrorType`, a bound that can never be satisfied. When a type in the error is elided as `...`, the full form is written to a file the compiler names in a final note (`the full name for the type has been written to '….long-type-….txt'`). Reading that file is how you recover *which* path or *which* context the error is really about — the elided middle is frequently the one segment that reveals the mistake, as when a dependency's context parameter turns out to be a `PathCons<..>` path rather than the real context. @@ -32,13 +32,13 @@ The single most effective technique, and the one to reach for before theorizing, Two habits make this fast. Build the reproduction *up* rather than *down* when you are unsure what triggers it: start from a version that compiles and add one variable at a time — one dependency, one generic parameter, one namespace entry — until it breaks, so the last addition is the trigger. And when two forms *should* behave identically but one fails, put both in one file as separate modules and compile once; a side-by-side that isolates the single difference is worth more than any amount of reasoning about why they "must" be the same. Reasoning about Rust's coherence and trait resolution on paper is unreliable enough that a compile-and-observe loop almost always settles the question faster than argument. -A scratch test under [crates/tests/cgp-tests](../../crates/tests) is the natural place for this: it compiles quickly against the real crates, and a compile-time wiring check needs no assertions to be a test — successful compilation *is* the pass. Delete the scratch once it has told you what you need, or promote it to a real regression test if it pins a fixed bug. +A scratch module or throwaway test is the natural place for this — it compiles quickly, and a compile-time wiring check needs no assertions to be a test, since successful compilation *is* the pass. Delete the scratch once it has told you what you need, or promote it to a regression test if it pins a fixed bug. ## Inspect what the macro actually emitted -When a reproduction still puzzles you, stop guessing about the expansion and look at it. The wiring failure is always "the emitted impls do not resolve," and you cannot reason about why until you see the impls. The `snapshot_*!` helpers in `cgp-macro-test-util` (`snapshot_cgp_impl!`, `snapshot_delegate_components!`, `snapshot_cgp_namespace!`, …) emit the real generated code into the module *and* pretty-print it into an inline snapshot, so wrapping a construct in one and running `cargo insta test --accept` shows you exactly what it produced. Reading the generated `where` clauses and the exact key types is often enough to spot the defect — a bound on the wrong type, a path with a segment too many, a parameter that should not be there. +When a reproduction still puzzles you, stop guessing about the expansion and look at it. The wiring failure is always "the emitted impls do not resolve," and you cannot reason about why until you see the impls. In any project, `cargo expand` prints the macro-expanded source directly; in a project set up with the CGP test utilities, the `snapshot_*!` helpers in `cgp-macro-test-util` (`snapshot_cgp_impl!`, `snapshot_delegate_components!`, `snapshot_cgp_namespace!`, …) additionally emit the real generated code into the module *and* pretty-print it into an inline snapshot you can review and keep. Either way, reading the generated `where` clauses and the exact key types is often enough to spot the defect — a bound on the wrong type, a path with a segment too many, a parameter that should not be there. -Comparing two expansions side by side localizes a difference precisely. When one form works and another does not, snapshot both and diff the output; the delta is the bug. This is how the `#[default_impl]` defect in this project was pinned — the namespace-registration impl it emitted carried a `where` clause that the equivalent body entry did not, and seeing the two snapshots next to each other made the leaked clause obvious. Snapshotting doubles as a permanent regression test, so the effort is not throwaway. +Comparing two expansions side by side localizes a difference precisely. When one form works and another does not, expand both and diff the output; the delta is the bug. This is how a `#[default_impl]` defect can be pinned: the namespace-registration impl it emitted carried a `where` clause that the equivalent body entry did not, and seeing the two expansions next to each other made the leaked clause obvious. Capturing the expansion as a snapshot doubles as a permanent regression test, so the effort is not throwaway. ## Bisect a wiring table @@ -48,15 +48,17 @@ For a large `delegate_components!` or `cgp_namespace!` table that fails as a who A handful of specific compiler errors recur in CGP code, and each maps to a small set of causes. Use this as a lookup once you have the error code and the trait it names. -- **`E0277` on `IsProviderFor<…>` or a consumer trait** — a dependency is unmet or a component is unwired. Either the context has no `DelegateComponent` entry for the component (wire it), or the chosen provider needs something the context does not supply (read the note chain to the innermost failing bound and satisfy it, or add a [`check_components!`](../reference/macros/check_components.md) to name it). A bound like `SomeType: IsProviderFor<…>` where `SomeType` looks like a `RedirectLookup` or a path means the lookup resolved to a delegate that is not actually a provider for that component. -- **`E0119` conflicting implementation of `DelegateComponent<…>` (or a namespace trait)** — the same key is wired twice. Two `delegate_components!` entries for one key, two `cgp_namespace!` entries for one path, or — the subtle case — a context that joins a namespace and *also* wires a path the namespace itself registers, so the `namespace` blanket impl and the direct entry overlap. Remove one; to keep a context override, target a path the namespace routes to but does not itself terminate (see [`#[cgp_namespace]`](../reference/macros/cgp_namespace.md) Known issues). -- **`E0210`/`E0117` orphan-rule violation on a generated impl** — a `#[default_impl]` or hand-written provider impl is being written for a foreign trait and foreign types. A per-component `#[default_impl]` for a *prefixed* component expands to `impl Namespace for PathCons<..>`, which a downstream crate cannot write because the whole impl is foreign; register it from the namespace's own crate, or use an unprefixed component key the crate owns (see [`DefaultNamespace`](../reference/traits/default_namespace.md)). -- **`E0275`/overflow evaluating a requirement** — usually a wiring cycle. The classic case is delegating a component to [`UseContext`](../reference/providers/use_context.md) when the context's only implementation of that component *is* that delegation, so the lookup chases its own tail. Break the cycle by wiring the component to a concrete provider. -- **`E0207` unconstrained type parameter on a generated impl** — a generic parameter appears only in an associated-type position and so does not constrain the impl. This shows up when a *generic* provider is registered as a per-type default, since the provider's parameter lands only in the `Delegate` type; register a concrete provider instead. +- **`E0599` "method exists but its trait bounds were not satisfied"** — the *hidden* form of an unsatisfied dependency: a consumer-trait method called on a context whose wiring will not resolve. It names the consumer and provider traits but **hides** the dependency that actually failed, because the compiler drops the nested bound when a blanket impl sits among the candidate impls. Do not scan it for a root cause — there is none in it; promote it with a [`check_components!`](../reference/macros/check_components.md) to surface one. See [Unsatisfied dependency (hidden)](../errors/hidden/unsatisfied-dependency.md). +- **`E0277` on `IsProviderFor<…>`, `CanUseComponent<…>`, or a consumer trait** — a dependency is unmet or a component is unwired. Either the context has no `DelegateComponent` entry for the component (wire it), or the chosen provider needs something the context does not supply (read the note chain to the innermost failing bound and satisfy it, or add a [`check_components!`](../reference/macros/check_components.md) to name it). A bound whose *key* is a `PathCons<..>` path — `PathCons<..>: DefaultNamespace` or `Ctx: DelegateComponent>` unsatisfied — means a namespace redirect landed on a path no entry binds; bind a provider there (a `#[default_impl]`, a namespace body entry, or a direct `@path:` line), per [Unregistered namespace path](../errors/checks/unregistered-namespace-path.md). A bound like `SomeType: IsProviderFor<…>` where `SomeType` looks like a `RedirectLookup` means the lookup instead resolved to a delegate that is not actually a provider for that component. The surfaced (checked) form names the real bound in a `help:` note — see [Check-trait failure](../errors/checks/check-trait-failure.md); a repeated wall of these over a single cause is a [verbose cascade](../errors/checks/verbose-cascade.md). +- **`E0119` conflicting implementation of `DelegateComponent<…>` (or a namespace trait)** — the same key is wired twice. Two `delegate_components!` entries for one key, two `cgp_namespace!` entries for one path, or — the subtle case — a context that joins a namespace and *also* wires a path the namespace itself registers, so the `namespace` blanket impl and the direct entry overlap. Remove one; to keep a context override, target a path the namespace routes to but does not itself terminate (see [`#[cgp_namespace]`](../reference/macros/cgp_namespace.md) Known issues). See [Conflicting wiring](../errors/wiring/conflicting-wiring.md). +- **`E0210`/`E0117` orphan-rule violation on a generated impl** — a `#[default_impl]` or hand-written provider impl is being written for a foreign trait and foreign types. A per-component `#[default_impl]` for a *prefixed* component expands to `impl Namespace for PathCons<..>`, which a downstream crate cannot write because the whole impl is foreign; register it from the namespace's own crate, or use an unprefixed component key the crate owns (see [`DefaultNamespace`](../reference/traits/default_namespace.md)). See [Orphan-rule violation](../errors/wiring/orphan-rule.md). +- **`E0275`/overflow evaluating a requirement** — a resolution cycle. The classic case is delegating a component to [`UseContext`](../reference/providers/use_context.md) when the context's only implementation of that component *is* that delegation, so the lookup chases its own tail; break it by wiring the component to a concrete provider. This form surfaces only when *forced through a check* (a plain method call hides it as the `E0599` above) — see [Wiring cycle](../errors/wiring/wiring-cycle.md). A second form is a circular namespace parent chain (`cgp_namespace! { new A: B }` with `new B: A`, or `new A: A`), which overflows *eagerly at the `cgp_namespace!` definitions* — see [Namespace inheritance cycle](../errors/wiring/namespace-inheritance-cycle.md). For either, ignore the `help: increase the recursion limit` suggestion: a true cycle never terminates, so no limit clears it. +- **`E0207` unconstrained type parameter on a generated impl** — a generic parameter appears only in an associated-type position and so does not constrain the impl. This shows up when a *generic* provider is registered as a per-type default, since the provider's parameter lands only in the `Delegate` type; register a concrete provider instead. See [Unconstrained generic](../errors/wiring/unconstrained-generic.md). - **A dependency demanded of a `PathCons<..>` path rather than the context** — a bound like `PathCons<..>: HasErrorType` means a `Self`-keyed impl-side bound leaked onto an impl whose `Self` is a path key. This is a macro-level defect rather than a wiring mistake; capture it as a minimal reproduction and check the emitting macro's lowering. ## Related documentation +- [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. - [`IsProviderFor`](../reference/traits/is_provider_for.md) and [`DelegateComponent`](../reference/traits/delegate_component.md) — the two traits every wiring error is ultimately about. diff --git a/docs/implementation/AGENTS.md b/docs/implementation/AGENTS.md index 65f693b2..b0a74d4c 100644 --- a/docs/implementation/AGENTS.md +++ b/docs/implementation/AGENTS.md @@ -71,6 +71,8 @@ Because a CGP macro expands to ordinary Rust that the compiler checks afterward, 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. +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. + ## Keeping the reference and implementation documents aligned The reference and implementation documents are two views of the same macro and must not drift apart or duplicate each other. The reference owns the user-facing contract — syntax, the expansion shown as before/after Rust, and user-visible corner cases — and points only at library source. The implementation document owns the internal mechanics — the pipeline, the synthesizing functions, the AST types, and all test and snapshot pointers. A reference document may link to an implementation document to elaborate a corner case it does not want to explain in full, and every reference document's Source section links to its implementation counterpart so an agent can drop from "what it does" into "how it is built"; the implementation document links back to the reference for the user-facing semantics rather than re-deriving them. When you change a macro, update both views in the same change: the reference Expansion for the contract, and the implementation Pipeline and Generated items for the mechanics. diff --git a/docs/implementation/entrypoints/cgp_component.md b/docs/implementation/entrypoints/cgp_component.md index 3eb6d761..6897213d 100644 --- a/docs/implementation/entrypoints/cgp_component.md +++ b/docs/implementation/entrypoints/cgp_component.md @@ -66,9 +66,7 @@ The **marker struct's name span** is the provider identifier's span, not `Span:: ## Failure modes -This failure is one `#[cgp_component]` **intentionally defers to the Rust compiler**: each invocation expands with no view of the rest of the module, so a name clash only a whole-module view could catch is left to `rustc`. It is pinned by a fixture under [acceptable/cgp_component/](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component) in `cgp-compile-fail-tests`. - -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 `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 fixture exists to pin that span, since a regression to `call_site` would move the note onto the attribute. Pinned by [acceptable/cgp_component/duplicate_component_name.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/duplicate_component_name.rs). +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. ## Snapshots diff --git a/docs/implementation/entrypoints/cgp_impl.md b/docs/implementation/entrypoints/cgp_impl.md index 412195af..0cf96df3 100644 --- a/docs/implementation/entrypoints/cgp_impl.md +++ b/docs/implementation/entrypoints/cgp_impl.md @@ -68,7 +68,7 @@ The one wholly-synthesized item is each `#[default_impl]` delegation impl, built ## Failure modes -Both failures below are ones `#[cgp_impl]` **intentionally defers to the Rust compiler**: each block lowers independently with no view of any other, so a collision only a whole-program view could catch is left to `rustc`, exactly as two hand-written definitions would be. Each is pinned 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 fixture under [acceptable/cgp_impl/](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_impl) in `cgp-compile-fail-tests`. 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). diff --git a/docs/implementation/entrypoints/cgp_namespace.md b/docs/implementation/entrypoints/cgp_namespace.md index 1c857680..3ca6aa27 100644 --- a/docs/implementation/entrypoints/cgp_namespace.md +++ b/docs/implementation/entrypoints/cgp_namespace.md @@ -75,31 +75,15 @@ Each generated namespace impl is re-spanned onto the entry that produced it, so ## Failure modes -Some namespace input is accepted by the macro but then fails to compile, and these failures are ones the macro **intentionally defers to the Rust compiler** because it lacks the whole-program view the check needs. Each is intended behavior, not a bug, and is pinned by a fixture under [acceptable/cgp_namespace/](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace) in `cgp-compile-fail-tests`. +Some namespace input is accepted by the macro but fails to compile downstream, deferred to the compiler because it lacks the whole-program view the check needs. Each is intended behavior, not a bug, and its full anatomy lives in the [error catalog](../../errors/README.md); the [Error spans](#error-spans) section below covers how each caret is aimed at the offending entry. -A **duplicate key** — the same key mapped twice in one namespace block — emits two conflicting impls of the namespace lookup trait for that key and fails with `E0119`, exactly as two hand-written impls would. The error points at each offending entry rather than the whole block, per [Error spans](#error-spans): +A **duplicate key** (the same key mapped twice in one `cgp_namespace!` block), **overriding a path the joined namespace already registers**, and a **bare-key `for` loop alongside a `namespace` join** all produce `E0119` — the [conflicting wiring](../../errors/wiring/conflicting-wiring.md) error class. The override case is the subtle one: a `namespace N;` header emits a blanket `impl DelegateComponent for Ctx where Key: N` that covers every path `N` resolves, so a direct `@path: Provider` entry for a path `N` also registers overlaps with it. To keep such an override, target a path the namespace routes *to* but does not itself terminate, so the context supplies the leaf without the two impls overlapping. A `for in Table { Key: Value }` loop that wires the bound key *bare* rather than inside a path emits a second blanket `DelegateComponent` impl that overlaps the `namespace` header's blanket impl — which is why a loop key must sit inside a path. -```rust -cgp_namespace! { - new MyNamespace { - @foo.bar => @baz, - @foo.bar => @qux, // E0119: conflicting `MyNamespace<_>` impl for the same path - } -} -``` - -**Overriding a path the joined namespace already registers** also fails with `E0119`. A `namespace N;` header emits a blanket `impl DelegateComponent for Ctx where Key: N`, which covers every path `N` resolves — including any path `N` registers directly (through a body entry or a `#[default_impl]`). A direct `@path: Provider` entry on the same context emits a second `DelegateComponent> for Ctx` for that path, and the two overlap. The macro lowers both faithfully; only the whole program reveals that the path is claimed twice. To leave a path open for a context to override, the namespace must route the component *to* that path (via a `#[prefix]` redirect it inherits) without *terminating* the redirect there — so the context supplies the leaf: +**Registering a default for a prefixed component into a namespace the crate does not own** fails the orphan rule (`E0210`) — the [orphan-rule violation](../../errors/wiring/orphan-rule.md) error class. `#[default_impl(@path in Namespace)]` on a prefixed component expands to `impl Namespace<_> for PathCons<..>`, an impl of a foreign trait for a fully foreign type, so a per-component default keyed on a prefix path is confined to the namespace's own crate; the orphan-safe alternative is a *local* component key. -```rust -delegate_components! { - App { - namespace AppNamespace; // registers @app.GreeterComponent via a #[default_impl] - @app.GreeterComponent: GreetBye, // E0119: also implements DelegateComponent for that path - } -} -``` +**Joining a namespace that routes a component to a path no entry binds** produces an `E0277` when the wiring is checked — the [unregistered namespace path](../../errors/checks/unregistered-namespace-path.md) error class. A `#[prefix]` registers the *routing* (the namespace resolves the component to a `RedirectLookup` along the path) but not a provider at the path's leaf; if nothing (a `#[default_impl]`, a body entry, or a direct `@path:` line) ever binds one, the `RedirectLookup` finds no `DelegateComponent` entry and the lookup fails. This is a *lookup* failure, distinct from an unsatisfied *dependency*. -**Registering a default for a prefixed component into a namespace the crate does not own** fails the orphan rule (`E0210`/`E0117`). `#[default_impl(@path in Namespace)]` on a prefixed component expands to `impl Namespace<_> for PathCons<..>`, whose `Self` type is built entirely from the `cgp`-owned `PathCons`/`Symbol` types and the component's marker. Rust accepts a foreign-trait impl only if a type in it is local, so a downstream crate — which owns neither the foreign `Namespace` trait nor any element of the foreign path — cannot write it. A per-component default keyed on a prefix path is therefore confined to the namespace's own crate; the orphan-safe alternative is a *local* component key (`#[default_impl(LocalComponent in Namespace)]`), whose marker is a local type. This is a whole-program coherence fact the macro cannot see, so it defers to the compiler. +**A circular namespace parent chain** (`new A: B` together with `new B: A`, or a self-inheriting `new A: A`) overflows the trait solver with `E0275` — the [namespace inheritance cycle](../../errors/wiring/namespace-inheritance-cycle.md) error class. The inheritance blanket impl's `where` clause requires the parent's lookup trait, so a looping parent chain yields a `where` clause that never discharges; unlike the lazy `UseContext` [wiring cycle](../../errors/wiring/wiring-cycle.md), the compiler catches it *eagerly* while checking each generated inheritance impl, so both `cgp_namespace!` definitions carry the overflow with no use site required. ## Snapshots @@ -133,11 +117,14 @@ 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, described under [Failure modes](#failure-modes): +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`). - [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`); depends on `cgp-test-crate-a` for the upstream component and namespace. +- [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 blanket-versus-blanket shape of [conflicting wiring](../../errors/wiring/conflicting-wiring.md). +- [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. ## Source diff --git a/docs/implementation/entrypoints/check_components.md b/docs/implementation/entrypoints/check_components.md index 04f9f9c0..706e0173 100644 --- a/docs/implementation/entrypoints/check_components.md +++ b/docs/implementation/entrypoints/check_components.md @@ -50,13 +50,13 @@ 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 with `E0277` at the check site, naming the missing bound through `IsProviderFor` rather than lazily at the eventual call site. 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`. This is pinned by [acceptable/check_components/missing_dependency.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_dependency.rs) in `cgp-compile-fail-tests`, a regression test for that re-span; the [`delegate_components!` counterpart](delegate_components.md) leaves the same wiring unchecked to show the contrasting lazy error at the call site. +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 **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. -A **duplicate check entry** — the same component and parameters listed twice, whether directly (`Context { FooComponent, FooComponent }`) or through array expansion (`[A, A]: P`) — emits two identical check impls and fails with the coherence error `E0119`, exactly as two hand-written impls would. The span override aims the conflict at the repeated component. +A **duplicate check entry** — the same component and parameters listed twice, directly (`Context { FooComponent, FooComponent }`) or through array expansion (`[A, A]: P`) — emits two identical check impls and fails with `E0119`, the [conflicting wiring](../../errors/wiring/conflicting-wiring.md) error class. The span override aims the conflict at the repeated component. -**Two tables for the same context with no `#[check_trait]` override** both derive the same `__Check{Context}` name and emit conflicting trait definitions, failing with `E0428`. The fix is a `#[check_trait(Name)]` on one table, which is why the override exists. +**Two tables for the same context with no `#[check_trait]` override** both derive the same `__Check{Context}` name and emit conflicting trait definitions, failing with `E0428` (the [conflicting wiring](../../errors/wiring/conflicting-wiring.md) error class). The fix is a `#[check_trait(Name)]` on one table, which is why the override exists. ## Snapshots @@ -75,7 +75,7 @@ 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)). +- [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. ## Source diff --git a/docs/implementation/entrypoints/delegate_and_check_components.md b/docs/implementation/entrypoints/delegate_and_check_components.md index bbbf5247..0ee84362 100644 --- a/docs/implementation/entrypoints/delegate_and_check_components.md +++ b/docs/implementation/entrypoints/delegate_and_check_components.md @@ -57,7 +57,7 @@ A table-level `#[check_trait(Name)]` overrides the derived name. A generic table ## Failure modes -Because the macro reuses the `delegate_components!` and `check_components!` pipelines, its accepted-but-uncompilable inputs are the same ones those macros defer to the compiler, and each is intended behavior rather than a bug. A **duplicate key** emits conflicting `DelegateComponent` impls on the wiring side (and, if checked, conflicting check impls too), failing with the coherence error `E0119` exactly as [`delegate_components!`](delegate_components.md) documents. A **missing impl-side dependency** is what the check half exists to surface — the derived check fails to compile at the wiring site and names the unmet bound. +Because the macro reuses the `delegate_components!` and `check_components!` pipelines, its accepted-but-uncompilable inputs are the same ones those macros defer to the compiler. A **duplicate key** produces `E0119` on the wiring side (and, if checked, the check side too) — the [conflicting wiring](../../errors/wiring/conflicting-wiring.md) error class. A **missing impl-side dependency** is what the check half exists to surface — the [check-trait failure](../../errors/checks/check-trait-failure.md) error class, reported at the wiring site rather than lazily at the call site. A table whose every entry is `#[skip_check]` (or an empty table) still emits the check trait but no check impls, so it verifies nothing; this parallels the empty `#[check_providers()]` that `check_components!` rejects, but here it is accepted because skipping every entry is a legitimate, if degenerate, request. diff --git a/docs/implementation/entrypoints/delegate_components.md b/docs/implementation/entrypoints/delegate_components.md index 34ba90ca..0e0bcf0e 100644 --- a/docs/implementation/entrypoints/delegate_components.md +++ b/docs/implementation/entrypoints/delegate_components.md @@ -74,43 +74,13 @@ Because the `.stderr` fixtures record the exact line and column of each caret, t ## Failure modes -Some `delegate_components!` inputs are accepted by the macro but then fail to compile, and for `delegate_components!` these failures are all ones the macro **intentionally defers to the Rust compiler**: it lowers each block independently, with no whole-program view, so a mistake that only a global check could catch is left to `rustc`. Each is intended behavior rather than a bug — the diagnostic below is the one a user should expect — and each is pinned by a fixture under [acceptable/delegate_components/](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components) in `cgp-compile-fail-tests`. +Some `delegate_components!` inputs are accepted by the macro but fail to compile downstream, because the macro lowers each block independently with no whole-program view. These are intended behavior rather than bugs, and their full anatomy is documented in the [error catalog](../../errors/README.md); the [Error spans](#error-spans) section below covers how each caret is aimed at the offending entry. -A **duplicate key** — the same component mapped twice, whether by two entries in one block, two separate blocks, or an `open` header colliding with an explicit mapping — emits two conflicting `DelegateComponent` impls and fails with the coherence error `E0119`, exactly as two hand-written impls would. The error points at each offending entry rather than the whole block, per [Error spans](#error-spans): +A **duplicate key** (the same component mapped twice — across blocks, within a block, or an `open` header colliding with a mapping) and an **overlapping generic entry** (a ` Wrapper` table conflicting with a specific `Wrapper`) both produce the coherence error `E0119`, the [conflicting wiring](../../errors/wiring/conflicting-wiring.md) error class. -```rust -delegate_components! { Person { GreeterComponent: GreetHello } } -delegate_components! { Person { GreeterComponent: GreetGoodbye } } // E0119: conflicting impl -``` - -An **overlapping generic entry** is the same failure reached through generics: a ` Wrapper` table and a specific `Wrapper` table wiring the same component overlap at `Wrapper`, and since stable Rust has no specialization the two impls conflict with `E0119`. - -```rust -delegate_components! { Wrapper { GreeterComponent: GreetHello } } -delegate_components! { Wrapper { GreeterComponent: GreetHello } } // E0119 at Wrapper -``` +A **missing impl-side dependency** — a lazily-wired provider whose `where` clause the context cannot satisfy — is accepted here and fails only when the consumer trait is used. Its full anatomy (the `E0599` that names `Person: Greeter` while *hiding* the missing dependency, and how a `check_components!` site promotes it to a readable error) is documented as the [hidden unsatisfied-dependency](../../errors/hidden/unsatisfied-dependency.md) error class in the error catalog. -A **missing impl-side dependency** follows from wiring being lazy: `delegate_components!` records the entry without checking the provider's transitive requirements, so wiring a provider whose `where` clause the context cannot satisfy is accepted, and the unmet bound surfaces only when the consumer trait is used (an `E0599` naming the missing `Greeter` / `IsProviderFor` bound). A `check_components!` site moves the same error earlier, to the wiring. - -```rust -// GreetHello requires `Self: HasName`, but `Person` has no `name` field. -delegate_components! { Person { GreeterComponent: GreetHello } } // accepted — wiring is lazy -person.greet(); // E0599: `Person: Greeter` is not satisfied -``` - -An **unconstrained per-entry generic** is accepted when its parameter appears only in the provider value and not in the key. A per-entry generic list is well-formed only when it reaches the key (as in ` BazKey`, where `DelegateComponent>` binds it); writing one that never does is ill-formed input, and the macro lowers it faithfully rather than second-guessing it, so the compiler rejects the free parameter with `E0207` just as it would a hand-written impl with an unused parameter: - -```rust -delegate_components! { - Person { - GreeterComponent: GreetWith, // T never reaches the key - } -} -// lowers to an impl with an unconstrained parameter: -impl DelegateComponent for Person { - type Delegate = GreetWith; // E0207: `T` is not constrained -} -``` +An **unconstrained per-entry generic** — a parameter that reaches the provider value but not the key — is rejected with `E0207`, the [unconstrained generic](../../errors/wiring/unconstrained-generic.md) error class. ## Known issues @@ -155,14 +125,14 @@ 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 are described under [Failure modes](#failure-modes) above: +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`). +- [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/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. ## Source diff --git a/docs/reference/macros/cgp_namespace.md b/docs/reference/macros/cgp_namespace.md index c5789262..8fe50627 100644 --- a/docs/reference/macros/cgp_namespace.md +++ b/docs/reference/macros/cgp_namespace.md @@ -212,7 +212,9 @@ Inheritance composes the same way at the namespace level: `ExtendedNamespace: De ## Known issues -A context that joins a namespace with `namespace N;` cannot also wire, directly on itself, a path that `N` already registers. The `namespace N;` header emits a blanket `impl DelegateComponent for Ctx where Key: N`, which already covers every path `N` resolves; a direct `@path: Provider` entry on the same context emits a second `DelegateComponent` impl for that path, and the compiler rejects the overlap with `E0119`. Overriding therefore works only on a path the namespace routes *to* but does not itself terminate: register the component's [`#[prefix]`](cgp_component.md) redirect in a base namespace the context inherits, and leave the leaf path unclaimed by the namespace so the context can supply it. A path the namespace registers with a `:` body entry or a `#[default_impl]` is not overridable on the context; change it in the namespace instead. This is a whole-program coherence fact the macro cannot detect, so it is deferred to the compiler. +A context that joins a namespace with `namespace N;` cannot also wire, directly on itself, a path that `N` already registers. The `namespace N;` header emits a blanket `impl DelegateComponent for Ctx where Key: N`, which already covers every path `N` resolves; a direct `@path: Provider` entry on the same context emits a second `DelegateComponent` impl for that path, and the compiler rejects the overlap with `E0119`. Overriding therefore works only on a path the namespace routes *to* but does not itself terminate: register the component's [`#[prefix]`](cgp_component.md) redirect in a base namespace the context inherits, and leave the leaf path unclaimed by the namespace so the context can supply it. A path the namespace registers with a `:` body entry or a `#[default_impl]` is not overridable on the context; change it in the namespace instead. The same overlap arises from a bare-key `for` loop — `for in Table { Key: Value }` emits a blanket `DelegateComponent` impl of its own — which is why a loop key must be embedded in a path (`@app.SomeComponent.Key: Value`). This is a whole-program coherence fact the macro cannot detect, so it is deferred to the compiler; its full anatomy is in the [conflicting wiring](../../errors/wiring/conflicting-wiring.md) error class. + +Two further whole-program failures are deferred to the compiler the same way. If a component is routed into a joined namespace by a `#[prefix]` but no entry ever *binds* a provider at its path — no `#[default_impl]`, body entry, or direct wiring — the redirect lands on an empty table slot, and a `check_components!` reports the lookup as unsatisfied (`E0277`); this is the [unregistered namespace path](../../errors/checks/unregistered-namespace-path.md) error class. And a circular parent chain — `new A: B` with `new B: A`, or a self-inheriting `new A: A` — makes the inheritance blanket impl's `where` clause loop, which the compiler rejects eagerly at the `cgp_namespace!` definitions with an `E0275` overflow; this is the [namespace inheritance cycle](../../errors/wiring/namespace-inheritance-cycle.md) error class. ## Source diff --git a/docs/skills/cgp/SKILL.md b/docs/skills/cgp/SKILL.md index ad99c126..e5e4c023 100644 --- a/docs/skills/cgp/SKILL.md +++ b/docs/skills/cgp/SKILL.md @@ -872,6 +872,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, 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/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 new file mode 100644 index 00000000..c7e51d22 --- /dev/null +++ b/docs/skills/cgp/references/error-extraction.md @@ -0,0 +1,55 @@ +# Extracting CGP compile errors + +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). + +## 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. + +## The two shapes to recognize + +Before decoding any nested type, read the *trait* in the error and decide which of two shapes you are looking at, because the shapes differ in whether the root cause is even present. The [macro-grammar](macro-grammar.md) decoder and the [checking](checking.md) playbook cover the full set; extraction needs the one binary distinction. + +A **surfaced** error carries the root cause. It is an `E0277` note chain, typically topped by `CanUseComponent` or `IsProviderFor`, that names a concrete missing bound such as `HasField`. This is what a [`check_components!`](checking.md) assertion produces, because the check requires `IsProviderFor` as a *direct* bound and forces the compiler to evaluate the provider's `where` clause. The concrete bound is the fact to extract — the compiler names it in a `help:`/"is not implemented" note, and the `required for …` notes trace the dependency path from it back to the check. + +A **hidden** error does not. It is an `E0599` "the method `greet` exists for struct `Person`, but its trait bounds were not satisfied", or an `E0277` that a consumer trait like `Person: CanGreet` is unsatisfied, and it names the consumer or provider trait and then stops — with no note descending to the missing field or dependency. This happens when broken wiring is exercised by *calling the consumer-trait method directly* rather than through a check: the compiler sees the consumer trait's blanket impl among the candidate impls, finds it inapplicable, and suppresses the nested bound that made it so. The cause is **absent**, not buried. Do not scan a hidden error for a root cause; there is none to find. The fix is to *promote* it into a surfaced error — add a `check_components!` for the failing component at the wiring site — and read that instead. + +## Role 1 — extracting an error yourself + +You are in this role whenever you are the one reading the raw output — as a sub-agent handed the job, or as the main agent facing an error short enough to read inline. Your product is the [compact summary](#reduce-to-the-compact-summary) below, never the raw dump. + +### Capture the output without flooding context + +Capture the compiler output to a file rather than letting it stream into your transcript, and target the smallest unit that reproduces the failure. A whole-workspace build multiplies the cascade across crates; a single crate, test, example, or ten-line scratch module shrinks it to the one thing you care about. The commands are just your project's ordinary build and test invocations — nothing CGP-specific: + +```bash +# Target the smallest failing unit and redirect everything to a scratch file. +cargo check -p 2>&1 | tee /tmp/cgp-error.txt + +# or a single test / example that exercises the failing wiring: +cargo test -p --test 2>&1 | tee /tmp/cgp-error.txt +``` + +Two details recur, both plain `rustc` behavior rather than anything CGP-specific. When a type in the error is elided as `...`, the compiler writes its full form to a file named in a final note (`the full name for the type has been written to '….long-type-….txt'`); that elided middle is frequently the one segment that reveals *which* context or path the error is about, so read that file when the cause hinges on a long type. And watch for the **near-contradiction** shape — "the trait `X` is not implemented for `T`" immediately followed by a `help:` note that `X` *is* implemented for `T` — which means an impl exists but a nested bound it carries does not hold, or two candidates are ambiguous; trust the error, not the `help:`. + +When the cause hinges on *what the macro emitted* rather than on the message itself, stop reading the error and read the expansion. `cargo expand` prints the macro-expanded source in any project; in a project set up with the CGP test utilities, the `snapshot_*!` helpers from `cgp-macro-test-util` additionally pin an expansion as a reviewable snapshot. The [macro-grammar](macro-grammar.md) skill covers how to read the expanded impls once you have them. + +### Reduce to the compact summary + +Reduce the output to the same few facts — the anatomy a CGP error catalog records — and nothing more: + +- **Class and code** — the error code(s) and the trait(s) named (`E0599` on a provider trait, `E0277` through `CanUseComponent`, `E0119` conflicting `DelegateComponent`, `E0207` unconstrained generic, and so on). +- **Hidden or surfaced** — whether the root cause is present in the output at all. +- **Root cause and position** — if surfaced, the concrete failing bound and where it sits (in the compiler's `help:` note, near the last or second-to-last block of a cascade, or inside the `long-type-….txt` file); if hidden, say so plainly. +- **Recommended next action** — fix the named field or wiring, promote a hidden error with a `check_components!`, break a cycle, remove a duplicate key — whatever the class implies. + +A good summary is a few lines, not a transcript. Never hand back the raw output; that defeats the purpose of extracting it. + +## Role 2 — delegating the extraction to a sub-agent + +You are in this role when you are the main agent and the error is long — a deep cascade, many crates, or output that runs to screens. Spawn a sub-agent to read it and return only the [compact summary](#reduce-to-the-compact-summary); the sub-agent absorbs the wall of text while you keep your own context clean. Give the sub-agent three things: the exact command to run (or the path to an already-captured output file), this skill so it knows the two shapes and the summary format, and the instruction to return *only* the summary. This is the intended workflow both when documenting an error class — where a sub-agent gathers the facts from a failing reproduction — and in an ordinary debugging session, where a sub-agent reads an error too long to justify reading inline. When the returned summary says the error is hidden, the follow-up is almost always to add a check and re-run, which itself may be worth delegating. + +## Further reference + +- Sibling skills: [checking](checking.md) for why wiring is lazy and how checks force a surfaced error; [macro-grammar](macro-grammar.md) for the full error decoder and how to read an expansion; [wiring](wiring.md) for the delegation mechanics behind a conflict or cycle. +- Online: the [error catalog](https://github.com/contextgeneric/cgp/tree/main/docs/errors) documents each error class and where its root cause sits, and the [debugging guide](https://github.com/contextgeneric/cgp/blob/main/docs/guides/debugging.md) is the full tracing playbook.