From c74e619863e325e2afbdb7fc92f6a1d7f5e81654 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Sun, 5 Jul 2026 10:40:31 +0200 Subject: [PATCH 1/6] Audit spans and add compile-fail tests --- .../cgp_auto_getter/option_slice.stderr | 8 +++ .../cgp_component/duplicate_component_name.rs | 30 ++++++++++ .../duplicate_component_name.stderr | 10 ++++ .../check_components/missing_dependency.rs | 59 +++++++++++++++++++ .../missing_dependency.stderr | 38 ++++++++++++ .../entrypoints/cgp_component.md | 7 +++ .../entrypoints/check_components.md | 3 + 7 files changed, 155 insertions(+) create mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/duplicate_component_name.rs create mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/duplicate_component_name.stderr create mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_dependency.rs create mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_dependency.stderr diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_auto_getter/option_slice.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_auto_getter/option_slice.stderr index 92398d5d..31e9aba3 100644 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_auto_getter/option_slice.stderr +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_auto_getter/option_slice.stderr @@ -7,6 +7,9 @@ error[E0277]: the size for values of type `[u8]` cannot be known at compilation = help: the trait `Sized` is not implemented for `[u8]` note: required by an implicit `Sized` bound in `Option` --> $RUST/core/src/option.rs + | + | pub enum Option { + | ^ required by the implicit `Sized` requirement on this type parameter in `Option` = note: this error originates in the attribute macro `cgp_auto_getter` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0599]: the method `as_ref` exists for reference `&Option<[u8]>`, but its trait bounds were not satisfied @@ -15,6 +18,11 @@ error[E0599]: the method `as_ref` exists for reference `&Option<[u8]>`, but its 17 | #[cgp_auto_getter] | ^^^^^^^^^^^^^^^^^^ method cannot be called on `&Option<[u8]>` due to unsatisfied trait bounds | + ::: $RUST/core/src/option.rs + | + | pub enum Option { + | ------------------ doesn't satisfy `Option<[u8]>: AsRef<_>` + | = note: the following trait bounds were not satisfied: `[u8]: Sized` `Option<[u8]>: AsRef<_>` 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 new file mode 100644 index 00000000..c513745a --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/duplicate_component_name.rs @@ -0,0 +1,30 @@ +//! Acceptable failure: `#[cgp_component(Greeter)]` derives a marker +//! `pub struct GreeterComponent;`, and this module also declares its own +//! `GreeterComponent`, so the name is defined twice (E0428). `#[cgp_component]` +//! expands without any view of the rest of the module, so it emits the derived +//! marker faithfully and lets the compiler report the clash — exactly as two +//! hand-written definitions would. +//! +//! This fixture pins the span of the *derived* `#[cgp_component]` marker. The +//! E0428 "previous definition of the type `GreeterComponent` here" note falls on +//! the `Greeter` provider name the user wrote inside `#[cgp_component(…)]`, not on +//! the whole attribute, because the derived marker struct ident is emitted with +//! the provider identifier's own span (see +//! cgp-macro-core/src/types/cgp_component/args/component_args.rs). A regression +//! that stamped the marker with `Span::call_site()` would move that note onto the +//! whole `#[cgp_component(..)]` attribute — the leak the span fix removed so that +//! cross-crate go-to-definition on the marker resolves to the provider name alone. +//! +//! See docs/implementation/entrypoints/cgp_component.md (Failure modes). + +use cgp::prelude::*; + +#[cgp_component(Greeter)] +pub trait CanGreet { + fn greet(&self); +} + +// Collides with the `GreeterComponent` marker derived from `Greeter` above. +pub struct GreeterComponent; + +fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/duplicate_component_name.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/duplicate_component_name.stderr new file mode 100644 index 00000000..e7ff36dc --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/duplicate_component_name.stderr @@ -0,0 +1,10 @@ +error[E0428]: the name `GreeterComponent` is defined multiple times + --> tests/acceptable/cgp_component/duplicate_component_name.rs:28:1 + | +22 | #[cgp_component(Greeter)] + | ------- previous definition of the type `GreeterComponent` here +... +28 | pub struct GreeterComponent; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `GreeterComponent` redefined here + | + = note: `GreeterComponent` must be defined only once in the type namespace of this module diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_dependency.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_dependency.rs new file mode 100644 index 00000000..8f3a2da2 --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_dependency.rs @@ -0,0 +1,59 @@ +//! Acceptable failure: `GreetHello` carries the impl-side dependency +//! `Self: HasName`, but `Person` has no `name` field, so it cannot satisfy it. +//! `check_components!` exists precisely to surface this at the wiring site rather +//! than lazily at the call site (contrast +//! acceptable/delegate_components/missing_dependency.rs, which leaves the same +//! wiring unchecked and hits the error only when `greet` is called). The failure +//! is the check doing its job, not a macro defect. +//! +//! This fixture pins the `check_components!` error span. The unsatisfied-bound +//! caret falls on `GreeterComponent` inside the `check_components!` block, not on +//! the `Person` context type, because the check impl re-spans the shared context +//! token onto each listed component in turn with `override_span` (see +//! cgp-macro-core/src/types/check_components/table.rs). A regression that dropped +//! that re-span would report the error on the single `Person` token shared by +//! every checked component instead of on the component that actually fails. +//! +//! See docs/implementation/entrypoints/check_components.md (Failure modes). + +use cgp::prelude::*; + +#[cgp_component(Greeter)] +pub trait CanGreet { + fn greet(&self); +} + +#[cgp_auto_getter] +pub trait HasName { + fn name(&self) -> &str; +} + +#[cgp_impl(new GreetHello)] +impl Greeter +where + Self: HasName, +{ + fn greet(&self) { + let _ = self.name(); + } +} + +#[derive(HasField)] +pub struct Person { + pub age: u8, +} + +delegate_components! { + Person { + GreeterComponent: GreetHello, + } +} + +// `Person` cannot satisfy `GreetHello`'s `Self: HasName`, so the check fails here. +check_components! { + Person { + GreeterComponent, + } +} + +fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_dependency.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_dependency.stderr new file mode 100644 index 00000000..75cb10af --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_dependency.stderr @@ -0,0 +1,38 @@ +error[E0277]: the trait bound `Person: CanUseComponent` is not satisfied + --> tests/acceptable/check_components/missing_dependency.rs:55:9 + | +55 | GreeterComponent, + | ^^^^^^^^^^^^^^^^ unsatisfied trait bound + | +help: the trait `HasField>>>>>` is not implemented for `Person` + but trait `HasField>>>>` is implemented for it + --> tests/acceptable/check_components/missing_dependency.rs:41:10 + | +41 | #[derive(HasField)] + | ^^^^^^^^ +note: required for `Person` to implement `HasName` + --> tests/acceptable/check_components/missing_dependency.rs:26:1 + | +26 | #[cgp_auto_getter] + | ^^^^^^^^^^^^^^^^^^ +27 | pub trait HasName { + | ^^^^^^^ +note: required for `GreetHello` to implement `IsProviderFor` + --> tests/acceptable/check_components/missing_dependency.rs:31:1 + | +31 | #[cgp_impl(new GreetHello)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +34 | Self: HasName, + | ------- unsatisfied trait bound introduced here + = note: required for `Person` to implement `CanUseComponent` +note: required by a bound in `__CheckPerson` + --> tests/acceptable/check_components/missing_dependency.rs:53:1 + | +53 | / check_components! { +54 | | Person { +55 | | GreeterComponent, +56 | | } +57 | | } + | |_^ required by this bound in `__CheckPerson` + = note: this error originates in the derive macro `HasField` which comes from the expansion of the macro `check_components` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/docs/implementation/entrypoints/cgp_component.md b/docs/implementation/entrypoints/cgp_component.md index 86d4d1cd..3eb6d761 100644 --- a/docs/implementation/entrypoints/cgp_component.md +++ b/docs/implementation/entrypoints/cgp_component.md @@ -64,6 +64,12 @@ The **reserved identifiers** appear literally in the output: the context paramet The **marker struct's name span** is the provider identifier's span, not `Span::call_site()`. When the component name is not given explicitly, `CgpComponentArgs` derives it as `{Provider}Component` and spans that identifier on the provider identifier the user wrote in `#[cgp_component(Provider)]`. A `call_site` span would instead cover the whole `#[cgp_component(..)]` attribute, so the generated `struct {Provider}Component` would report its definition on a range that also contains the `cgp_component` macro path — and rust-analyzer go-to-definition on a delegate key naming the component would then offer both the component and the macro as targets. That misdirection appears only when navigating from another crate, where the editor relies on the recorded definition span rather than a live expansion; deriving the span from the provider identifier keeps the definition on a single user token, mirroring `derive_check_trait_ident`. See the [Spans note](../README.md#spans-aim-generated-items-at-the-token-the-user-wrote) for why span placement serves the editor as well as the compiler. +## 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). + ## Snapshots Every `snapshot_cgp_component!` invocation across the suite is indexed here, since these snapshots all belong to this entrypoint: @@ -84,6 +90,7 @@ The behavioral tests confirm the generated wiring works: - [generic_components/component_type_param.rs](../../../crates/tests/cgp-tests/tests/generic_components/component_type_param.rs) wires a single-type-parameter component to one provider, passes `check_components!` for a concrete type argument, and computes an area at run time. - [generic_components/component_lifetime.rs](../../../crates/tests/cgp-tests/tests/generic_components/component_lifetime.rs) wires the lifetime-carrying component and passes `check_components!`. - [cgp-macro-tests/tests/parser_rejections/cgp_component.rs](../../../crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_component.rs) asserts the macro rejects a non-trait item and a trait carrying a const generic parameter. +- [cgp-compile-fail-tests acceptable/cgp_component/duplicate_component_name.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/duplicate_component_name.rs) pins that the derived marker's `E0428` "previous definition" note falls on the provider name rather than the whole attribute — a regression test for the marker-name span (see [Failure modes](#failure-modes)). ## Source diff --git a/docs/implementation/entrypoints/check_components.md b/docs/implementation/entrypoints/check_components.md index 3859bc9e..04f9f9c0 100644 --- a/docs/implementation/entrypoints/check_components.md +++ b/docs/implementation/entrypoints/check_components.md @@ -50,6 +50,8 @@ 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 **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. @@ -73,6 +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)). ## Source From dd6a3ff7d34f59f44da1e80bc66c998b11113d3d Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Sun, 5 Jul 2026 15:01:20 +0200 Subject: [PATCH 2/6] Add namespace guide --- docs/{concepts => guides}/modern-idioms.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/{concepts => guides}/modern-idioms.md (100%) diff --git a/docs/concepts/modern-idioms.md b/docs/guides/modern-idioms.md similarity index 100% rename from docs/concepts/modern-idioms.md rename to docs/guides/modern-idioms.md From 5409cd91bed413c98e8e92768ca2c79ac929f211 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Sun, 5 Jul 2026 15:13:25 +0200 Subject: [PATCH 3/6] Update docs --- Cargo.lock | 1 + .../attributes/default_impl/attribute.rs | 12 + .../tests/cgp-compile-fail-tests/Cargo.toml | 1 + .../default_impl_foreign_prefix_path.rs | 36 ++ .../default_impl_foreign_prefix_path.stderr | 9 + .../cgp_namespace/override_registered_path.rs | 57 ++++ .../override_registered_path.stderr | 20 ++ crates/tests/cgp-test-crate-a/src/lib.rs | 9 + crates/tests/cgp-test-crate-b/src/lib.rs | 54 ++- .../tests/namespaces/default_impl_use_type.rs | 119 +++++++ .../tests/cgp-tests/tests/namespaces/mod.rs | 1 + docs/AGENTS.md | 6 + docs/README.md | 4 +- docs/concepts/README.md | 3 +- docs/guides/README.md | 26 ++ docs/guides/capability-supertraits.md | 34 ++ docs/guides/debugging.md | 63 ++++ docs/guides/declaring-dependencies.md | 40 +++ docs/guides/dispatching-per-type.md | 43 +++ docs/guides/importing-abstract-types.md | 42 +++ docs/guides/modern-idioms.md | 215 +----------- docs/guides/namespaces-and-prefixes.md | 314 ++++++++++++++++++ docs/guides/reading-context-fields.md | 46 +++ docs/guides/writing-providers.md | 70 ++++ docs/implementation/asts/attributes.md | 4 +- .../entrypoints/cgp_namespace.md | 22 +- docs/reference/macros/cgp_namespace.md | 4 + docs/reference/traits/default_namespace.md | 4 + docs/skills/cgp/references/namespaces.md | 4 +- 29 files changed, 1049 insertions(+), 214 deletions(-) create mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_prefix_path.rs create mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_prefix_path.stderr create mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/override_registered_path.rs create mode 100644 crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/override_registered_path.stderr create mode 100644 crates/tests/cgp-tests/tests/namespaces/default_impl_use_type.rs create mode 100644 docs/guides/README.md create mode 100644 docs/guides/capability-supertraits.md create mode 100644 docs/guides/debugging.md create mode 100644 docs/guides/declaring-dependencies.md create mode 100644 docs/guides/dispatching-per-type.md create mode 100644 docs/guides/importing-abstract-types.md create mode 100644 docs/guides/namespaces-and-prefixes.md create mode 100644 docs/guides/reading-context-fields.md create mode 100644 docs/guides/writing-providers.md diff --git a/Cargo.lock b/Cargo.lock index 5651571d..d2312dbc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -63,6 +63,7 @@ name = "cgp-compile-fail-tests" version = "0.8.0-alpha" dependencies = [ "cgp", + "cgp-test-crate-a", "trybuild", ] diff --git a/crates/macros/cgp-macro-core/src/types/attributes/default_impl/attribute.rs b/crates/macros/cgp-macro-core/src/types/attributes/default_impl/attribute.rs index e56e5fca..1dba807d 100644 --- a/crates/macros/cgp-macro-core/src/types/attributes/default_impl/attribute.rs +++ b/crates/macros/cgp-macro-core/src/types/attributes/default_impl/attribute.rs @@ -29,6 +29,18 @@ impl DefaultImplAttribute { .push(parse_internal!(__Components__)); let mut generics = provider_generics.clone(); + + // Drop the provider's impl-side dependencies. `provider_generics` is the + // provider impl's generics *after* `#[implicit]`/`#[uses]`/`#[use_type]`/ + // `#[use_provider]` have pushed their `Self`-keyed bounds into its `where` + // clause (e.g. `Self: HasErrorType`). Those belong on the provider's own + // impl and its `IsProviderFor`, never on this registration impl, whose only + // job is `type Delegate = Provider`. Left in place they would bind the + // registration impl's `Self` — the path key `PathCons<..>` — so a + // dependency like `Self: HasErrorType` would demand `PathCons<..>: + // HasErrorType` and never resolve. The impl carries only the parameters + // that name the key and provider, plus the `__Components__` table. + generics.where_clause = None; generics.params.push(parse_internal!(__Components__)); let (impl_generics, _, where_clause) = generics.split_for_impl(); diff --git a/crates/tests/cgp-compile-fail-tests/Cargo.toml b/crates/tests/cgp-compile-fail-tests/Cargo.toml index 932ed384..f6791abc 100644 --- a/crates/tests/cgp-compile-fail-tests/Cargo.toml +++ b/crates/tests/cgp-compile-fail-tests/Cargo.toml @@ -12,6 +12,7 @@ readme = "./README.md" [dependencies] cgp = { workspace = true } +cgp-test-crate-a = { workspace = true } [dev-dependencies] trybuild = { version = "1.0.117" } diff --git a/crates/tests/cgp-compile-fail-tests/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 new file mode 100644 index 00000000..00611806 --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_prefix_path.rs @@ -0,0 +1,36 @@ +//! Acceptable failure: a downstream crate cannot register a default for a +//! *prefixed* upstream component into the upstream namespace with `#[default_impl]`. +//! +//! `cgp-test-crate-a`'s `Announcer` carries `#[prefix(@app in DefaultNamespace)]`, +//! so its namespace key is the path `@app.AnnouncerComponent`. Registering a +//! default at that path in crate-a's `AppNamespace` expands to +//! `impl AppNamespace<_> for PathCons, PathCons>`, +//! whose trait (`AppNamespace`) and every element of the `Self` type (`PathCons` +//! and `Symbol` from `cgp`, `AnnouncerComponent` from crate-a) are all foreign to +//! this crate — an orphan-rule violation (E0117). A per-component default keyed on +//! a *prefix path* can therefore only be written in the crate that owns the +//! namespace; a downstream crate must own the key, which for a prefixed component +//! it does not. (The orphan-safe counterpart — a *local* component key registered +//! into a foreign namespace — is exercised in `cgp-test-crate-b`.) +//! +//! This is why `#[default_impl]` couples an implementation to the namespace's +//! crate and why the guide recommends namespace *body* entries for wiring that +//! must live downstream of the namespace. +//! +//! See docs/implementation/entrypoints/cgp_namespace.md (Failure modes). + +use cgp::prelude::*; +use cgp_test_crate_a::{Announcer, AnnouncerComponent, AppNamespace, HasName}; + +#[cgp_impl(new AnnounceQuietly)] +#[default_impl(@app.AnnouncerComponent in AppNamespace)] +impl Announcer +where + Self: HasName, +{ + fn announce(&self) -> String { + format!("(psst, {})", self.name()) + } +} + +fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_prefix_path.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_prefix_path.stderr new file mode 100644 index 00000000..5fc9529e --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_prefix_path.stderr @@ -0,0 +1,9 @@ +error[E0210]: type parameter `__Components__` must be used as the type parameter for some local type (e.g., `MyStruct<__Components__>`) + --> tests/acceptable/cgp_namespace/default_impl_foreign_prefix_path.rs:25:1 + | +25 | #[cgp_impl(new AnnounceQuietly)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type parameter `__Components__` must be used as the type parameter for some local type + | + = note: implementing a foreign trait is only possible if at least one of the types for which it is implemented is local + = note: only traits defined in the current crate can be implemented for a type parameter + = note: this error originates in the attribute macro `cgp_impl` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/override_registered_path.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/override_registered_path.rs new file mode 100644 index 00000000..e18701c8 --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/override_registered_path.rs @@ -0,0 +1,57 @@ +//! Acceptable failure: a context that joins a namespace with `namespace N;` +//! cannot also wire, directly on itself, a path that `N` already registers. +//! +//! `GreetHello` registers the path `@app.GreeterComponent` into `AppNamespace` +//! with `#[default_impl]`, so `PathCons` implements +//! `AppNamespace<_>`. The `namespace AppNamespace;` header then emits a blanket +//! `impl DelegateComponent for App where Key: AppNamespace`, which +//! already covers that path. The extra `@app.GreeterComponent: GreetBye` entry +//! emits a second `DelegateComponent> for App`, +//! and the two overlap — E0119. CGP lowers both entries faithfully; only the whole +//! program reveals the overlap, so it defers to the compiler. +//! +//! The rule this pins: override a component the namespace routes by shadowing its +//! *marker* only when the namespace does not itself terminate the redirect path, +//! or wire the override on a path the namespace never registers. A namespace that +//! registers the leaf path leaves nothing for the context to override there. +//! +//! See docs/implementation/entrypoints/cgp_namespace.md (Failure modes). + +use cgp::prelude::*; + +#[cgp_component(Greeter)] +#[prefix(@app in DefaultNamespace)] +pub trait CanGreet { + fn greet(&self) -> String; +} + +#[cgp_impl(new GreetHello)] +#[default_impl(@app.GreeterComponent in AppNamespace)] +impl Greeter { + fn greet(&self) -> String { + "Hello".to_owned() + } +} + +#[cgp_impl(new GreetBye)] +impl Greeter { + fn greet(&self) -> String { + "Bye".to_owned() + } +} + +cgp_namespace! { + new AppNamespace: DefaultNamespace {} +} + +pub struct App; + +delegate_components! { + App { + namespace AppNamespace; + + @app.GreeterComponent: GreetBye, + } +} + +fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/override_registered_path.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/override_registered_path.stderr new file mode 100644 index 00000000..356a93b5 --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/override_registered_path.stderr @@ -0,0 +1,20 @@ +error[E0119]: conflicting implementations of trait `IsProviderFor>>>, PathCons>, _, _>` for type `App` + --> tests/acceptable/cgp_namespace/override_registered_path.rs:53:14 + | +51 | namespace AppNamespace; + | ------------ first implementation here +52 | +53 | @app.GreeterComponent: GreetBye, + | ^^^^^^^^^^^^^^^^ conflicting implementation for `App` + | + = note: downstream crates may implement trait `cgp::prelude::IsProviderFor>>>, cgp::prelude::PathCons>, _, _>` for type `GreetHello` + = note: downstream crates may implement trait `cgp::prelude::IsProviderFor>>>, cgp::prelude::PathCons>, _, _>` for type `GreetBye` + +error[E0119]: conflicting implementations of trait `DelegateComponent>>>, PathCons>>` for type `App` + --> tests/acceptable/cgp_namespace/override_registered_path.rs:53:14 + | +51 | namespace AppNamespace; + | ------------ first implementation here +52 | +53 | @app.GreeterComponent: GreetBye, + | ^^^^^^^^^^^^^^^^ conflicting implementation for `App` diff --git a/crates/tests/cgp-test-crate-a/src/lib.rs b/crates/tests/cgp-test-crate-a/src/lib.rs index c7e406d1..70e49617 100644 --- a/crates/tests/cgp-test-crate-a/src/lib.rs +++ b/crates/tests/cgp-test-crate-a/src/lib.rs @@ -52,3 +52,12 @@ where format!("ANNOUNCEMENT from {}!", self.name()) } } + +// A shared namespace downstream crates populate and join. It inherits the +// built-in `DefaultNamespace`, so a context joining it also inherits the standard +// defaults. `cgp-test-crate-b` registers a *local* component into it with +// `#[default_impl]` — orphan-safe because the crate owns the component key even +// though it does not own this namespace. +cgp_namespace! { + new AppNamespace: DefaultNamespace {} +} diff --git a/crates/tests/cgp-test-crate-b/src/lib.rs b/crates/tests/cgp-test-crate-b/src/lib.rs index 0fb0639d..561b0255 100644 --- a/crates/tests/cgp-test-crate-b/src/lib.rs +++ b/crates/tests/cgp-test-crate-b/src/lib.rs @@ -1,18 +1,23 @@ //! Downstream crate for cross-crate CGP coherence tests. //! //! Everything here consumes the CGP surface defined in `cgp-test-crate-a`, -//! demonstrating three cross-crate abilities that Rust's coherence rules would +//! demonstrating four cross-crate abilities that Rust's coherence rules would //! otherwise make awkward: //! //! 1. wiring a foreign component to a foreign provider on a local context; //! 2. defining a *local* provider for a *foreign* provider trait (orphan-safe, //! because the provider struct is local) and wiring a context to it; -//! 3. participating in a namespace declared upstream. +//! 3. participating in a namespace declared upstream; +//! 4. registering a *local* component into an upstream namespace with +//! `#[default_impl]` (orphan-safe because the crate owns the component key). //! //! See crates/tests/AGENTS.md and docs/concepts/coherence.md. use cgp::prelude::*; -use cgp_test_crate_a::{AnnounceLoudly, AnnouncerComponent, GreetHello, Greeter, GreeterComponent}; +use cgp_test_crate_a::{ + AnnounceLoudly, AnnouncerComponent, AppNamespace, GreetHello, Greeter, GreeterComponent, + HasName, +}; /// (1) A local context wires the foreign `Greeter` component to the foreign /// `GreetHello` provider. `GreetHello` needs `HasName`, satisfied by the `name` @@ -67,12 +72,55 @@ delegate_components! { } } +/// (4) A *local* component and provider registered into crate-a's *foreign* +/// `AppNamespace` with `#[default_impl]`. This is orphan-safe because the key — +/// the local `FarewellComponent` — is owned by this crate, even though +/// `AppNamespace` is not: registering a per-component default needs the crate to +/// own either the namespace or the component key. (A `#[prefix]`-ed component, +/// whose key is a foreign `PathCons<..>` path rather than a local marker, could +/// only be registered from the crate that owns the namespace.) `Leaver` then joins +/// `AppNamespace` and resolves the farewell through it, with no direct wiring. +#[cgp_component(Farewell)] +pub trait CanFarewell { + fn farewell(&self) -> String; +} + +#[cgp_impl(new GoodbyeFarewell)] +#[default_impl(FarewellComponent in AppNamespace)] +impl Farewell +where + Self: HasName, +{ + fn farewell(&self) -> String { + format!("Goodbye, {}!", self.name()) + } +} + +#[derive(HasField)] +pub struct Leaver { + pub name: String, +} + +delegate_components! { + Leaver { + namespace AppNamespace; + } +} + #[cfg(test)] mod tests { use cgp_test_crate_a::{CanAnnounce, CanGreet}; use super::*; + #[test] + fn default_impl_into_upstream_namespace() { + let leaver = Leaver { + name: "John".to_owned(), + }; + assert_eq!(leaver.farewell(), "Goodbye, John!"); + } + #[test] fn wire_foreign_provider() { let person = Person { diff --git a/crates/tests/cgp-tests/tests/namespaces/default_impl_use_type.rs b/crates/tests/cgp-tests/tests/namespaces/default_impl_use_type.rs new file mode 100644 index 00000000..97dd7fbb --- /dev/null +++ b/crates/tests/cgp-tests/tests/namespaces/default_impl_use_type.rs @@ -0,0 +1,119 @@ +//! Regression: `#[default_impl]` on a provider that carries a `#[use_type]` +//! abstract-type dependency (and an `#[implicit]` field read). +//! +//! `#[default_impl]` builds its namespace-registration impl (`impl Namespace<..> +//! for PathKey { type Delegate = Provider; }`) from the provider impl's generics, +//! which by this stage already carry the impl-side bounds that `#[use_type]`, +//! `#[uses]`, `#[implicit]`, and `#[use_provider]` push onto `Self`. Those bounds +//! must be dropped: the registration impl's `Self` is the path key +//! (`PathCons<..>`), so a leaked `Self: HasNameType` would demand `PathCons<..>: +//! HasNameType` and never resolve, silently breaking every context that joins the +//! namespace. The snapshot pins that the registration impl carries only +//! `__Components__` and no `where` clause; the wiring below proves a context +//! resolves the provider (and its abstract-type dependency) through the namespace. +//! +//! See docs/implementation/asts/attributes.md (`#[default_impl]`) and +//! docs/reference/traits/default_namespace.md. + +use cgp::prelude::*; +use cgp_macro_test_util::snapshot_cgp_impl; + +#[cgp_type] +#[prefix(@app.types in DefaultNamespace)] +pub trait HasNameType { + type Name: core::fmt::Display; +} + +#[cgp_component(Greeter)] +#[prefix(@app.core in DefaultNamespace)] +#[use_type(HasNameType.Name)] +pub trait CanGreet { + fn greet(&self) -> String; +} + +snapshot_cgp_impl! { + #[cgp_impl(new GreetByName)] + #[default_impl(@app.core.GreeterComponent in AppNamespace)] + #[use_type(HasNameType.Name)] + impl Greeter { + fn greet(&self, #[implicit] name: &Name) -> String { + format!("Hello, {name}!") + } + } + + expand_greet_by_name(output) { + insta::assert_snapshot!(output, @r#" + impl<__Context__> Greeter<__Context__> for GreetByName + where + __Context__: HasField< + Symbol<4, Chars<'n', Chars<'a', Chars<'m', Chars<'e', Nil>>>>>, + Value = <__Context__ as HasNameType>::Name, + >, + __Context__: HasNameType, + { + fn greet(__context__: &__Context__) -> String { + let name: &<__Context__ as HasNameType>::Name = __context__ + .get_field( + ::core::marker::PhantomData::< + Symbol<4, Chars<'n', Chars<'a', Chars<'m', Chars<'e', Nil>>>>>, + >, + ); + format!("Hello, {name}!") + } + } + impl<__Context__> IsProviderFor for GreetByName + where + __Context__: HasField< + Symbol<4, Chars<'n', Chars<'a', Chars<'m', Chars<'e', Nil>>>>>, + Value = <__Context__ as HasNameType>::Name, + >, + __Context__: HasNameType, + {} + pub struct GreetByName; + impl<__Components__> AppNamespace<__Components__> + for PathCons< + Symbol<3, Chars<'a', Chars<'p', Chars<'p', Nil>>>>, + PathCons< + Symbol<4, Chars<'c', Chars<'o', Chars<'r', Chars<'e', Nil>>>>>, + PathCons, + >, + > { + type Delegate = GreetByName; + } + "#) + } +} + +cgp_namespace! { + new AppNamespace: DefaultNamespace { + @app.types.NameTypeProviderComponent: + UseType, + } +} + +#[derive(HasField)] +pub struct App { + pub name: String, +} + +delegate_components! { + App { + namespace AppNamespace; + } +} + +check_components! { + App { + NameTypeProviderComponent, + GreeterComponent, + } +} + +#[test] +fn test_default_impl_use_type() { + let app = App { + name: "World".to_owned(), + }; + + assert_eq!(app.greet(), "Hello, World!"); +} diff --git a/crates/tests/cgp-tests/tests/namespaces/mod.rs b/crates/tests/cgp-tests/tests/namespaces/mod.rs index ca68c9e6..d362862f 100644 --- a/crates/tests/cgp-tests/tests/namespaces/mod.rs +++ b/crates/tests/cgp-tests/tests/namespaces/mod.rs @@ -26,6 +26,7 @@ pub mod redirect_lookup; // `#[prefix]`, and `#[default_impl]` snapshots); the `*_wiring` modules consume // them from sibling modules to exercise the `for <..> in ..` loop, `DefaultImpls1`, // and namespace inheritance in `delegate_components!`. +pub mod default_impl_use_type; pub mod default_impls; pub mod default_impls_wiring; pub mod extended; diff --git a/docs/AGENTS.md b/docs/AGENTS.md index 044947fd..c9312aaa 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -36,6 +36,12 @@ Follow the shape of the existing examples. Open with a level-one heading naming When an example would demonstrate a concept the reference does not yet cover, document the concept where it belongs rather than explaining it inside the example. Add the missing detail to the relevant reference document, or — when the concept is a cross-cutting idea that ties several constructs together — add a new page under [concepts/](concepts/) and register it in the [concepts index](concepts/README.md). The example then links to that documentation like any other, keeping the example itself focused on the use case rather than on teaching a new construct. +## The guides directory + +The [guides/](guides/) directory holds prescriptive guides to *writing* CGP — documents that direct the choices an author makes when several constructs could express the same thing. A guide is not a reference and not a concept: where a reference states what a construct means and a concept explains an idea, a guide recommends a default form, names the trade-offs of the alternatives, and usually walks a concrete before/after refactoring to ground the recommendation in real code. The migration from explicit to modern idioms and the use of namespaces to shrink wiring tables are the current guides; [guides/README.md](guides/README.md) is the catalog you register a new one in. + +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 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 5ff84944..58b2b146 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 three top-level sections, and will grow to contain more as the need arises. +The knowledge base is divided into four 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. @@ -18,6 +18,8 @@ The [concepts/](concepts/README.md) directory holds the cross-cutting conceptual The [examples/](examples/README.md) directory holds self-contained worked examples, one realistic use case developed end to end per document, from its contexts and components through to the wiring that connects them. The examples are the canonical source of code snippets the reference and concept documents reuse, so the same running scenarios recur across the whole knowledge base. +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. The migration from explicit to modern idioms and the use of namespaces to keep wiring tables short both live here. + ## 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/concepts/README.md b/docs/concepts/README.md index 271a19eb..aba7bbcd 100644 --- a/docs/concepts/README.md +++ b/docs/concepts/README.md @@ -4,7 +4,7 @@ This directory holds the high-level conceptual overviews that tie the CGP constr ## How concepts differ from reference documents and examples -A concept document explains an *idea that spans several constructs*, whereas a [reference document](../reference/README.md) explains a *single construct completely* and an [example](../examples/README.md) develops a *single use case end to end*. The three are complementary. The reference for `delegate_components!` states its syntax and exact expansion; the [coherence](coherence.md) concept explains *why* CGP wires components through such a table at all, and the [modular serialization](../examples/modular-serialization.md) example shows the pattern solving a real problem. A concept document carries only enough mechanism to make the idea legible and links to the reference for the rest, so a reader who wants the detail follows the link while a reader who wants the framing stays here. +A concept document explains an *idea that spans several constructs*, whereas a [reference document](../reference/README.md) explains a *single construct completely* and an [example](../examples/README.md) develops a *single use case end to end*. A fourth section, [guides](../guides/README.md), is different again: a concept explains *what an idea is*, while a guide directs *which choice to make* when writing CGP code. These are complementary. The reference for `delegate_components!` states its syntax and exact expansion; the [coherence](coherence.md) concept explains *why* CGP wires components through such a table at all, and the [modular serialization](../examples/modular-serialization.md) example shows the pattern solving a real problem. A concept document carries only enough mechanism to make the idea legible and links to the reference for the rest, so a reader who wants the detail follows the link while a reader who wants the framing stays here. ## The catalog @@ -12,7 +12,6 @@ The authoring rules for concept documents, including when a cross-cutting idea e - [Bypassing coherence](coherence.md) — what Rust's coherence rules forbid, and the incoherent-impl-plus-local-wiring strategy CGP uses to work around them. - [Modularity hierarchy](modularity-hierarchy.md) — the ladder from a single blanket impl to per-type-per-provider wiring, and how to pick the lowest rung a use case needs. -- [Modern idioms: a migration guide](modern-idioms.md) — the preferred higher-level forms for providers, dependencies, abstract types, and dispatch, mapped from the explicit forms they replace. - [Consumer and provider traits](consumer-and-provider-traits.md) — the trait duality at the heart of CGP and how it sidesteps coherence. - [Impl-side dependencies](impl-side-dependencies.md) — dependency injection through the `where` clause of blanket impls. - [Implicit arguments](implicit-arguments.md) — writing providers as ordinary functions whose arguments come from context fields. diff --git a/docs/guides/README.md b/docs/guides/README.md new file mode 100644 index 00000000..b42dcb67 --- /dev/null +++ b/docs/guides/README.md @@ -0,0 +1,26 @@ +# CGP Guides + +This directory holds the *guides* to writing Context-Generic Programming code — documents that direct the **choices** an author makes, rather than explaining what a construct is. Where the [reference](../reference/README.md) tells you what a construct means and the [concepts](../concepts/README.md) explain the ideas that tie constructs together, a guide answers the question that comes up once you already understand the pieces: *given several ways to express something, which should I use, and how do I evolve code from one form to another?* + +## How guides differ from concepts, reference, and examples + +The four sections answer four different questions, and a reader in a hurry can pick the one that matches their need. A [reference document](../reference/README.md) answers "what does `#[prefix]` mean and what does it expand to?" A [concept document](../concepts/README.md) answers "what is a namespace, as an idea?" An [example](../examples/README.md) answers "show me a namespace solving a real problem end to end." A **guide** answers "my `delegate_components!` table has grown unwieldy — what should I do about it, and in what order?" A guide is prescriptive: it recommends a default, names the trade-offs of the alternatives, and often walks a concrete before/after refactoring so the recommendation is grounded in real code rather than stated in the abstract. + +A guide leans on the other three rather than restating them. It links to the reference for the exact syntax of each construct it recommends, to the concepts for the mechanism behind a recommendation, and to the examples for a fuller worked scenario. Keep the guide focused on the decision and the migration path; when a guide finds itself explaining what a construct *is* at length, that explanation belongs in the reference or a concept, linked from the guide. + +## The catalog + +The authoring rules for these documents live in [../AGENTS.md](../AGENTS.md). Each guide below names a decision you face when writing CGP and walks through how to make it. + +- [Organizing wiring with namespaces and prefixes](namespaces-and-prefixes.md) — how to keep a growing `delegate_components!` table short: grouping components under path prefixes with `#[prefix]`, binding providers to a namespace with `#[default_impl]`, and merging multiple providers into one flattened table, worked as a refactoring of a real application. +- [Debugging CGP compile errors](debugging.md) — the playbook for tracing a wiring failure back to its cause: reading the error's shape, moving the error to the wiring site with checks, reducing to a minimal reproduction, inspecting the macro expansion, and a decoder for the errors you actually see. + +The **modern idioms** are a family of small, related choices — each a shift from an explicit form to a vanilla-looking one — so they are grouped under a hub with a focused guide per idiom. Start at the hub for the framing and the map, or go straight to the idiom you are deciding on: + +- [Modern idioms: a migration guide](modern-idioms.md) — the hub, with the explicit-to-modern framing and the list of cases where an explicit form is still right. +- [Writing providers the modern way](writing-providers.md) — `#[cgp_impl]` in consumer-trait shape, omitting the context parameter. +- [Declaring a provider's dependencies](declaring-dependencies.md) — `#[uses]` and `#[use_provider]` instead of hand-written `where` bounds. +- [Reading context fields](reading-context-fields.md) — `#[implicit]` arguments instead of getter traits. +- [Importing abstract types](importing-abstract-types.md) — `#[use_type]` aliases and the concrete-type equality form. +- [Adding capability supertraits](capability-supertraits.md) — `#[extend]` instead of native `:` supertrait syntax. +- [Dispatching a component per type](dispatching-per-type.md) — the `open` statement or a namespace instead of a `UseDelegate` table. diff --git a/docs/guides/capability-supertraits.md b/docs/guides/capability-supertraits.md new file mode 100644 index 00000000..376f129a --- /dev/null +++ b/docs/guides/capability-supertraits.md @@ -0,0 +1,34 @@ +# Adding capability supertraits + +A CGP component often depends on another capability, and this guide is about declaring that dependency with `#[extend]`, which reads as importing a capability, rather than the native `:` supertrait syntax, which reads as inheritance. + +This is one of the [modern idioms](modern-idioms.md); it is the capability counterpart to [importing abstract types](importing-abstract-types.md), which handles a supertrait whose *type* the signature names. + +## Add supertraits with `#[extend]`, not native `:` syntax + +Add a non-type capability supertrait to a [`#[cgp_component]`](../reference/macros/cgp_component.md) trait with [`#[extend(...)]`](../reference/attributes/extend.md), rather than writing the native `pub trait CanDoX: Supertrait` form. Both produce the same trait with the same supertrait, but the attribute reads as an import — a capability the trait re-exports — which matches how CGP actually uses supertraits: as declared dependencies, not as a base class. Native `:` supertrait syntax tends to read as inheritance to programmers coming from object-oriented languages, suggesting an is-a relationship to a parent that a CGP component does not have. `#[extend(...)]` avoids that misreading and pairs symmetrically with [`#[uses(...)]`](declaring-dependencies.md): `#[uses]` imports a capability for the implementation's private use, `#[extend]` re-exports one as part of the trait's public contract. The native form: + +```rust +#[cgp_component(Greeter)] +pub trait CanGreet: HasName { + fn greet(&self) -> String; +} +``` + +becomes: + +```rust +#[cgp_component(Greeter)] +#[extend(HasName)] +pub trait CanGreet { + fn greet(&self) -> String; +} +``` + +`#[extend]` is the tool for a supertrait that contributes only a *capability* — like `HasName` here, which `CanGreet` depends on but whose value it reads through the getter rather than naming an abstract type in the signature. When the supertrait is instead an **abstract-type component** whose associated type the signature does name, use [`#[use_type]`](importing-abstract-types.md) instead: `#[use_type]` adds the supertrait *and* rewrites the bare type, which `#[extend]` does not, so it is the recommended form for abstract-type components. In [`#[cgp_fn]`](../reference/macros/cgp_fn.md), whose `where` clauses are impl-side dependencies rather than supertraits, `#[extend]` is the only way to declare a supertrait at all. + +## Related guides + +- [Importing abstract types](importing-abstract-types.md) — use `#[use_type]` instead when the supertrait carries an associated type the signature names. +- [Declaring dependencies](declaring-dependencies.md) — the `#[uses]` counterpart for a capability the implementation uses privately rather than re-exporting. +- [Modern idioms](modern-idioms.md) — the overview that ties the idioms together. diff --git a/docs/guides/debugging.md b/docs/guides/debugging.md new file mode 100644 index 00000000..cd896bd9 --- /dev/null +++ b/docs/guides/debugging.md @@ -0,0 +1,63 @@ +# Debugging CGP compile errors + +A broken CGP wiring rarely fails where it is wrong — it fails wherever the wiring is finally used, often as a wall of errors naming deeply nested types; this guide is the playbook for tracing such an error back to its cause. + +## Why CGP errors are shaped the way they are + +The first thing to internalize is that CGP wiring is resolved *lazily*, so a single missing or wrong entry does not error at the entry — it errors at every place that transitively needs it. A context assembles its behavior from a table of [`DelegateComponent`](../reference/traits/delegate_component.md) entries, and the compiler only checks that a provider's dependencies are met when something actually calls the consumer trait. One unsatisfied dependency deep in the graph therefore surfaces as a cascade of failures on unrelated-looking lines, and the *count* of errors tells you nothing about the number of mistakes — a dozen errors are usually one broken link seen from a dozen angles. Fix the first root cause and the cascade collapses. + +The second thing is that the errors quote *generated* code, so they name types you never wrote. A component's wiring runs through [`IsProviderFor`](../reference/traits/is_provider_for.md), `DelegateComponent`, [`CanUseComponent`](../reference/traits/can_use_component.md), `RedirectLookup`, and the type-level `PathCons`/`Symbol`/`Chars` spines, and a failure prints these in full. The nested types are noise until you learn to read past them to the one trait and the one context that actually failed. The techniques below are all ways to cut through that noise: read the error's shape, move the error to where the mistake is, or shrink the program until the mistake is the only thing left. + +## Read the error's shape before its contents + +Most CGP errors are one of a few shapes, and recognizing the shape tells you what kind of mistake you are looking for before you decode a single nested type. Learn to read the *trait* in the error and ignore the type arguments on a first pass. + +An unsatisfied **`IsProviderFor`** bound means "this provider is not a valid provider for this component, because one of its own dependencies is missing." The macros attach `IsProviderFor` to every provider under the same `where` clause the provider needs, precisely so that a missing transitive requirement is named here rather than swallowed. When you see `SomeProvider: IsProviderFor` is not satisfied, read it as "`MyContext` is missing something `SomeProvider` needs to implement `Foo`," and look at the provider's dependencies for the one the context does not supply. + +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. + +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. + +## Move the error to where the mistake is with checks + +Because a lazy failure surfaces far from its cause, the most reliable first move is to force the check *at the wiring site* with [`check_components!`](../reference/macros/check_components.md). A standalone check trait asserts `CanUseComponent` for each component you name, so a missing dependency errors on the checked component's line — walking through `IsProviderFor` to name the real cause — instead of at some distant call site. Adding a check for the component you suspect turns a confusing downstream error into one anchored at the wiring you control. The [check-traits concept](../concepts/check-traits.md) explains why this works. + +For a [higher-order provider](../concepts/higher-order-providers.md) — a provider wrapping another provider — a plain check tells you the stack is broken but not which layer. The `#[check_providers(...)]` attribute changes the assertion from `CanUseComponent` on the context to `IsProviderFor` on each named provider, so a dependency missing only from the outer wrapper errors on its line alone while one missing from the inner provider errors on both, pinpointing the layer. When a component has generic parameters, check it with concrete ones (`FooComponent: (Rectangle, f64)`) so the check actually instantiates the wiring you doubt rather than leaving it generic and unchecked. + +## Reduce the failure to a minimal reproduction + +The single most effective technique, and the one to reach for before theorizing, is to reproduce the symptom in the smallest self-contained program you can. A large wiring cross-cuts, so a mistake in it produces entangled errors; the same mistake in a ten-line module produces one. Copy the failing construct into a fresh test file, strip everything the symptom does not need, and confirm it still fails. If it stops failing as you strip, the last thing you removed is implicated; if it keeps failing, you now have a tractable case to reason about. + +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. + +## 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. + +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. + +## Bisect a wiring table + +For a large `delegate_components!` or `cgp_namespace!` table that fails as a whole, bisect it. Comment out entries until it compiles, then add them back a few at a time; the entry that reintroduces the failure is the culprit, or interacts with one that is. Because the entries are mostly independent, this converges quickly, and it works even when the error message points nowhere useful. The same approach applies to a context that joins a namespace *and* adds its own entries: reduce the context to just the `namespace N;` join first to confirm the namespace alone resolves, then add the `for` loops and overrides back one at a time to find which addition conflicts. + +## A decoder for the errors you will actually see + +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. +- **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 + +- [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. +- [`#[cgp_namespace]`](../reference/macros/cgp_namespace.md) and [`DefaultNamespace`](../reference/traits/default_namespace.md) — the namespace-specific conflicts and orphan restrictions the decoder above references. diff --git a/docs/guides/declaring-dependencies.md b/docs/guides/declaring-dependencies.md new file mode 100644 index 00000000..a1b98380 --- /dev/null +++ b/docs/guides/declaring-dependencies.md @@ -0,0 +1,40 @@ +# Declaring a provider's dependencies + +A provider states what it needs from its context in its `where` clause, and this guide is about writing those needs as attributes that read like imports rather than as hand-written trait bounds. + +This is one of the [modern idioms](modern-idioms.md); it follows on from [writing the provider header](writing-providers.md), which leaves the bounds off the header for these attributes to supply. + +## Declare dependencies with `#[uses]` and `#[use_provider]` + +State a provider's impl-side dependencies with [`#[uses(...)]`](../reference/attributes/uses.md) and [`#[use_provider(...)]`](../reference/attributes/use_provider.md) rather than hand-written `where` clauses, so a dependency reads like a `use` import instead of a trait bound. A capability the body calls on the context is imported with `#[uses]`: writing `#[uses(CanCalculateArea)]` adds `Self: CanCalculateArea` to the generated impl. An inner provider a [higher-order provider](../concepts/higher-order-providers.md) delegates to is declared with `#[use_provider]`: writing `#[use_provider(InnerCalculator: AreaCalculator)]` adds the bound `InnerCalculator: AreaCalculator`, filling in the `` argument that a provider trait inserts. The legacy `where` forms: + +```rust +#[cgp_impl(new ScaledArea)] +impl AreaCalculator for Context +where + Self: HasField, + InnerCalculator: AreaCalculator, +{ + fn area(&self) -> f64 { /* ... */ } +} +``` + +become: + +```rust +#[cgp_impl(new ScaledArea)] +#[use_provider(InnerCalculator: AreaCalculator)] +impl AreaCalculator { + fn area(&self, #[implicit] scale_factor: f64) -> f64 { /* ... */ } +} +``` + +Both attributes desugar to the same `where` predicates they replace. When a provider imports several capabilities or binds several inner providers, list them all in one attribute separated by commas — `#[uses(CanTransferMoney, CanRaiseHttpError)]`, `#[use_provider(A: TraitA, B: TraitB)]` — rather than stacking the same attribute repeatedly; one combined attribute reads as a single dependency list. + +`#[uses(...)]` accepts any bound a `where` clause allows, including one with associated-type equality, though the simple `Trait` form is the idiomatic one. One case moves elsewhere: when a bound pins an *abstract type* — `Self: HasErrorType` — express it with the [`#[use_type]` equality form](importing-abstract-types.md) rather than spelling the equality in `#[uses]` or a hand-written `where`. Only equality on a trait that is not a `#[use_type]` import (`Iterator`) stays an explicit `where` clause, where it reads more clearly than crammed into an import-shaped attribute. + +## Related guides + +- [Writing providers](writing-providers.md) — the `#[cgp_impl]` header these attributes attach to. +- [Importing abstract types](importing-abstract-types.md) — where an abstract-type pin belongs instead of `#[uses]`. +- [Modern idioms](modern-idioms.md) — the overview and the list of cases where an explicit `where` clause is still right. diff --git a/docs/guides/dispatching-per-type.md b/docs/guides/dispatching-per-type.md new file mode 100644 index 00000000..8d291b1b --- /dev/null +++ b/docs/guides/dispatching-per-type.md @@ -0,0 +1,43 @@ +# Dispatching a component per type + +A component that is generic over a type parameter often wants a different provider per value of that parameter, and this guide is about doing that with the `open` statement or a namespace rather than the legacy `UseDelegate` nested table. + +This is one of the [modern idioms](modern-idioms.md), and it connects directly to [organizing wiring with namespaces and prefixes](namespaces-and-prefixes.md), which develops the namespace side of the choice in depth. + +## Dispatch per type with `open` and namespaces, not `UseDelegate` + +Route a generic-parameter component to a different provider per type with the [`open` statement](../reference/macros/delegate_components.md) or a [namespace](namespaces-and-prefixes.md), rather than the legacy [`UseDelegate`](../reference/providers/use_delegate.md) nested-table pattern. Both the `open` statement and namespaces dispatch through the `RedirectLookup` impl that every [`#[cgp_component]`](../reference/macros/cgp_component.md) already generates, so they store the per-type entries directly on the context and need no wrapper type. The legacy form nests a `UseDelegate` table: + +```rust +delegate_components! { + MyApp { + AreaCalculatorComponent: + UseDelegate, + } +} +``` + +while the modern form dispatches inline with `open`: + +```rust +delegate_components! { + MyApp { + open AreaCalculatorComponent; + + @AreaCalculatorComponent.Rectangle: RectangleArea, + @AreaCalculatorComponent.Circle: CircleArea, + } +} +``` + +Because `open` and namespaces ride `RedirectLookup`, **a new component you intend to dispatch this way does not need the [`#[derive_delegate(UseDelegate)]`](../reference/attributes/derive_delegate.md) attribute at all** — that attribute exists only to generate the `UseDelegate` provider the legacy nested-table form relies on. You will still see `#[derive_delegate]` on some CGP-shipped components, such as the error and handler families, which carry it so existing `UseDelegate`-based wiring keeps working; but code that dispatches only through `open` or a namespace can omit it. + +Choose between the two modern forms by scope. Prefer `open` for a self-contained context wiring its own components directly — it folds the per-type entries into the context's own table with no separate type. Reach for a [namespace](namespaces-and-prefixes.md) when a reusable, inheritable dispatch table is worth sharing across contexts, or when a single generic component is served by several providers whose per-type entries you want to merge into one flat table. + +## Related guides + +- [Organizing wiring with namespaces and prefixes](namespaces-and-prefixes.md) — the full namespace treatment, including flattening multi-provider dispatch that `open` alone cannot. +- [Modern idioms](modern-idioms.md) — the overview that ties the idioms together. diff --git a/docs/guides/importing-abstract-types.md b/docs/guides/importing-abstract-types.md new file mode 100644 index 00000000..752c8bb6 --- /dev/null +++ b/docs/guides/importing-abstract-types.md @@ -0,0 +1,42 @@ +# Importing abstract types + +CGP abstracts over types with associated types on components, and this guide is about bringing such a type into a definition as a plain alias rather than a supertrait plus a fully-qualified `Self::Type` at every use. + +This is one of the [modern idioms](modern-idioms.md). It applies inside [`#[cgp_component]`](../reference/macros/cgp_component.md) definitions and [`#[cgp_impl]`](../reference/macros/cgp_impl.md)/[`#[cgp_fn]`](../reference/macros/cgp_fn.md) providers alike, and it is the recommended form for the built-in error type as much as for a domain type. + +## Import abstract types with `#[use_type]` + +Bring an abstract type into a definition with [`#[use_type]`](../reference/attributes/use_type.md) and write it as a bare alias, rather than declaring the owning trait as a supertrait and qualifying every use as `Self::Type`. The attribute does both jobs at once: `#[use_type(HasScalarType.Scalar)]` adds the trait as a supertrait (on a `#[cgp_component]`) or a `where` bound (on a `#[cgp_impl]`/`#[cgp_fn]`), and rewrites each bare `Scalar` to `::Scalar`. This is the preferred form even for the built-in error type: the legacy component definition + +```rust +#[cgp_component(Loader)] +pub trait CanLoad: HasErrorType { + fn load(&self, path: &str) -> Result; +} +``` + +becomes + +```rust +#[cgp_component(Loader)] +#[use_type(HasErrorType.Error)] +pub trait CanLoad { + fn load(&self, path: &str) -> Result; +} +``` + +One rule bounds the rewrite: it fires only on the bare identifier of an *imported* type. A construct's own **local associated type always stays qualified as `Self::Assoc`** — a handler that declares `type Output` writes `Self::Output`, never a bare `Output`, because `Output` is the trait's own type rather than one imported from another trait. A mixed signature such as `Result` is therefore exactly right: the local `Self::Output` stays qualified while the imported foreign `Error` is written bare. + +When a definition imports types from several traits, combine them into one `#[use_type]` attribute by separating the trait paths with commas — `#[use_type(HasUserIdType.UserId, HasCurrencyType.Currency, HasErrorType.Error)]` — rather than stacking one attribute per trait; the combined form reads as a single import list. Several types from one trait use a braced list (`#[use_type(HasFooType.{Foo, Bar})]`). + +## Pinning an abstract type to a concrete one + +On a `#[cgp_impl]` or `#[cgp_fn]`, `#[use_type]` also *pins* an abstract type to a concrete one with the equality form `{Assoc = Type}`, which is the modern replacement for a hand-written `where Self: HasXType` clause. Writing `#[use_type(HasErrorType.{Error = AppError})]` emits `Self: HasErrorType` (and rewrites any bare `Error`), so a provider fixed to a concrete error type moves that pin out of its `where` clause and into the import. The right-hand side may name another imported alias to *unify* two abstract types — `#[use_type(HasPasswordType.Password, HasHashedPasswordType.{HashedPassword = Password})]` emits `Self: HasHashedPasswordType::Password>`. The equality form is rejected on `#[cgp_component]`, since a trait definition cannot carry the impl-side constraint it produces — this is the one place a pin stays in a hand-written `where` clause, and only for equality on a trait you would never `#[use_type]` from. + +When a capability supertrait has no associated type to import — a plain capability like `HasName` — add it with [`#[extend]`](capability-supertraits.md) rather than `#[use_type]`. Use `#[use_type]` when the signature names the trait's associated type; use `#[extend]` when it only calls the trait's methods. + +## Related guides + +- [Capability supertraits](capability-supertraits.md) — the companion for a supertrait that contributes a capability rather than a type. +- [Declaring dependencies](declaring-dependencies.md) — where an abstract-type pin moves *from* (a `#[uses]` or hand-written `where`). +- [Modern idioms](modern-idioms.md) — the overview and the local-associated-type exception restated among the other still-explicit cases. diff --git a/docs/guides/modern-idioms.md b/docs/guides/modern-idioms.md index 270c44c4..1c4266b3 100644 --- a/docs/guides/modern-idioms.md +++ b/docs/guides/modern-idioms.md @@ -1,215 +1,22 @@ # Modern idioms: a migration guide -CGP offers a set of newer, higher-level idioms for writing components, providers, and wiring that read much closer to ordinary Rust — and this guide maps each older, more explicit form to the modern one you should prefer. +CGP offers a set of newer, higher-level idioms for writing components, providers, and wiring that read much closer to ordinary Rust — and this guide is the hub that maps each older, more explicit form to the modern one you should prefer, linking to a focused guide for each shift. The explicit forms came first. Early CGP exposed the machinery directly: a provider was an inside-out `impl` of a provider trait, dependencies were spelled as `where` clauses, abstract types were pulled from supertraits and written in fully-qualified `::Type` form, and per-type dispatch went through a `UseDelegate` table. Those forms still work and are exactly what the macros desugar to, so you will keep reading them in generated code, in expansion documentation, and in existing codebases. The newer idioms exist to lower the barrier to entry: they let a provider look like an ordinary trait `impl`, a dependency look like a `use` import, and an abstract type look like a plain generic, so that a reader who knows Rust but not CGP can follow the code. **Prefer the modern idioms in all new code, and reach for an explicit form only when a construct genuinely cannot express the case.** -This guide is organized by the shift each idiom makes. The concepts it draws on are documented in full elsewhere: writing providers in [consumer and provider traits](consumer-and-provider-traits.md), dependency injection in [impl-side dependencies](impl-side-dependencies.md), field injection in [implicit arguments](implicit-arguments.md), abstract types in [abstract types](abstract-types.md), the provider-parameterized pattern in [higher-order providers](higher-order-providers.md), and per-type dispatch in [dispatching](dispatching.md) and [namespaces](namespaces.md). Each section below links to the reference document that owns the construct. +## The idioms, one guide each -## Write providers with `#[cgp_impl]`, not the raw provider forms +Each idiom is a single shift from an explicit form to a modern one, and each has its own guide with the before/after mapping, the rules that bound it, and worked examples. The concepts behind them are documented in full elsewhere: writing providers in [consumer and provider traits](../concepts/consumer-and-provider-traits.md), dependency injection in [impl-side dependencies](../concepts/impl-side-dependencies.md), field injection in [implicit arguments](../concepts/implicit-arguments.md), abstract types in [abstract types](../concepts/abstract-types.md), the provider-parameterized pattern in [higher-order providers](../concepts/higher-order-providers.md), and per-type dispatch in [dispatching](../concepts/dispatching.md) and [namespaces](../concepts/namespaces.md). -A provider should be written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md), which keeps `self`, `Self`, and the consumer method signatures, rather than with the lower-level [`#[cgp_provider]`](../reference/macros/cgp_provider.md) or [`#[cgp_new_provider]`](../reference/macros/cgp_new_provider.md), which require the inside-out provider-trait shape. The lower forms move the context into an explicit leading type parameter and force the method to take `context: &Context` instead of `&self`; `#[cgp_impl]` restores the familiar shape and performs that rewrite for you. The legacy form: - -```rust -#[cgp_new_provider] -impl AreaCalculator for RectangleArea -where - Context: HasField, - Context: HasField, -{ - fn area(context: &Context) -> f64 { - *context.get_field(PhantomData::) - * *context.get_field(PhantomData::) - } -} -``` - -becomes, with the modern idiom and [`#[implicit]`](../reference/attributes/implicit.md) arguments: - -```rust -#[cgp_impl(new RectangleArea)] -impl AreaCalculator { - fn area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { - width * height - } -} -``` - -`#[cgp_impl]` desugars back to `#[cgp_provider]`/`#[cgp_new_provider]`, so the raw forms are still what the reference documents show in their Expansion sections and what you read in generated code. Write the raw form yourself only when you specifically need the inside-out shape — for instance, to state a bound the sugar cannot express. - -## Omit the context parameter - -Inside a `#[cgp_impl]` block, prefer the unqualified `impl AreaCalculator` and let the macro insert the context parameter, rather than naming it explicitly as `impl AreaCalculator for Context`. Omitting `for Context` is what makes the provider read like an ordinary trait `impl`; the macro supplies a reserved context parameter and treats `self`/`Self` as the context. Write the context out by hand: - -```rust -#[cgp_impl(new RectangleArea)] -impl AreaCalculator for Context -where - Context: HasDimensions, -{ - fn area(&self) -> f64 { - self.width() * self.height() - } -} -``` - -only when you must name it — to bound it with a lifetime or higher-ranked bound the sugar cannot spell, or to refer to it by a readable name. Otherwise write the shorter form and use `#[uses(...)]` for the bound: - -```rust -#[cgp_impl(new RectangleArea)] -#[uses(HasDimensions)] -impl AreaCalculator { - fn area(&self) -> f64 { - self.width() * self.height() - } -} -``` - -## Declare dependencies with `#[uses]` and `#[use_provider]` - -State a provider's impl-side dependencies with [`#[uses(...)]`](../reference/attributes/uses.md) and [`#[use_provider(...)]`](../reference/attributes/use_provider.md) rather than hand-written `where` clauses, so a dependency reads like a `use` import instead of a trait bound. A capability the body calls on the context is imported with `#[uses]`: writing `#[uses(CanCalculateArea)]` adds `Self: CanCalculateArea` to the generated impl. An inner provider a higher-order provider delegates to is declared with `#[use_provider]`: writing `#[use_provider(InnerCalculator: AreaCalculator)]` adds the bound `InnerCalculator: AreaCalculator`, filling in the `` argument that a provider trait inserts. The legacy `where` forms: - -```rust -#[cgp_impl(new ScaledArea)] -impl AreaCalculator for Context -where - Self: HasField, - InnerCalculator: AreaCalculator, -{ - fn area(&self) -> f64 { /* ... */ } -} -``` - -become: - -```rust -#[cgp_impl(new ScaledArea)] -#[use_provider(InnerCalculator: AreaCalculator)] -impl AreaCalculator { - fn area(&self, #[implicit] scale_factor: f64) -> f64 { /* ... */ } -} -``` - -`#[uses(...)]` accepts any bound a `where` clause allows, including one with associated-type equality, though the simple `Trait` form is the idiomatic one. When that bound pins an *abstract type* — `Self: HasErrorType` — move it into the [`#[use_type]` equality form](#import-abstract-types-with-use_type) rather than spelling the equality in `#[uses]` or writing a hand-written `where`; only equality on a trait that is not a `#[use_type]` import (`Iterator`) stays an explicit `where` clause. Both attributes desugar to the same `where` predicates they replace. When a provider imports several capabilities or binds several inner providers, list them all in one attribute separated by commas — `#[uses(CanTransferMoney, CanRaiseHttpError)]`, `#[use_provider(A: TraitA, B: TraitB)]` — rather than stacking the same attribute repeatedly; one combined attribute reads as a single dependency list. - -## Read context fields with implicit arguments, not getter traits - -Read a value from a context field with an [`#[implicit]`](../reference/attributes/implicit.md) argument — in a [`#[cgp_impl]`](../reference/macros/cgp_impl.md) provider method just as in a [`#[cgp_fn]`](../reference/macros/cgp_fn.md) — rather than declaring a getter trait with [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md). An implicit argument names both a local variable and the field it is read from, so the field access reads like an ordinary parameter and the `HasField` machinery stays out of sight — the same shift the provider idioms make, applied to values. This is the default way to pull a field into a provider, and it covers the great majority of field reads: a value used throughout a body is bound once at the top and used freely thereafter, and a value shared across several methods is simply declared as an implicit argument on each. The getter-trait version pairs a `#[cgp_auto_getter]` declaration with a `#[uses(...)]` import: - -```rust -#[cgp_auto_getter] -pub trait HasDimensions { - fn width(&self) -> &f64; - fn height(&self) -> &f64; -} - -#[cgp_impl(new RectangleArea)] -#[uses(HasDimensions)] -impl AreaCalculator { - fn area(&self) -> f64 { - self.width() * self.height() - } -} -``` - -collapses to a provider that reads the two fields directly: - -```rust -#[cgp_impl(new RectangleArea)] -impl AreaCalculator { - fn area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { - width * height - } -} -``` - -Use `#[cgp_auto_getter]` sparingly — only where an implicit argument cannot reach the field. Because an implicit argument reads from the provider's own `self` and takes a plain `&T` by reference without cloning, it covers every same-context read, including a field several providers each consume. A getter trait earns its keep in the three cases an implicit argument cannot serve: a field that lives on a type *other* than the provider's context, so the getter is required as a `where` bound on that type (`Request: HasBasicAuthHeader`, with no `self` field to read); an accessor that must exist as a *named* capability other code depends on through `#[uses(HasName)]` or a supertrait; and a getter whose associated type is inferred from the field (`type Name; fn name(&self) -> &Self::Name;`) so the type stays abstract for callers. Both idioms desugar to the same `HasField` bounds and share the same access rules — `.clone()` for an owned value, `.as_str()` for a `&str`, a plain `&T` by reference — so the choice is only about whether an implicit argument can reach the value. - -Avoid [`#[cgp_getter]`](../reference/macros/cgp_getter.md) in ordinary code. It builds a full wireable component so the source field name can be chosen at wiring time through a [`UseField`](../reference/providers/use_field.md) provider, and that flexibility is reserved for the advanced case where you want full control over the context implementation — deciding per context which field a getter reads from, or supplying the value by means other than a same-named field. For the common case of reading a field, an implicit argument is the form to write, with `#[cgp_auto_getter]` held back for the getter-only cases above. - -## Import abstract types with `#[use_type]` - -Bring an abstract type into a definition with [`#[use_type]`](../reference/attributes/use_type.md) and write it as a bare alias, rather than declaring the owning trait as a supertrait and qualifying every use as `Self::Type`. The attribute does both jobs at once: `#[use_type(HasScalarType.Scalar)]` adds the trait as a supertrait (on a `#[cgp_component]`) or a `where` bound (on a `#[cgp_impl]`/`#[cgp_fn]`), and rewrites each bare `Scalar` to `::Scalar`. This is the preferred form even for the built-in error type: the legacy component definition - -```rust -#[cgp_component(Loader)] -pub trait CanLoad: HasErrorType { - fn load(&self, path: &str) -> Result; -} -``` - -becomes - -```rust -#[cgp_component(Loader)] -#[use_type(HasErrorType.Error)] -pub trait CanLoad { - fn load(&self, path: &str) -> Result; -} -``` - -One rule bounds the rewrite: it fires only on the bare identifier of an *imported* type. A construct's own **local associated type always stays qualified as `Self::Assoc`** — a handler that declares `type Output` writes `Self::Output`, never a bare `Output`, because `Output` is the trait's own type rather than one imported from another trait. A mixed signature such as `Result` is therefore exactly right: the local `Self::Output` stays qualified while the imported foreign `Error` is written bare. When a capability supertrait has no associated type to import, add it with [`#[extend]`](../reference/attributes/extend.md) rather than `#[use_type]`, as the next section describes. - -When a definition imports types from several traits, combine them into one `#[use_type]` attribute by separating the trait paths with commas — `#[use_type(HasUserIdType.UserId, HasCurrencyType.Currency, HasErrorType.Error)]` — rather than stacking one attribute per trait; the combined form reads as a single import list. Several types from one trait use a braced list (`#[use_type(HasFooType.{Foo, Bar})]`), and stacked attributes behave identically, for when a real reason calls for them. - -On a `#[cgp_impl]` or `#[cgp_fn]`, `#[use_type]` also *pins* an abstract type to a concrete one with the equality form `{Assoc = Type}`, which is the modern replacement for a hand-written `where Self: HasXType` clause. Writing `#[use_type(HasErrorType.{Error = AppError})]` emits `Self: HasErrorType` (and rewrites any bare `Error`), so a provider fixed to a concrete error type moves that pin out of its `where` clause and into the import. The right-hand side may name another imported alias to *unify* two abstract types — `#[use_type(HasPasswordType.Password, HasHashedPasswordType.{HashedPassword = Password})]` emits `Self: HasHashedPasswordType::Password>`. The equality form is rejected on `#[cgp_component]`, since a trait definition cannot carry the impl-side constraint it produces. - -## Add supertraits with `#[extend]`, not native `:` syntax - -Add a non-type capability supertrait to a [`#[cgp_component]`](../reference/macros/cgp_component.md) trait with [`#[extend(...)]`](../reference/attributes/extend.md), rather than writing the native `pub trait CanDoX: Supertrait` form. Both produce the same trait with the same supertrait, but the attribute reads as an import — a capability the trait re-exports — which matches how CGP actually uses supertraits: as declared dependencies, not as a base class. Native `:` supertrait syntax tends to read as inheritance to programmers coming from object-oriented languages, suggesting an is-a relationship to a parent that a CGP component does not have. `#[extend(...)]` avoids that misreading and pairs symmetrically with [`#[uses(...)]`](../reference/attributes/uses.md): `#[uses]` imports a capability for the implementation's private use, `#[extend]` re-exports one as part of the trait's public contract. The native form: - -```rust -#[cgp_component(Greeter)] -pub trait CanGreet: HasName { - fn greet(&self) -> String; -} -``` - -becomes: - -```rust -#[cgp_component(Greeter)] -#[extend(HasName)] -pub trait CanGreet { - fn greet(&self) -> String; -} -``` - -`#[extend]` is the tool for a supertrait that contributes only a *capability* — like `HasName` here, which `CanGreet` depends on but whose value it reads through the getter rather than naming an abstract type in the signature. When the supertrait is instead an **abstract-type component** whose associated type the signature does name, use [`#[use_type]`](../reference/attributes/use_type.md) instead, exactly as the previous section showed with `HasErrorType`: `#[use_type]` adds the supertrait *and* rewrites the bare type, which `#[extend]` does not, so it is the recommended form for abstract-type components. In [`#[cgp_fn]`](../reference/macros/cgp_fn.md), whose `where` clauses are impl-side dependencies, `#[extend]` is the only way to declare a supertrait at all. - -## Dispatch per type with `open` and namespaces, not `UseDelegate` - -Route a generic-parameter component to a different provider per type with the [`open` statement](../reference/macros/delegate_components.md) or a [namespace](namespaces.md), rather than the legacy [`UseDelegate`](../reference/providers/use_delegate.md) nested-table pattern. Both the `open` statement and namespaces dispatch through the `RedirectLookup` impl that every [`#[cgp_component]`](../reference/macros/cgp_component.md) already generates, so they store the per-type entries directly on the context and need no wrapper type. The legacy form nests a `UseDelegate` table: - -```rust -delegate_components! { - MyApp { - AreaCalculatorComponent: - UseDelegate, - } -} -``` - -while the modern form dispatches inline with `open`: - -```rust -delegate_components! { - MyApp { - open AreaCalculatorComponent; - - @AreaCalculatorComponent.Rectangle: RectangleArea, - @AreaCalculatorComponent.Circle: CircleArea, - } -} -``` - -Because `open` and namespaces ride `RedirectLookup`, **a new component you intend to dispatch this way does not need the [`#[derive_delegate(UseDelegate)]`](../reference/attributes/derive_delegate.md) attribute at all** — that attribute exists only to generate the `UseDelegate` provider the legacy nested-table form relies on. You will still see `#[derive_delegate]` on some CGP-shipped components, such as the error and handler families, which carry it so existing `UseDelegate`-based wiring keeps working; but code that dispatches only through `open` or a namespace can omit it. Prefer `open` for a self-contained context wiring its own components, and a [namespace](namespaces.md) when a reusable, inheritable dispatch table is worth sharing across contexts. +- [Writing providers the modern way](writing-providers.md) — write a provider with `#[cgp_impl]` in consumer-trait shape, omitting the context parameter, instead of the inside-out `#[cgp_provider]`/`#[cgp_new_provider]` forms. +- [Declaring a provider's dependencies](declaring-dependencies.md) — state impl-side dependencies with `#[uses]` and `#[use_provider]` rather than hand-written `where` bounds. +- [Reading context fields](reading-context-fields.md) — pull a field value in with an `#[implicit]` argument rather than declaring a getter trait. +- [Importing abstract types](importing-abstract-types.md) — bring an abstract type in with `#[use_type]` and write it as a bare alias, and pin one to a concrete type with the equality form, instead of a supertrait plus `Self::Type`. +- [Adding capability supertraits](capability-supertraits.md) — declare a capability supertrait with `#[extend]` rather than the native `:` inheritance syntax. +- [Dispatching a component per type](dispatching-per-type.md) — dispatch a generic-parameter component with the `open` statement or a namespace rather than a `UseDelegate` nested table. ## When the explicit forms are still right -A handful of cases genuinely need an explicit form, and reaching for one there is not a regression. Keep an explicit `where` clause for an associated-type-equality bound on a non-`#[use_type]` trait — `Iterator`, `From` — which reads more clearly as a `where` clause than in an import-shaped `#[uses]` (which does accept it); but not for one pinning an abstract type: `Self: HasErrorType` belongs in the [`#[use_type]` equality form](#import-abstract-types-with-use_type), not a hand-written `where`. Name the context explicitly, as `impl Trait for Context`, when you must attach a lifetime or higher-ranked bound the sugar cannot carry. Reach for [`#[cgp_getter]`](../reference/macros/cgp_getter.md) when you specifically want full control over which field a getter reads from, chosen per context at wiring time. Write a raw provider-trait `impl` when you need the inside-out shape directly, for instance a provider whose `Self` is a concrete context rather than a generic one. And keep a local associated type qualified as `Self::Output` always — it is never a `#[use_type]` import. In every other case, the modern idiom is the one to write. +A handful of cases genuinely need an explicit form, and reaching for one there is not a regression. Keep an explicit `where` clause for an associated-type-equality bound on a non-`#[use_type]` trait — `Iterator`, `From` — which reads more clearly as a `where` clause than in an import-shaped `#[uses]` (which does accept it); but not for one pinning an abstract type: `Self: HasErrorType` belongs in the [`#[use_type]` equality form](importing-abstract-types.md), not a hand-written `where`. Name the context explicitly, as `impl Trait for Context`, when you must attach a lifetime or higher-ranked bound the sugar cannot carry. Reach for [`#[cgp_getter]`](../reference/macros/cgp_getter.md) when you specifically want full control over which field a getter reads from, chosen per context at wiring time. Write a raw provider-trait `impl` when you need the inside-out shape directly, for instance a provider whose `Self` is a concrete context rather than a generic one. And keep a local associated type qualified as `Self::Output` always — it is never a `#[use_type]` import. In every other case, the modern idiom is the one to write. -Further reference: the per-construct mechanics live in the reference documents linked above; [modularity hierarchy](modularity-hierarchy.md) frames how much CGP a problem needs, and [consumer and provider traits](consumer-and-provider-traits.md) explains the duality the provider idioms rest on. +Further reference: the per-construct mechanics live in the reference documents linked from each guide above; [modularity hierarchy](../concepts/modularity-hierarchy.md) frames how much CGP a problem needs, and [consumer and provider traits](../concepts/consumer-and-provider-traits.md) explains the duality the provider idioms rest on. diff --git a/docs/guides/namespaces-and-prefixes.md b/docs/guides/namespaces-and-prefixes.md new file mode 100644 index 00000000..fdef1e67 --- /dev/null +++ b/docs/guides/namespaces-and-prefixes.md @@ -0,0 +1,314 @@ +# Organizing wiring with namespaces and prefixes + +A context's [`delegate_components!`](../reference/macros/delegate_components.md) table grows one entry at a time until it is the hardest thing in the codebase to read; this guide shows how to shrink it back down with path prefixes, namespaces, and per-type defaults, worked as a refactoring of a real application. + +## The problem: a wiring table that outgrows its reader + +The first thing a newcomer sees when they open an application's context module is its wiring table, and by the time the application does anything interesting that table is long enough to overwhelm them. Each component the application uses adds a line, each abstract type adds a line, and each per-type dispatch adds several. The table is mechanically correct and every entry is doing real work, but there is no structure to hold onto — it reads as one flat list of thirty unrelated facts, and a reader cannot tell at a glance which entries belong together or which are the ones they came to change. + +Consider a small money-transfer web service. It has an authentication layer, a finance layer, an HTTP error-mapping layer, and an API layer, wired onto a single `MockApp` context whose backend is an in-memory mock. Written out entry by entry, its table looks like this: + +```rust +delegate_components! { + MockApp { + open { + HttpErrorRaiserComponent, + ApiHandlerComponent, + }; + + ErrorTypeProviderComponent: UseType, + + @HttpErrorRaiserComponent. Code.String: + DisplayHttpError, + @HttpErrorRaiserComponent. Code.anyhow::Error: + HandleHttpErrorWithAnyhow, + + [ + UserIdTypeProviderComponent, + PasswordTypeProviderComponent, + HashedPasswordTypeProviderComponent, + ]: + UseType, + QuantityTypeProviderComponent: + UseType, + CurrencyTypeProviderComponent: + UseType, + + [ + PasswordCheckerComponent, + UserHashedPasswordQuerierComponent, + UserBalanceQuerierComponent, + ]: + UseMockedApp, + MoneyTransferrerComponent: + NoTransferToSelf, + + @ApiHandlerComponent.QueryBalanceApi: + HandleFromRequest< + AxumQueryBalanceRequest, + ResponseToJson>>, + >, + @ApiHandlerComponent.TransferApi: + HandleFromRequest< + AxumTransferRequest, + UseBasicAuth>, + >, + } +} +``` + +Every line here is necessary, and nothing about it is wrong. The problem is purely one of presentation: the table mixes error handling, abstract types, business logic, and API routing with no visible seam between them, and a second context that shared most of this wiring would have to copy the whole block. The rest of this guide refactors this exact table down to a handful of lines, introducing one technique at a time. The three techniques compose, and each is useful on its own, so you can stop at whichever level of organization your application needs. + +## Technique 1: group components under path prefixes with `#[prefix]` + +The first move is to give each component a **path** — a dotted address like `@app.auth` or `@app.finance` — so that related components sort together instead of scattering through the table. The [`#[prefix(@path in Namespace)]`](../reference/macros/cgp_namespace.md) attribute on a component's [`#[cgp_component]`](../reference/macros/cgp_component.md) (or [`#[cgp_type]`](../reference/macros/cgp_type.md)) trait registers that component into a namespace under a path prefix, so that from then on the component is addressed by its path rather than by its bare marker name. The standard namespace to register into is the built-in [`DefaultNamespace`](../reference/traits/default_namespace.md), which every context can join. + +The abstract types divide cleanly by the layer that owns them, so they take a `types` sub-path under each layer: + +```rust +#[cgp_type] +#[prefix(@app.auth.types in DefaultNamespace)] +pub trait HasUserIdType { + type UserId: Display; +} + +#[cgp_type] +#[prefix(@app.finance.types in DefaultNamespace)] +pub trait HasQuantityType { + type Quantity: Display; +} +``` + +The capability components take the layer path directly, without the `types` segment, so the authentication logic sits under `@app.auth` and the finance logic under `@app.finance`: + +```rust +#[cgp_component(PasswordChecker)] +#[prefix(@app.auth in DefaultNamespace)] +#[use_type(HasPasswordType.Password, HasHashedPasswordType.HashedPassword)] +pub trait CanCheckPassword { + fn check_password(password: &Password, hashed_password: &HashedPassword) -> bool; +} + +#[cgp_component(UserBalanceQuerier)] +#[prefix(@app.finance in DefaultNamespace)] +#[async_trait] +#[use_type(HasUserIdType.UserId, HasCurrencyType.Currency, HasQuantityType.Quantity, HasErrorType.Error)] +pub trait CanQueryUserBalance { + async fn query_user_balance(&self, user: &UserId, currency: &Currency) -> Result; +} +``` + +The HTTP error raiser and the API handler each get their own layer path, `@app.error` and `@app.api`. With every component prefixed, the application's whole namespace looks like a directory tree — `@app.auth.types`, `@app.auth`, `@app.finance.types`, `@app.finance`, `@app.error`, `@app.api` — and a reader can find the auth wiring without reading the finance wiring. + +### Choosing prefixes for implementations you have not written yet + +The prefix you choose depends less on how *this* application wires a component and more on whether a *different* implementation would ever wire it separately. The natural instinct is to group by the provider you happen to use: since one `UseType` serves all three auth types in this application, you might put them wherever is convenient. Resist collapsing distinctions that a future implementation would need. A component author picks prefixes for every implementation the component might ever have, not just the one in front of them, because the prefix is part of the component's public surface and is expensive to change once downstream code depends on it. + +The rule that follows is to **give components a separate sub-path whenever they are likely to need separate providers**, even when the current implementation happens to wire them the same way. The abstract types sit under `@app.auth.types` rather than sharing `@app.auth` with the logic, because a real backend would supply the auth *logic* (password checking, hashed-password lookup) very differently from how it supplies the auth *types* — the types are almost always plain `UseType` while the logic talks to a database. Keeping them on separate sub-paths means a production context can point `@app.auth` at a database provider while leaving `@app.auth.types` on the same concrete types, without either wiring disturbing the other. Grouping them together would have read fine today and forced them apart tomorrow. + +## Technique 2: bind providers to a namespace so the context just joins it + +Prefixes organize the table but do not shorten it — the context still names every path. The second technique lifts the wiring off the context entirely and into a reusable **namespace**, so that most contexts join the namespace with a single line and wire nothing directly. A namespace defined with [`cgp_namespace!`](../reference/macros/cgp_namespace.md) is a preset: a named table of default wirings a context inherits wholesale and then selectively overrides. (The [namespaces concept](../concepts/namespaces.md) explains the mechanism; this section is about how to *use* it to organize an application.) + +Define one namespace for the application's mock backend, inheriting `DefaultNamespace` so a context that joins it also inherits every standard default: + +```rust +cgp_namespace! { + new MockNamespace: DefaultNamespace { + @cgp.core.error.ErrorTypeProviderComponent: + UseType, + + @app.error.HttpErrorRaiserComponent. Code.String: + DisplayHttpError, + @app.error.HttpErrorRaiserComponent. Code.anyhow::Error: + HandleHttpErrorWithAnyhow, + + @app.auth.types.{ + UserIdTypeProviderComponent, + PasswordTypeProviderComponent, + HashedPasswordTypeProviderComponent, + }: + UseType, + @app.finance.types.QuantityTypeProviderComponent: + UseType, + @app.finance.types.CurrencyTypeProviderComponent: + UseType, + } +} +``` + +These entries are the wirings that have no [`#[cgp_impl]`](../reference/macros/cgp_impl.md) block of their own to attach an attribute to: the concrete error type, the HTTP error dispatch, and the abstract-type choices are all built from library providers like [`UseType`](../reference/providers/use_type.md), so they are written directly in the namespace **body**, keyed by their full paths. The body of a namespace accepts exactly the same key and value forms as `delegate_components!`, including grouped keys (`@app.auth.types.{A, B, C}`) and generic-parameter dispatch keys (`@app.error.HttpErrorRaiserComponent. Code.String`). + +### Registering a provider with `#[default_impl]` + +The application's business logic *does* have `#[cgp_impl]` blocks — the mock backend implements password checking, hashed-password lookup, and balance querying — so those providers register themselves into the namespace from their own definition, with the [`#[default_impl(@path in Namespace)]`](../reference/traits/default_namespace.md) attribute: + +```rust +#[cgp_impl(UseMockedApp)] +#[default_impl(@app.auth.UserHashedPasswordQuerierComponent in MockNamespace)] +#[use_type(HasUserIdType.UserId, HasHashedPasswordType.HashedPassword, HasErrorType.Error)] +impl UserHashedPasswordQuerier +where + UserId: Ord, + HashedPassword: Clone, +{ + async fn query_user_hashed_password( + &self, + user_id: &UserId, + #[implicit] user_passwords: &BTreeMap, + ) -> Result, Error> { + Ok(user_passwords.get(user_id).cloned()) + } +} +``` + +`#[default_impl]` emits one extra impl that maps the path `@app.auth.UserHashedPasswordQuerierComponent` to `UseMockedApp` inside `MockNamespace`, without changing the provider itself. The effect is that the wiring lives next to the implementation it wires, which is where a reader looks for it, rather than in a distant table. A context that joins `MockNamespace` now resolves `UserHashedPasswordQuerier` to `UseMockedApp` automatically. + +With the namespace carrying the whole backend, a context that wants the mock backend joins it with one statement: + +```rust +delegate_components! { + MockApp { + namespace MockNamespace; + } +} +``` + +Everything the earlier table spelled out by hand — the error type, the error dispatch, the abstract types, the auth and finance logic — now arrives through the namespace, and a second mock context would need only the same single line. + +### Keep `#[prefix]` and `#[default_impl]` in different namespaces + +A subtle but important rule governs which namespace each attribute names: **register a component's `#[prefix]` into a base namespace and its `#[default_impl]` into a namespace that inherits the base, never the same one.** In the example, every `#[prefix]` names `DefaultNamespace` while every `#[default_impl]` names `MockNamespace`, and `MockNamespace: DefaultNamespace` inherits the prefixes. This separation is not stylistic. A namespace's entry for a key, once defined, cannot be overridden — so if the prefixes and the mock defaults shared one namespace, a *second* backend (a production one, say) could not reuse the prefixes without also inheriting the mock's providers, and would have to redeclare every prefix from scratch. Splitting them lets the prefix layer be reused by any number of backend namespaces, each supplying its own `#[default_impl]` bindings on top of the shared prefixes. Put the prefixes in the base namespace that describes the application's *structure*, and put each backend's provider choices in an inheriting namespace that describes one *configuration*. + +## Technique 3: merge several namespaces in one table with a `for` loop + +The API handlers are the one part of the wiring that is neither a plain library provider nor a `#[cgp_impl]` block — each is a hand-assembled pipeline of combinators — and they describe the application's public API surface rather than one backend's choices. That makes them a good fit for a *separate* namespace that any backend can pull in. Define the API surface as its own table keyed by the API marker: + +```rust +cgp_namespace! { + new DefaultApiHandlers { + QueryBalanceApi: + HandleFromRequest< + AxumQueryBalanceRequest, + ResponseToJson>>, + >, + TransferApi: + HandleFromRequest< + AxumTransferRequest, + UseBasicAuth>, + >, + } +} +``` + +A context then pulls this table onto its own `ApiHandler` dispatch path with a `for` loop, which reads each entry of the named table and emits one mapping per entry: + +```rust +delegate_components! { + MockApp { + namespace MockNamespace; + + for in DefaultApiHandlers { + @app.api.ApiHandlerComponent.Key: Value, + } + } +} +``` + +The `for in DefaultApiHandlers` loop binds each `QueryBalanceApi`/`TransferApi` entry as `Key` and its pipeline as `Value`, wiring `MockApp`'s `ApiHandler` for that API to that pipeline. This is how a single table draws on **more than one** namespace at once: `MockApp` joins `MockNamespace` for its backend *and* loops over `DefaultApiHandlers` for its API surface, keeping the two concerns in separate reusable tables while merging them onto one context. + +One constraint shapes how the loop key is written: **the loop's bound key must appear inside a path, never as the whole key.** Writing `Key: Value` on its own would collide with the general `DelegateComponent` impl that the `namespace` statement already generates for every key; embedding it in a path as `@app.api.ApiHandlerComponent.Key` keeps it distinct. This is also why the loop is the natural tool for a component with a generic parameter — the API marker *is* the dispatch parameter of `ApiHandlerComponent`, so each looped entry lands on that component's per-type dispatch path. + +## The payoff, and overriding through a namespace + +The three techniques together turn the thirty-line opening table into four lines, and the difference is not just length — it is that each remaining line now states a *decision* rather than a mechanical fact: + +```rust +delegate_components! { + MockApp { + namespace MockNamespace; + + for in DefaultApiHandlers { + @app.api.ApiHandlerComponent.Key: Value, + } + + @app.finance.MoneyTransferrerComponent: + NoTransferToSelf, + } +} +``` + +`MockApp` uses the mock backend, serves the default API surface, and — the one place it departs from the defaults — wraps money transfers in a `NoTransferToSelf` guard that rejects a transfer whose sender and recipient are the same account. That last entry is an **override**, and overriding through a namespace follows a rule worth stating outright: **a context can only wire a path that the namespace it joins does not itself register.** `MockNamespace` deliberately does *not* register `@app.finance.MoneyTransferrerComponent` — the base `MoneyTransferrer` provider is used only as the inner handler of the `NoTransferToSelf` wrapper, never registered on its own — so the context is free to wire that path directly. Had the namespace registered a provider at that exact path, the context's entry and the namespace's blanket forwarding would both implement `DelegateComponent` for that key, and the compiler would reject the overlap. To leave a path open for a context to override, route the component through the namespace but terminate the redirect on the context, not in the namespace. + +## Limitations: why `#[default_impl]` is for the basic case + +`#[default_impl]` is the most convenient of these tools and also the most constrained, and knowing where it stops keeps you from designing around it and then hitting a wall. Its central limitation is that **a `#[default_impl]` must live in the same crate as the namespace it registers into, whenever the component carries a prefix.** The attribute expands to `impl Namespace<_> for PathCons<..>`, and for a prefixed component that path is built entirely from the `cgp`-owned `PathCons`/`Symbol` types and the component's marker; Rust's orphan rule then accepts the impl only if the crate owns the `Namespace` trait. A downstream crate cannot register a default for an upstream prefixed component into an upstream namespace — the whole impl is foreign to it. + +The restriction relaxes only when no prefix is involved. For a component without a prefix, `#[default_impl(Component in Namespace)]` expands to `impl Namespace<_> for Component`, whose key is the component's own marker; a downstream crate that owns that marker can register it into a foreign namespace, because the marker is a local type. So a crate may register a default when it owns *either* the namespace or the un-prefixed component key — but a prefixed component's key is a foreign path, leaving only the "owns the namespace" option. + +Two consequences follow for how you reach for the tool. First, `#[default_impl]` couples an implementation to the namespace's crate, so splitting an application into finer crates eventually forces the provider, the component, and the namespace together in ways that reduce modularity — the opposite of what CGP's crate split is for. Second, because a namespace entry cannot be overridden once set, a `#[default_impl]` bakes in a choice that inheriting namespaces cannot revise. For these reasons **`#[default_impl]` is best seen as the tool for the basic case** — an application still written in a single crate, as a newcomer naturally starts — where it earns its keep by letting them add CGP wiring gradually, keeping their impls looking almost exactly like ordinary trait impls, and deferring the full wiring tables until later. When an application outgrows a single crate, move the affected wirings from `#[default_impl]` attributes into namespace **body** entries, which have no such crate restriction because the namespace's own crate writes them. + +## Advanced: flattening multi-provider dispatch into one table + +The deepest payoff of namespace paths appears when a single generic-parameter component is served by *several* providers depending on its parameter, and the providers live in different crates. The traditional way to dispatch such a component is a nested [`UseDelegate`](../reference/providers/use_delegate.md) table per provider, which forces the wiring into a tree of inner tables. Namespace paths let you flatten that tree into one table, because every entry is addressed by a full path and so entries for the same component but different providers can sit side by side. + +A handler component dispatched on a code parameter shows the shape. Rather than one `UseDelegate` table wrapping all handlers, each group of codes is mapped to its provider by a path with the code inline, and the groups for different providers coexist in one namespace: + +```rust +cgp_namespace! { + new AppNamespace: DefaultNamespace { + @cgp.extra.handler.HandlerComponent.[ + BytesToString, + ConvertTo, + Pipe, + ]: + BaseHandlerProvider, + + @cgp.extra.handler.HandlerComponent.[ + ReadFile, + StreamToBytes, + ]: + FileHandlerProvider, + + @cgp.extra.handler.HandlerComponent.[ + HttpRequest, + ]: + HttpHandlerProvider, + } +} +``` + +Each `@cgp.extra.handler.HandlerComponent.[..]` block maps a set of code types — some carrying their own generic parameters, bound with the inline `` form — to one provider, and the three blocks for three providers read as one flat list of "which provider handles which codes." The equivalent `UseDelegate` wiring would nest three inner tables inside an outer dispatch, and merging a fourth provider's codes would mean editing the tree; here it is one more block. Flattening this way is the pattern to reach for once a component is dispatched across providers that live in separate crates, since each crate can contribute its block to a shared namespace by owning the block's provider, without any crate needing to own the others' entries. + +### Collapsing a whole subtree with nested groups + +When one provider serves *many* components spread across several sub-paths, the path grouping nests, so a single entry can map an entire subtree to one provider. A `.{ … }` group after a path segment holds a comma-separated list of continuations, each of which is itself a path that may carry its own `.[ … ]` code list or another `.{ … }` group. Mapping every component that a single reqwest-based provider handles — some under a `core` sub-path, some under a `reqwest` sub-path, several dispatched on their own code parameters — collapses to one entry: + +```rust +@app.{ + core.{ + HttpMethodTypeProviderComponent, + UrlTypeProviderComponent, + MethodArgExtractorComponent.[GetMethod, PostMethod], + }, + reqwest.RequestBuilderUpdaterComponent.[ + WithHeaders, + Header, + ], +}: + ReqwestProvider, +``` + +The `@app.{ core.{ … }, reqwest.… }` shape reads as a directory listing: everything under `core` and everything under `reqwest` named here resolves to `ReqwestProvider`, with the leaf `.[ … ]` lists dispatching the generic-parameter components on their codes. Written out flat, this is seven separate `@app.core.…` and `@app.reqwest.…` entries; nested, it is one. Reach for nesting when a provider owns a cohesive slice of the namespace — it keeps the "one provider, these components" relationship on a single entry instead of scattering it down the table. + +## Choosing how far to go + +The three techniques form a ladder, and most applications should climb only as far as they need. Reach for `#[prefix]` as soon as a table has enough components that grouping helps a reader — it costs nothing and pays off immediately in navigability, and it is the one technique with no downstream restriction. Add a namespace with `#[default_impl]` once two contexts would share most of their wiring, or once a newcomer wants their impls to carry their own wiring instead of a central table, accepting that this ties the wiring to one crate. Split wiring across several namespaces with `for` loops, and flatten multi-provider dispatch into paths, when the application is large enough that different concerns — a backend, an API surface, a set of handlers — genuinely deserve their own reusable tables. Stop at the rung that makes the wiring clear; the goal is a table a reader can hold in their head, not the maximum use of the machinery. + +## Related documentation + +- [`#[cgp_namespace]`](../reference/macros/cgp_namespace.md) — the full syntax of defining a namespace, the `namespace`/`for … in` statements, and the `#[prefix]` attribute. +- [`DefaultNamespace`, `DefaultImpls1`, `DefaultImpls2`](../reference/traits/default_namespace.md) — the lookup traits behind namespaces and the `#[default_impl]` attribute. +- [`delegate_components!`](../reference/macros/delegate_components.md) — the wiring table these techniques restructure, including the `open` statement for the self-contained case. +- [Namespaces](../concepts/namespaces.md) — the mechanism (inheritance, `RedirectLookup`, and paths) that makes the preset pattern work. +- [Modern idioms: a migration guide](modern-idioms.md) — the companion guide covering the provider, dependency, and abstract-type idioms the code above uses. diff --git a/docs/guides/reading-context-fields.md b/docs/guides/reading-context-fields.md new file mode 100644 index 00000000..971cea58 --- /dev/null +++ b/docs/guides/reading-context-fields.md @@ -0,0 +1,46 @@ +# Reading context fields + +A provider reads values from its context's fields, and this guide is about doing that with an argument that looks like an ordinary parameter rather than a getter trait declared just to fetch it. + +This is one of the [modern idioms](modern-idioms.md); it is the value-level counterpart to [writing providers](writing-providers.md) in vanilla-looking form. + +## Read context fields with implicit arguments, not getter traits + +Read a value from a context field with an [`#[implicit]`](../reference/attributes/implicit.md) argument — in a [`#[cgp_impl]`](../reference/macros/cgp_impl.md) provider method just as in a [`#[cgp_fn]`](../reference/macros/cgp_fn.md) — rather than declaring a getter trait with [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md). An implicit argument names both a local variable and the field it is read from, so the field access reads like an ordinary parameter and the `HasField` machinery stays out of sight. This is the default way to pull a field into a provider, and it covers the great majority of field reads: a value used throughout a body is bound once at the top, and a value shared across several methods is simply declared as an implicit argument on each. The getter-trait version pairs a `#[cgp_auto_getter]` declaration with a `#[uses(...)]` import: + +```rust +#[cgp_auto_getter] +pub trait HasDimensions { + fn width(&self) -> &f64; + fn height(&self) -> &f64; +} + +#[cgp_impl(new RectangleArea)] +#[uses(HasDimensions)] +impl AreaCalculator { + fn area(&self) -> f64 { + self.width() * self.height() + } +} +``` + +collapses to a provider that reads the two fields directly: + +```rust +#[cgp_impl(new RectangleArea)] +impl AreaCalculator { + fn area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { + width * height + } +} +``` + +Use `#[cgp_auto_getter]` sparingly — only where an implicit argument cannot reach the field. Because an implicit argument reads from the provider's own `self` and takes a plain `&T` by reference without cloning, it covers every same-context read, including a field several providers each consume. A getter trait earns its keep in the three cases an implicit argument cannot serve: a field that lives on a type *other* than the provider's context, so the getter is required as a `where` bound on that type (`Request: HasBasicAuthHeader`, with no `self` field to read); an accessor that must exist as a *named* capability other code depends on through `#[uses(HasName)]` or a supertrait; and a getter whose associated type is inferred from the field (`type Name; fn name(&self) -> &Self::Name;`) so the type stays abstract for callers. Both idioms desugar to the same `HasField` bounds and share the same access rules — `.clone()` for an owned value, `.as_str()` for a `&str`, a plain `&T` by reference — so the choice is only about whether an implicit argument can reach the value. + +Avoid [`#[cgp_getter]`](../reference/macros/cgp_getter.md) in ordinary code. It builds a full wireable component so the source field name can be chosen at wiring time through a [`UseField`](../reference/providers/use_field.md) provider, and that flexibility is reserved for the advanced case where you want full control over the context implementation — deciding per context which field a getter reads from, or supplying the value by means other than a same-named field. For the common case of reading a field, an implicit argument is the form to write, with `#[cgp_auto_getter]` held back for the getter-only cases above. + +## Related guides + +- [Writing providers](writing-providers.md) — the `#[cgp_impl]` provider these arguments live in. +- [Importing abstract types](importing-abstract-types.md) — for a getter whose return type is an abstract type shared across contexts. +- [Modern idioms](modern-idioms.md) — the overview and the cases where a getter trait is still the right tool. diff --git a/docs/guides/writing-providers.md b/docs/guides/writing-providers.md new file mode 100644 index 00000000..b7b6f008 --- /dev/null +++ b/docs/guides/writing-providers.md @@ -0,0 +1,70 @@ +# Writing providers the modern way + +A provider can be written at three levels of sugar over the same machinery, and this guide is about choosing the highest one — writing a provider that reads like an ordinary trait `impl` rather than the inside-out provider-trait form the macros desugar to. + +This is one of the [modern idioms](modern-idioms.md); it pairs with [declaring a provider's dependencies](declaring-dependencies.md) and [reading its context's fields](reading-context-fields.md), which cover what goes inside the provider once its header is written this way. + +## Write providers with `#[cgp_impl]`, not the raw provider forms + +Write a provider with [`#[cgp_impl]`](../reference/macros/cgp_impl.md), which keeps `self`, `Self`, and the consumer method signatures, rather than with the lower-level [`#[cgp_provider]`](../reference/macros/cgp_provider.md) or [`#[cgp_new_provider]`](../reference/macros/cgp_new_provider.md), which require the inside-out provider-trait shape. The lower forms move the context into an explicit leading type parameter and force the method to take `context: &Context` instead of `&self`; `#[cgp_impl]` restores the familiar shape and performs that rewrite for you. The legacy form: + +```rust +#[cgp_new_provider] +impl AreaCalculator for RectangleArea +where + Context: HasField, + Context: HasField, +{ + fn area(context: &Context) -> f64 { + *context.get_field(PhantomData::) + * *context.get_field(PhantomData::) + } +} +``` + +becomes, with the modern idiom and [`#[implicit]`](../reference/attributes/implicit.md) arguments: + +```rust +#[cgp_impl(new RectangleArea)] +impl AreaCalculator { + fn area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { + width * height + } +} +``` + +`#[cgp_impl]` desugars back to `#[cgp_provider]`/`#[cgp_new_provider]`, so the raw forms are still what the reference documents show in their Expansion sections and what you read in generated code. Write the raw form yourself only when you specifically need the inside-out shape — for instance, to state a bound the sugar cannot express, or to implement a provider trait on a concrete context rather than a generic one. + +## Omit the context parameter + +Inside a `#[cgp_impl]` block, prefer the unqualified `impl AreaCalculator` and let the macro insert the context parameter, rather than naming it explicitly as `impl AreaCalculator for Context`. Omitting `for Context` is what makes the provider read like an ordinary trait `impl`; the macro supplies a reserved context parameter and treats `self`/`Self` as the context. Write the context out by hand: + +```rust +#[cgp_impl(new RectangleArea)] +impl AreaCalculator for Context +where + Context: HasDimensions, +{ + fn area(&self) -> f64 { + self.width() * self.height() + } +} +``` + +only when you must name it — to bound it with a lifetime or higher-ranked bound the sugar cannot spell, or to refer to it by a readable name. Otherwise write the shorter form and declare the bound with [`#[uses(...)]`](declaring-dependencies.md): + +```rust +#[cgp_impl(new RectangleArea)] +#[uses(HasDimensions)] +impl AreaCalculator { + fn area(&self) -> f64 { + self.width() * self.height() + } +} +``` + +## Related guides + +- [Declaring a provider's dependencies](declaring-dependencies.md) — state the `where` bounds this idiom leaves off the header with `#[uses]` and `#[use_provider]`. +- [Reading context fields](reading-context-fields.md) — pull field values into a provider with `#[implicit]` arguments, as the first example does. +- [Modern idioms](modern-idioms.md) — the overview that ties these idioms together and lists when an explicit form is still right. diff --git a/docs/implementation/asts/attributes.md b/docs/implementation/asts/attributes.md index f32663b6..13be1761 100644 --- a/docs/implementation/asts/attributes.md +++ b/docs/implementation/asts/attributes.md @@ -68,6 +68,8 @@ for PathCons>> The namespace path gains a trailing `__Components__` type argument and the impl generics gain a matching `__Components__` parameter, so the default is generic over any table the namespace is queried through. The host (`#[cgp_impl]`) collects it in `CgpImplAttributes` and emits one such impl per attribute after the provider impl, using the provider's own generics and provider type. +**The provider's `where` clause is deliberately dropped from this impl.** `to_item_impl` receives the provider impl's generics *after* `#[implicit]`/`#[uses]`/`#[use_type]`/`#[use_provider]` have pushed their `Self`-keyed impl-side bounds into it — a provider with `#[use_type(HasErrorType.Error)]`, for instance, arrives carrying `where Self: HasErrorType`. Those bounds belong on the provider's own impl and its `IsProviderFor`, never on this registration impl, whose only job is `type Delegate = Provider`. The registration impl's `Self` is the path key (`PathCons<..>`), so a retained `Self: HasErrorType` would demand `PathCons<..>: HasErrorType` — a bound that never holds — and silently break every context that joins the namespace. `to_item_impl` therefore clears `generics.where_clause` before splitting, keeping only the parameters that name the key and provider plus the `__Components__` table. (A provider whose *type* is generic, and whose parameter appears only in the `Delegate` associated type, would leave that parameter unconstrained, so a per-component default is written for a concrete provider.) + ## Tests The behavioral and snapshot tests that exercise each modifier are listed per attribute below; test and snapshot pointers for a construct live only in these implementation documents. @@ -78,7 +80,7 @@ The behavioral and snapshot tests that exercise each modifier are listed per att - **`#[extend]`** — [impl_side_dependencies/fn_extend.rs](../../../crates/tests/cgp-tests/tests/impl_side_dependencies/fn_extend.rs) pins the `#[cgp_fn]` supertrait form; [abstract_types/extend_component.rs](../../../crates/tests/cgp-tests/tests/abstract_types/extend_component.rs) and [abstract_types/use_type_fn_extend.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_extend.rs) exercise it on a component and alongside `#[use_type]`; [getters/abstract_type_extend.rs](../../../crates/tests/cgp-tests/tests/getters/abstract_type_extend.rs) uses it with a getter. - **`#[extend_where]`** — [abstract_types/use_type_fn_nested_foreign.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_nested_foreign.rs) exercises it alongside `#[use_type]` on a `#[cgp_fn]`. - **`#[derive_delegate]`** — [dispatching/use_delegate_getter.rs](../../../crates/tests/cgp-tests/tests/dispatching/use_delegate_getter.rs) wires a component defined with `#[derive_delegate]` through a `UseDelegate` table. -- **`#[default_impl]`** — [namespaces/default_impls.rs](../../../crates/tests/cgp-tests/tests/namespaces/default_impls.rs) pins the emitted namespace-default impl (`snapshot_cgp_impl!`), and [namespaces/default_impls_wiring.rs](../../../crates/tests/cgp-tests/tests/namespaces/default_impls_wiring.rs) checks a context picks up the default. +- **`#[default_impl]`** — [namespaces/default_impls.rs](../../../crates/tests/cgp-tests/tests/namespaces/default_impls.rs) pins the emitted namespace-default impl (`snapshot_cgp_impl!`), and [namespaces/default_impls_wiring.rs](../../../crates/tests/cgp-tests/tests/namespaces/default_impls_wiring.rs) checks a context picks up the default. [namespaces/default_impl_use_type.rs](../../../crates/tests/cgp-tests/tests/namespaces/default_impl_use_type.rs) pins that the registration impl carries no `where` clause when the provider has a `#[use_type]` dependency, and resolves such a provider through a context that joins the namespace; the cross-crate orphan restriction on a prefixed component's default is pinned in [acceptable/cgp_namespace/default_impl_foreign_prefix_path.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_prefix_path.rs). ## Source diff --git a/docs/implementation/entrypoints/cgp_namespace.md b/docs/implementation/entrypoints/cgp_namespace.md index af95c152..1c857680 100644 --- a/docs/implementation/entrypoints/cgp_namespace.md +++ b/docs/implementation/entrypoints/cgp_namespace.md @@ -88,6 +88,19 @@ cgp_namespace! { } ``` +**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: + +```rust +delegate_components! { + App { + namespace AppNamespace; // registers @app.GreeterComponent via a #[default_impl] + @app.GreeterComponent: GreetBye, // E0119: also implements DelegateComponent for that path + } +} +``` + +**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. + ## Snapshots Every `snapshot_cgp_namespace!` invocation across the suite is indexed here, since these snapshots all belong to this entrypoint: @@ -110,14 +123,21 @@ The behavioral tests confirm the generated namespaces wire correctly: - [namespaces/default_impls_wiring.rs](../../../crates/tests/cgp-tests/tests/namespaces/default_impls_wiring.rs) checks that a `DefaultNamespace`-based namespace supplies defaults a context can override. - [namespaces/multi_param_namespace.rs](../../../crates/tests/cgp-tests/tests/namespaces/multi_param_namespace.rs) and [namespaces/prefix_default_namespace.rs](../../../crates/tests/cgp-tests/tests/namespaces/prefix_default_namespace.rs) exercise parameterized namespaces and the `#[prefix(...)]` join that attaches a component to one. - [namespaces/for_where_clause.rs](../../../crates/tests/cgp-tests/tests/namespaces/for_where_clause.rs) pins the `for <..> in .. where ..` loop's `where` clause landing on each generated impl (a `snapshot_delegate_components!`, since `delegate_components!` owns the `for`-loop form). +- [namespaces/default_impl_use_type.rs](../../../crates/tests/cgp-tests/tests/namespaces/default_impl_use_type.rs) registers a provider with a `#[use_type]` abstract-type dependency into a namespace via `#[default_impl]` and resolves it through a context that joins the namespace. Its `snapshot_cgp_impl!` pins that the namespace-registration impl carries no `where` clause — the provider's impl-side bounds must not leak onto the registration impl's `Self` (the path key), or the abstract-type dependency would be demanded of the path rather than the context. See [asts/attributes.md](../asts/attributes.md) for the mechanism. + +The cross-crate coverage in `cgp-test-crate-b` confirms the orphan-safe direction of the default-impl coherence rule: + +- [cgp-test-crate-b/src/lib.rs](../../../crates/tests/cgp-test-crate-b/src/lib.rs) registers a *local* component into `cgp-test-crate-a`'s foreign `AppNamespace` with `#[default_impl(FarewellComponent in AppNamespace)]` — legal because the crate owns the component key — and resolves it through a local context that joins the namespace. 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 fixture in `cgp-compile-fail-tests` pins an accepted expansion that fails to compile — an **acceptable** failure 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, described under [Failure modes](#failure-modes): - [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. ## Source diff --git a/docs/reference/macros/cgp_namespace.md b/docs/reference/macros/cgp_namespace.md index ab0248d9..c5789262 100644 --- a/docs/reference/macros/cgp_namespace.md +++ b/docs/reference/macros/cgp_namespace.md @@ -210,6 +210,10 @@ Inheritance composes the same way at the namespace level: `ExtendedNamespace: De `cgp_namespace!` sits between component definitions and context wiring, so it relates to constructs on both sides. [`#[cgp_component]`](cgp_component.md) defines the components whose keys a namespace maps, and its [`#[prefix(...)]`](cgp_component.md) attribute is what registers a component into a namespace under a path. [`delegate_components!`](delegate_components.md) is where a context joins a namespace (via its `namespace` header) and where individual overrides are written; [`delegate_and_check_components!`](delegate_and_check_components.md) does the same and additionally checks the entries written directly in the block, though its derivation does not cover the components inherited through the namespace, so verifying the full merged wiring is left to a standalone [`check_components!`](check_components.md). The namespace's `Delegate` entries are resolved through [`RedirectLookup`](../providers/redirect_lookup.md), and per-type defaults are commonly expressed through [`use_delegate`](../providers/use_delegate.md)-style dispatch and the `DefaultNamespace` / `DefaultImpls1` traits in `cgp-component`. The underlying per-key table machinery is [`DelegateComponent`](../traits/delegate_component.md), which `RedirectLookup` walks at resolution time. +## 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. + ## Source - Entry point: `cgp_namespace` in [crates/macros/cgp-macro-lib/src/cgp_namespace.rs](../../../crates/macros/cgp-macro-lib/src/cgp_namespace.rs), which parses a `NamespaceTable` and calls `.eval()`. diff --git a/docs/reference/traits/default_namespace.md b/docs/reference/traits/default_namespace.md index 5dc49f58..6bf9442f 100644 --- a/docs/reference/traits/default_namespace.md +++ b/docs/reference/traits/default_namespace.md @@ -34,6 +34,10 @@ pub trait DefaultImpls2 { Each trait is implemented once per default entry, and resolving a default is reading `Delegate` from the matching impl. `DefaultNamespace` is implemented for a component-name key when a namespace supplies a default for that component regardless of any type parameter; the [`#[prefix(...)]`](../macros/cgp_namespace.md) attribute that attaches a component to a namespace emits exactly such an impl, with `Delegate` a [`RedirectLookup`](../providers/redirect_lookup.md) that re-routes the lookup along a path. `DefaultImpls1` is implemented for a component-name key carrying one instance type `T`, so the same component resolves per type; the `#[default_impl(T in DefaultImpls1)]` attribute on a provider impl (shown under Examples below) registers the provider as the default for that `T`, emitting `impl DefaultImpls1 for T { type Delegate = Provider; }`. `DefaultImpls2` does the same with two instance types for a pair-parameterized component. +The registration impl carries only the parameters that name the key and provider plus the `Components` table — never the provider's impl-side `where` clause. A provider whose bounds come from `#[use_type]`, `#[uses]`, `#[implicit]`, or `#[use_provider]` (for example `where Self: HasErrorType`) registers cleanly: those bounds stay on the provider's own impl and its [`IsProviderFor`](is_provider_for.md), and are checked when a real context resolves the provider, so a per-type default works regardless of what abstract types the provider depends on. + +Where a `#[default_impl]` may be *written* is bounded by Rust's orphan rule, because the emitted impl is `impl Namespace<..> for Key`. A crate may register a default when it owns either the namespace trait or the key type. For an unprefixed component the key is the component's own marker, so a downstream crate that owns the component can register a default into a foreign namespace. For a [`#[prefix]`](../macros/cgp_namespace.md)-ed component the key is a `PathCons<..>` path built from `cgp`-owned path types and the component marker, so the impl is only orphan-legal in the crate that owns the namespace — a per-component default keyed on a prefix path is confined to the namespace's crate. This is why `#[default_impl]` couples a wiring to the namespace's crate; wiring that must live downstream of the namespace goes in the namespace body of whatever crate owns it instead. + The hierarchical part is how a context consumes these defaults, which the [`delegate_components!`](../macros/delegate_components.md) `namespace` header and `for … in` syntax generate. A `namespace N;` header emits a blanket [`DelegateComponent`](delegate_component.md) impl on the context that forwards every key through `N`: `impl DelegateComponent for App where Key: N { type Delegate = Value; }`, paired with the matching [`IsProviderFor`](is_provider_for.md) forwarding so dependencies stay diagnosable. A `for in DefaultImpls1 { … }` loop emits a `DelegateComponent` impl keyed on a path whose `where` clause projects the default: `where T: DefaultImpls1`. Reading the loop: for each type `T` that has a `DefaultImpls1` default, wire that path to the projected `Provider`. The same loop works against a `DefaultNamespace`-style table or any namespace trait by changing the `in` target. Inheritance and override compose on top. A namespace that inherits from a parent (`new Child: DefaultNamespace { … }`) emits a blanket impl forwarding any key the parent resolves to the child, so the child resolves everything the parent does plus its own entries. A context's directly-wired entry resolves before the namespace fallback, so it shadows the inherited default for that key without disturbing the rest — the inheritance-with-override pattern presets rely on, expressed entirely through these projections with no runtime cost. diff --git a/docs/skills/cgp/references/namespaces.md b/docs/skills/cgp/references/namespaces.md index e9da81b2..0852a4fe 100644 --- a/docs/skills/cgp/references/namespaces.md +++ b/docs/skills/cgp/references/namespaces.md @@ -85,7 +85,7 @@ delegate_components! { } ``` -The `namespace DefaultNamespace;` line emits a blanket `DelegateComponent` impl on `AppA` that forwards every key through `DefaultNamespace`, paired with the matching `IsProviderFor` forwarding so dependency errors stay diagnosable through [checking](checking.md). The direct `@test.ShowImplComponent.u64` line resolves first, so it wins for `u64` only — the inherit-and-override pattern in action. Joining through `delegate_and_check_components!` does the same and additionally checks the entries the block writes directly, but its derivation does not cover the components the namespace itself brings in — so verifying the full inherited wiring is a job for a standalone `check_components!` (see [checking](checking.md)). +The `namespace DefaultNamespace;` line emits a blanket `DelegateComponent` impl on `AppA` that forwards every key through `DefaultNamespace`, paired with the matching `IsProviderFor` forwarding so dependency errors stay diagnosable through [checking](checking.md). The direct `@test.ShowImplComponent.u64` line resolves first, so it wins for `u64` only — the inherit-and-override pattern in action. The override works because `DefaultNamespace` only routes the *marker* `ShowImplComponent` to a path that the context itself fills; a context cannot instead override a path the joined namespace *itself* registers (through a `:` body entry or a `#[default_impl]`), because the direct entry and the namespace's blanket `DelegateComponent` impl would both cover that path and conflict (`E0119`). To leave a path overridable, route the marker through the namespace but terminate the redirect on the context. Joining through `delegate_and_check_components!` does the same and additionally checks the entries the block writes directly, but its derivation does not cover the components the namespace itself brings in — so verifying the full inherited wiring is a job for a standalone `check_components!` (see [checking](checking.md)). ## Paths with `Path!` @@ -135,7 +135,7 @@ pub trait DefaultImpls1 { } ``` -`DefaultNamespace` is the built-in namespace that `#[prefix(... in DefaultNamespace)]` registers components into and that a context joins with `namespace DefaultNamespace;`. A per-type default is registered with the `#[default_impl(T in DefaultImpls1)]` attribute on a provider impl, which emits `impl DefaultImpls1 for T { type Delegate = Provider; }`. A context then pulls those defaults in with a `for … in` loop that projects the `Delegate` for each type: +`DefaultNamespace` is the built-in namespace that `#[prefix(... in DefaultNamespace)]` registers components into and that a context joins with `namespace DefaultNamespace;`. A per-type default is registered with the `#[default_impl(T in DefaultImpls1)]` attribute on a provider impl, which emits `impl DefaultImpls1 for T { type Delegate = Provider; }`. The registration impl drops the provider's own impl-side `where` clause (its `#[use_type]`/`#[uses]` bounds), so a provider that depends on abstract types registers cleanly. Where the attribute may be *written* follows the orphan rule: the crate must own the namespace trait or the key — so an unprefixed component's marker key can be registered into a foreign namespace downstream, but a `#[prefix]`-ed component's key is a foreign `PathCons` path, confining its default to the namespace's own crate. A context then pulls those defaults in with a `for … in` loop that projects the `Delegate` for each type: ```rust delegate_components! { From d554fe84a4151389b7bdf59638660da5d7bdd077 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Sun, 5 Jul 2026 15:18:33 +0200 Subject: [PATCH 4/6] Remove modern idioms --- docs/guides/README.md | 24 ++++++++++++++++++++++-- docs/guides/capability-supertraits.md | 4 ++-- docs/guides/declaring-dependencies.md | 4 ++-- docs/guides/dispatching-per-type.md | 4 ++-- docs/guides/importing-abstract-types.md | 4 ++-- docs/guides/modern-idioms.md | 22 ---------------------- docs/guides/namespaces-and-prefixes.md | 2 +- docs/guides/reading-context-fields.md | 4 ++-- docs/guides/writing-providers.md | 4 ++-- 9 files changed, 35 insertions(+), 37 deletions(-) delete mode 100644 docs/guides/modern-idioms.md diff --git a/docs/guides/README.md b/docs/guides/README.md index b42dcb67..47de92ef 100644 --- a/docs/guides/README.md +++ b/docs/guides/README.md @@ -15,12 +15,32 @@ The authoring rules for these documents live in [../AGENTS.md](../AGENTS.md). Ea - [Organizing wiring with namespaces and prefixes](namespaces-and-prefixes.md) — how to keep a growing `delegate_components!` table short: grouping components under path prefixes with `#[prefix]`, binding providers to a namespace with `#[default_impl]`, and merging multiple providers into one flattened table, worked as a refactoring of a real application. - [Debugging CGP compile errors](debugging.md) — the playbook for tracing a wiring failure back to its cause: reading the error's shape, moving the error to the wiring site with checks, reducing to a minimal reproduction, inspecting the macro expansion, and a decoder for the errors you actually see. -The **modern idioms** are a family of small, related choices — each a shift from an explicit form to a vanilla-looking one — so they are grouped under a hub with a focused guide per idiom. Start at the hub for the framing and the map, or go straight to the idiom you are deciding on: +The **modern idioms** are a family of small, related choices — each a shift from an explicit form to a vanilla-looking one — so each has its own focused guide. The [Summary](#summary) below condenses these idioms *and* the two guides above into one cheat-sheet; go straight to a guide when you want the before/after mapping and the rules in full: -- [Modern idioms: a migration guide](modern-idioms.md) — the hub, with the explicit-to-modern framing and the list of cases where an explicit form is still right. - [Writing providers the modern way](writing-providers.md) — `#[cgp_impl]` in consumer-trait shape, omitting the context parameter. - [Declaring a provider's dependencies](declaring-dependencies.md) — `#[uses]` and `#[use_provider]` instead of hand-written `where` bounds. - [Reading context fields](reading-context-fields.md) — `#[implicit]` arguments instead of getter traits. - [Importing abstract types](importing-abstract-types.md) — `#[use_type]` aliases and the concrete-type equality form. - [Adding capability supertraits](capability-supertraits.md) — `#[extend]` instead of native `:` supertrait syntax. - [Dispatching a component per type](dispatching-per-type.md) — the `open` statement or a namespace instead of a `UseDelegate` table. + +## Summary + +This section condenses every guide above into one quick reference. Read it for the recommendation and the reason; follow a link when you need the full before/after mapping, the corner cases, or a worked example. + +**Write CGP that looks like ordinary Rust.** The explicit forms — an inside-out provider-trait `impl`, `where`-clause dependencies, `::Type` abstract types, `UseDelegate` dispatch tables — are exactly what the macros desugar to, so you keep reading them in generated code and older codebases, but you should *write* the modern idiom in all new code and reach for an explicit form only when a construct genuinely cannot express the case. Each row below is one such shift: + +| When you… | Prefer | Instead of | +|---|---|---| +| write a provider ([guide](writing-providers.md)) | `#[cgp_impl]` with the header `impl Trait` (omit `for Context`, keep `self`/`Self`) | raw `#[cgp_provider]`/`#[cgp_new_provider]` in inside-out shape | +| require a capability or an inner provider ([guide](declaring-dependencies.md)) | `#[uses(Trait)]` / `#[use_provider(P: Trait)]`, comma-separated in one attribute | hand-written `Self:`/`P: Trait` `where` bounds | +| read a value from the context's own field ([guide](reading-context-fields.md)) | an `#[implicit]` argument | a getter trait declared only to read it | +| name an abstract type ([guide](importing-abstract-types.md)) | `#[use_type(Trait.Type)]` + the bare alias (and `{Type = Concrete}` to pin one) | a `: Trait` supertrait + qualified `Self::Type` | +| add a non-type capability supertrait ([guide](capability-supertraits.md)) | `#[extend(Trait)]` | native `: Supertrait` inheritance syntax | +| dispatch a generic-parameter component per type ([guide](dispatching-per-type.md)) | the `open` statement or a [namespace](namespaces-and-prefixes.md) | `#[derive_delegate]` + a `UseDelegate` nested table | + +**When an explicit form is still right.** Keep a hand-written `where` clause for an associated-type-equality bound on a trait you would not `#[use_type]` from (`Iterator`, `From`) — but move an *abstract-type* pin like `Self: HasErrorType` into the `#[use_type]` equality form. Name the context explicitly (`impl Trait for Context`) only for a lifetime or higher-ranked bound the sugar cannot carry, or reach for `#[cgp_getter]` only when a context must choose per-wiring which field a getter reads. And a construct's own local associated type stays qualified as `Self::Output` always — it is never a `#[use_type]` import. + +**Keep a growing wiring table short with namespaces and prefixes** ([guide](namespaces-and-prefixes.md)). As component counts rise, group components under path prefixes with `#[prefix(@path in DefaultNamespace)]`, lift a backend's provider choices into a reusable namespace (via `#[default_impl]` or namespace body entries) so a context joins it with one `namespace N;` line, and pull a separate concern's table in with a `for … in` loop. Two rules bite: a context can only override a path its namespace routes *to* but does not itself register, and a prefixed component's `#[default_impl]` must live in the namespace's own crate. + +**When a wiring fails to compile, trace it, don't stare at it** ([guide](debugging.md)). CGP wiring is lazy, so one broken link surfaces as many errors on distant lines naming generated types; read the failing *trait* (an unmet `IsProviderFor` means a dependency is missing, a failed `DelegateComponent` means the lookup has no entry), move the error to the wiring site with `check_components!`, and when a large program puzzles you, reduce it to the smallest reproduction — or snapshot the expansion — rather than reasoning about coherence on paper. diff --git a/docs/guides/capability-supertraits.md b/docs/guides/capability-supertraits.md index 376f129a..bdca3e49 100644 --- a/docs/guides/capability-supertraits.md +++ b/docs/guides/capability-supertraits.md @@ -2,7 +2,7 @@ A CGP component often depends on another capability, and this guide is about declaring that dependency with `#[extend]`, which reads as importing a capability, rather than the native `:` supertrait syntax, which reads as inheritance. -This is one of the [modern idioms](modern-idioms.md); it is the capability counterpart to [importing abstract types](importing-abstract-types.md), which handles a supertrait whose *type* the signature names. +This is one of the [modern idioms](README.md#summary); it is the capability counterpart to [importing abstract types](importing-abstract-types.md), which handles a supertrait whose *type* the signature names. ## Add supertraits with `#[extend]`, not native `:` syntax @@ -31,4 +31,4 @@ pub trait CanGreet { - [Importing abstract types](importing-abstract-types.md) — use `#[use_type]` instead when the supertrait carries an associated type the signature names. - [Declaring dependencies](declaring-dependencies.md) — the `#[uses]` counterpart for a capability the implementation uses privately rather than re-exporting. -- [Modern idioms](modern-idioms.md) — the overview that ties the idioms together. +- [Modern idioms](README.md#summary) — the overview that ties the idioms together. diff --git a/docs/guides/declaring-dependencies.md b/docs/guides/declaring-dependencies.md index a1b98380..3126b1e8 100644 --- a/docs/guides/declaring-dependencies.md +++ b/docs/guides/declaring-dependencies.md @@ -2,7 +2,7 @@ A provider states what it needs from its context in its `where` clause, and this guide is about writing those needs as attributes that read like imports rather than as hand-written trait bounds. -This is one of the [modern idioms](modern-idioms.md); it follows on from [writing the provider header](writing-providers.md), which leaves the bounds off the header for these attributes to supply. +This is one of the [modern idioms](README.md#summary); it follows on from [writing the provider header](writing-providers.md), which leaves the bounds off the header for these attributes to supply. ## Declare dependencies with `#[uses]` and `#[use_provider]` @@ -37,4 +37,4 @@ Both attributes desugar to the same `where` predicates they replace. When a prov - [Writing providers](writing-providers.md) — the `#[cgp_impl]` header these attributes attach to. - [Importing abstract types](importing-abstract-types.md) — where an abstract-type pin belongs instead of `#[uses]`. -- [Modern idioms](modern-idioms.md) — the overview and the list of cases where an explicit `where` clause is still right. +- [Modern idioms](README.md#summary) — the overview and the list of cases where an explicit `where` clause is still right. diff --git a/docs/guides/dispatching-per-type.md b/docs/guides/dispatching-per-type.md index 8d291b1b..2a2a24af 100644 --- a/docs/guides/dispatching-per-type.md +++ b/docs/guides/dispatching-per-type.md @@ -2,7 +2,7 @@ A component that is generic over a type parameter often wants a different provider per value of that parameter, and this guide is about doing that with the `open` statement or a namespace rather than the legacy `UseDelegate` nested table. -This is one of the [modern idioms](modern-idioms.md), and it connects directly to [organizing wiring with namespaces and prefixes](namespaces-and-prefixes.md), which develops the namespace side of the choice in depth. +This is one of the [modern idioms](README.md#summary), and it connects directly to [organizing wiring with namespaces and prefixes](namespaces-and-prefixes.md), which develops the namespace side of the choice in depth. ## Dispatch per type with `open` and namespaces, not `UseDelegate` @@ -40,4 +40,4 @@ Choose between the two modern forms by scope. Prefer `open` for a self-contained ## Related guides - [Organizing wiring with namespaces and prefixes](namespaces-and-prefixes.md) — the full namespace treatment, including flattening multi-provider dispatch that `open` alone cannot. -- [Modern idioms](modern-idioms.md) — the overview that ties the idioms together. +- [Modern idioms](README.md#summary) — the overview that ties the idioms together. diff --git a/docs/guides/importing-abstract-types.md b/docs/guides/importing-abstract-types.md index 752c8bb6..d005bbed 100644 --- a/docs/guides/importing-abstract-types.md +++ b/docs/guides/importing-abstract-types.md @@ -2,7 +2,7 @@ CGP abstracts over types with associated types on components, and this guide is about bringing such a type into a definition as a plain alias rather than a supertrait plus a fully-qualified `Self::Type` at every use. -This is one of the [modern idioms](modern-idioms.md). It applies inside [`#[cgp_component]`](../reference/macros/cgp_component.md) definitions and [`#[cgp_impl]`](../reference/macros/cgp_impl.md)/[`#[cgp_fn]`](../reference/macros/cgp_fn.md) providers alike, and it is the recommended form for the built-in error type as much as for a domain type. +This is one of the [modern idioms](README.md#summary). It applies inside [`#[cgp_component]`](../reference/macros/cgp_component.md) definitions and [`#[cgp_impl]`](../reference/macros/cgp_impl.md)/[`#[cgp_fn]`](../reference/macros/cgp_fn.md) providers alike, and it is the recommended form for the built-in error type as much as for a domain type. ## Import abstract types with `#[use_type]` @@ -39,4 +39,4 @@ When a capability supertrait has no associated type to import — a plain capabi - [Capability supertraits](capability-supertraits.md) — the companion for a supertrait that contributes a capability rather than a type. - [Declaring dependencies](declaring-dependencies.md) — where an abstract-type pin moves *from* (a `#[uses]` or hand-written `where`). -- [Modern idioms](modern-idioms.md) — the overview and the local-associated-type exception restated among the other still-explicit cases. +- [Modern idioms](README.md#summary) — the overview and the local-associated-type exception restated among the other still-explicit cases. diff --git a/docs/guides/modern-idioms.md b/docs/guides/modern-idioms.md deleted file mode 100644 index 1c4266b3..00000000 --- a/docs/guides/modern-idioms.md +++ /dev/null @@ -1,22 +0,0 @@ -# Modern idioms: a migration guide - -CGP offers a set of newer, higher-level idioms for writing components, providers, and wiring that read much closer to ordinary Rust — and this guide is the hub that maps each older, more explicit form to the modern one you should prefer, linking to a focused guide for each shift. - -The explicit forms came first. Early CGP exposed the machinery directly: a provider was an inside-out `impl` of a provider trait, dependencies were spelled as `where` clauses, abstract types were pulled from supertraits and written in fully-qualified `::Type` form, and per-type dispatch went through a `UseDelegate` table. Those forms still work and are exactly what the macros desugar to, so you will keep reading them in generated code, in expansion documentation, and in existing codebases. The newer idioms exist to lower the barrier to entry: they let a provider look like an ordinary trait `impl`, a dependency look like a `use` import, and an abstract type look like a plain generic, so that a reader who knows Rust but not CGP can follow the code. **Prefer the modern idioms in all new code, and reach for an explicit form only when a construct genuinely cannot express the case.** - -## The idioms, one guide each - -Each idiom is a single shift from an explicit form to a modern one, and each has its own guide with the before/after mapping, the rules that bound it, and worked examples. The concepts behind them are documented in full elsewhere: writing providers in [consumer and provider traits](../concepts/consumer-and-provider-traits.md), dependency injection in [impl-side dependencies](../concepts/impl-side-dependencies.md), field injection in [implicit arguments](../concepts/implicit-arguments.md), abstract types in [abstract types](../concepts/abstract-types.md), the provider-parameterized pattern in [higher-order providers](../concepts/higher-order-providers.md), and per-type dispatch in [dispatching](../concepts/dispatching.md) and [namespaces](../concepts/namespaces.md). - -- [Writing providers the modern way](writing-providers.md) — write a provider with `#[cgp_impl]` in consumer-trait shape, omitting the context parameter, instead of the inside-out `#[cgp_provider]`/`#[cgp_new_provider]` forms. -- [Declaring a provider's dependencies](declaring-dependencies.md) — state impl-side dependencies with `#[uses]` and `#[use_provider]` rather than hand-written `where` bounds. -- [Reading context fields](reading-context-fields.md) — pull a field value in with an `#[implicit]` argument rather than declaring a getter trait. -- [Importing abstract types](importing-abstract-types.md) — bring an abstract type in with `#[use_type]` and write it as a bare alias, and pin one to a concrete type with the equality form, instead of a supertrait plus `Self::Type`. -- [Adding capability supertraits](capability-supertraits.md) — declare a capability supertrait with `#[extend]` rather than the native `:` inheritance syntax. -- [Dispatching a component per type](dispatching-per-type.md) — dispatch a generic-parameter component with the `open` statement or a namespace rather than a `UseDelegate` nested table. - -## When the explicit forms are still right - -A handful of cases genuinely need an explicit form, and reaching for one there is not a regression. Keep an explicit `where` clause for an associated-type-equality bound on a non-`#[use_type]` trait — `Iterator`, `From` — which reads more clearly as a `where` clause than in an import-shaped `#[uses]` (which does accept it); but not for one pinning an abstract type: `Self: HasErrorType` belongs in the [`#[use_type]` equality form](importing-abstract-types.md), not a hand-written `where`. Name the context explicitly, as `impl Trait for Context`, when you must attach a lifetime or higher-ranked bound the sugar cannot carry. Reach for [`#[cgp_getter]`](../reference/macros/cgp_getter.md) when you specifically want full control over which field a getter reads from, chosen per context at wiring time. Write a raw provider-trait `impl` when you need the inside-out shape directly, for instance a provider whose `Self` is a concrete context rather than a generic one. And keep a local associated type qualified as `Self::Output` always — it is never a `#[use_type]` import. In every other case, the modern idiom is the one to write. - -Further reference: the per-construct mechanics live in the reference documents linked from each guide above; [modularity hierarchy](../concepts/modularity-hierarchy.md) frames how much CGP a problem needs, and [consumer and provider traits](../concepts/consumer-and-provider-traits.md) explains the duality the provider idioms rest on. diff --git a/docs/guides/namespaces-and-prefixes.md b/docs/guides/namespaces-and-prefixes.md index fdef1e67..d0776c9d 100644 --- a/docs/guides/namespaces-and-prefixes.md +++ b/docs/guides/namespaces-and-prefixes.md @@ -311,4 +311,4 @@ The three techniques form a ladder, and most applications should climb only as f - [`DefaultNamespace`, `DefaultImpls1`, `DefaultImpls2`](../reference/traits/default_namespace.md) — the lookup traits behind namespaces and the `#[default_impl]` attribute. - [`delegate_components!`](../reference/macros/delegate_components.md) — the wiring table these techniques restructure, including the `open` statement for the self-contained case. - [Namespaces](../concepts/namespaces.md) — the mechanism (inheritance, `RedirectLookup`, and paths) that makes the preset pattern work. -- [Modern idioms: a migration guide](modern-idioms.md) — the companion guide covering the provider, dependency, and abstract-type idioms the code above uses. +- [Modern idioms summary](README.md#summary) — the condensed reference for the provider, dependency, and abstract-type idioms the code above uses. diff --git a/docs/guides/reading-context-fields.md b/docs/guides/reading-context-fields.md index 971cea58..991be395 100644 --- a/docs/guides/reading-context-fields.md +++ b/docs/guides/reading-context-fields.md @@ -2,7 +2,7 @@ A provider reads values from its context's fields, and this guide is about doing that with an argument that looks like an ordinary parameter rather than a getter trait declared just to fetch it. -This is one of the [modern idioms](modern-idioms.md); it is the value-level counterpart to [writing providers](writing-providers.md) in vanilla-looking form. +This is one of the [modern idioms](README.md#summary); it is the value-level counterpart to [writing providers](writing-providers.md) in vanilla-looking form. ## Read context fields with implicit arguments, not getter traits @@ -43,4 +43,4 @@ Avoid [`#[cgp_getter]`](../reference/macros/cgp_getter.md) in ordinary code. It - [Writing providers](writing-providers.md) — the `#[cgp_impl]` provider these arguments live in. - [Importing abstract types](importing-abstract-types.md) — for a getter whose return type is an abstract type shared across contexts. -- [Modern idioms](modern-idioms.md) — the overview and the cases where a getter trait is still the right tool. +- [Modern idioms](README.md#summary) — the overview and the cases where a getter trait is still the right tool. diff --git a/docs/guides/writing-providers.md b/docs/guides/writing-providers.md index b7b6f008..457a4fbe 100644 --- a/docs/guides/writing-providers.md +++ b/docs/guides/writing-providers.md @@ -2,7 +2,7 @@ A provider can be written at three levels of sugar over the same machinery, and this guide is about choosing the highest one — writing a provider that reads like an ordinary trait `impl` rather than the inside-out provider-trait form the macros desugar to. -This is one of the [modern idioms](modern-idioms.md); it pairs with [declaring a provider's dependencies](declaring-dependencies.md) and [reading its context's fields](reading-context-fields.md), which cover what goes inside the provider once its header is written this way. +This is one of the [modern idioms](README.md#summary); it pairs with [declaring a provider's dependencies](declaring-dependencies.md) and [reading its context's fields](reading-context-fields.md), which cover what goes inside the provider once its header is written this way. ## Write providers with `#[cgp_impl]`, not the raw provider forms @@ -67,4 +67,4 @@ impl AreaCalculator { - [Declaring a provider's dependencies](declaring-dependencies.md) — state the `where` bounds this idiom leaves off the header with `#[uses]` and `#[use_provider]`. - [Reading context fields](reading-context-fields.md) — pull field values into a provider with `#[implicit]` arguments, as the first example does. -- [Modern idioms](modern-idioms.md) — the overview that ties these idioms together and lists when an explicit form is still right. +- [Modern idioms](README.md#summary) — the overview that ties these idioms together and lists when an explicit form is still right. From 2fb0b9b3c3826f044f86f6f56dadf3989aecf768 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Sun, 5 Jul 2026 19:51:38 +0200 Subject: [PATCH 5/6] Revise docs --- docs/AGENTS.md | 2 +- docs/README.md | 2 +- docs/examples/README.md | 2 +- docs/examples/money-transfer-api.md | 347 ++++++++++++++++-------- docs/guides/README.md | 15 +- docs/guides/capability-supertraits.md | 4 +- docs/guides/declaring-dependencies.md | 4 +- docs/guides/dispatching-per-type.md | 8 +- docs/guides/importing-abstract-types.md | 6 +- docs/guides/namespaces-and-prefixes.md | 2 +- docs/guides/reading-context-fields.md | 4 +- docs/guides/writing-providers.md | 8 +- 12 files changed, 257 insertions(+), 147 deletions(-) diff --git a/docs/AGENTS.md b/docs/AGENTS.md index c9312aaa..58aead70 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -38,7 +38,7 @@ When an example would demonstrate a concept the reference does not yet cover, do ## The guides directory -The [guides/](guides/) directory holds prescriptive guides to *writing* CGP — documents that direct the choices an author makes when several constructs could express the same thing. A guide is not a reference and not a concept: where a reference states what a construct means and a concept explains an idea, a guide recommends a default form, names the trade-offs of the alternatives, and usually walks a concrete before/after refactoring to ground the recommendation in real code. The migration from explicit to modern idioms and the use of namespaces to shrink wiring tables are the current guides; [guides/README.md](guides/README.md) is the catalog you register a new one in. +The [guides/](guides/) directory holds prescriptive guides to *writing* CGP — documents that direct the choices an author makes when several constructs could express the same thing. A guide is not a reference and not a concept: where a reference states what a construct means and a concept explains an idea, a guide recommends a default form, names the trade-offs of the alternatives, and usually walks a concrete before/after refactoring to ground the recommendation in real code. Choosing a construct's vanilla-looking form over its explicit equivalent, shrinking wiring tables with namespaces, and debugging compile errors are the current guides; [guides/README.md](guides/README.md) is the catalog you register a new one in. 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. diff --git a/docs/README.md b/docs/README.md index 58b2b146..db19e9e6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -18,7 +18,7 @@ The [concepts/](concepts/README.md) directory holds the cross-cutting conceptual The [examples/](examples/README.md) directory holds self-contained worked examples, one realistic use case developed end to end per document, from its contexts and components through to the wiring that connects them. The examples are the canonical source of code snippets the reference and concept documents reuse, so the same running scenarios recur across the whole knowledge base. -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. The migration from explicit to modern idioms and the use of namespaces to keep wiring tables short both live here. +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. ## How to use it diff --git a/docs/examples/README.md b/docs/examples/README.md index 8a75efbf..95ef84bf 100644 --- a/docs/examples/README.md +++ b/docs/examples/README.md @@ -15,7 +15,7 @@ The authoring rules for examples, including how to add a new one, live in [../AG - [Area calculation](area-calculation.md) — computing the area of several shapes, progressing from field-driven functions to a wireable area component with composable higher-order providers. - [Shell-scripting DSL](shell-scripting-dsl.md) — a type-level DSL whose programs are types interpreted at compile time, progressing from a fixed CLI program through the handler component and its namespace wiring to a custom context and a language extension. - [Profile picture lookup](profile-picture.md) — fetching a user's profile picture across a database query and an object-storage download, progressing from field-driven async functions through impl-only generics that vary the database engine to a storage component wired per context. -- [Money-transfer API](money-transfer-api.md) — the backend for a balance-query and funds-transfer web service, progressing from a per-endpoint-dispatched async handler component through reusable handler wrappers to a context wired to all of it and served over HTTP with a recovered `Send` bound. +- [Money-transfer API](money-transfer-api.md) — the backend for a balance-query and funds-transfer web service, progressing from abstract domain types and a status-coded error component through a per-endpoint-dispatched async handler and reusable wrappers to a namespace-organized context wiring served over HTTP with a recovered `Send` bound. - [Application builder](application-builder.md) — assembling an application context from independent per-subsystem builder providers, progressing from a hand-written constructor through merged builder outputs to swapping subsystems and producing several application variants from one builder via the [extensible builder pattern](../concepts/extensible-records.md). - [Expression interpreter](expression-interpreter.md) — a modular interpreter for an arithmetic language that solves the expression problem, progressing from a closed enum through per-variant evaluation providers and a second to-Lisp operation to code-based dispatch and an extended language, using the [extensible visitor pattern](../concepts/extensible-variants.md). - [Extensible shapes](extensible-shapes.md) — operations over geometric shapes modeled as enum variants, progressing from an auto-dispatched per-variant operation through mutating and argument-taking methods and enum upcasting/downcasting to context-wired dispatch with the [visitor combinators](../reference/providers/dispatch_combinators.md); the non-recursive companion to the expression interpreter. diff --git a/docs/examples/money-transfer-api.md b/docs/examples/money-transfer-api.md index 650cbdb6..81b328e4 100644 --- a/docs/examples/money-transfer-api.md +++ b/docs/examples/money-transfer-api.md @@ -1,18 +1,18 @@ # Money-transfer API -This example builds the backend for a small money-transfer web service — querying a user's balance and moving funds between accounts — as a set of composable API handlers that an HTTP server drives. It progresses from a single dispatched handler component, through reusable handler wrappers that add request decoding and authentication, to an in-memory context wired to all of it and finally served over HTTP. It is a template for any request/response service whose endpoints share cross-cutting concerns and whose backend should be swappable behind abstract types. +This example builds the backend for a small money-transfer web service — querying a user's balance and moving funds between accounts — as a set of composable API handlers that an HTTP server drives. It progresses from abstract domain types and a status-coded error component, through a per-endpoint-dispatched handler and the reusable wrappers that add decoding, authentication, and encoding, to an in-memory context whose whole wiring is organized into a namespace and served over HTTP. It is a template for any request/response service whose endpoints share cross-cutting concerns and whose backend should be swappable behind abstract types. -The concepts each step demonstrates are documented in full in the reference; this example only notes which one is in play and links to it: +The concepts each step demonstrates are documented in full elsewhere; this example only notes which one is in play and links to it: - abstract domain types — [`#[cgp_type]`](../reference/macros/cgp_type.md) and the [abstract-types concept](../concepts/abstract-types.md) -- an async, per-endpoint-dispatched component — [`#[cgp_component]`](../reference/macros/cgp_component.md), [`#[async_trait]`](../reference/macros/async_trait.md), [`#[derive_delegate]`](../reference/attributes/derive_delegate.md) -- handlers that wrap other handlers — [higher-order providers](../concepts/higher-order-providers.md) written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md) -- backend providers reading context fields — [implicit field access](../concepts/implicit-arguments.md) via [`#[implicit]`](../reference/attributes/implicit.md) arguments, with [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) reserved for the request fields a handler reads through a `where` bound -- raising status-coded errors through an application-specific error component — [modular error handling](../concepts/modular-error-handling.md) over [`HasErrorType`](../reference/components/has_error_type.md) -- per-endpoint wiring — [`delegate_components!`](../reference/macros/delegate_components.md) with [`UseDelegate`](../reference/providers/use_delegate.md) and a [`check_components!`](../reference/macros/check_components.md) assertion +- status-coded errors through an application-specific error component — [modular error handling](../concepts/modular-error-handling.md) over [`HasErrorType`](../reference/components/has_error_type.md) +- an async, per-endpoint-dispatched component — [`#[cgp_component]`](../reference/macros/cgp_component.md) with [`#[async_trait]`](../reference/macros/async_trait.md) +- handlers, and a business capability, that wrap another provider — [higher-order providers](../concepts/higher-order-providers.md) written with [`#[cgp_impl]`](../reference/macros/cgp_impl.md) and [`#[use_provider]`](../reference/attributes/use_provider.md) +- a backend reading context fields — [implicit field access](../concepts/implicit-arguments.md) via [`#[implicit]`](../reference/attributes/implicit.md) arguments, with [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) reserved for the request fields a handler reads through a `where` bound +- organizing the wiring — path prefixes and a namespace, per the [namespaces-and-prefixes guide](../guides/namespaces-and-prefixes.md), backed by [`#[prefix]`](../reference/macros/cgp_namespace.md), [`#[default_impl]`](../reference/traits/default_namespace.md), and [`delegate_components!`](../reference/macros/delegate_components.md) with a [`check_components!`](../reference/macros/check_components.md) assertion - restoring a `Send` bound for the HTTP server — the [recovering `Send` bounds concept](../concepts/send-bounds.md) -All snippets assume `use cgp::prelude::*;`. The service speaks in terms of a handful of domain types that are kept abstract so the same handlers work whatever concrete types a deployment chooses. +All snippets assume `use cgp::prelude::*;`. The service speaks in terms of a handful of domain types kept abstract so the same handlers work whatever concrete types a deployment chooses. ## Abstract domain types @@ -20,31 +20,83 @@ The service never names a concrete user id, currency, or amount; it names abstra ```rust #[cgp_type] +#[prefix(@app.auth.types in DefaultNamespace)] pub trait HasUserIdType { type UserId: Display; } #[cgp_type] +#[prefix(@app.finance.types in DefaultNamespace)] pub trait HasQuantityType { type Quantity: Display; } #[cgp_type] +#[prefix(@app.finance.types in DefaultNamespace)] pub trait HasCurrencyType { type Currency: Display; } ``` -Keeping these abstract is what lets one balance-query handler serve a context whose currency is a rich enum and another whose currency is a bare string, without rewriting the handler. A context binds each type once during wiring, shown later. +Keeping these abstract is what lets one balance-query handler serve a context whose currency is a rich enum and another whose currency is a bare string, without rewriting the handler. The authentication types `HasPasswordType` and `HasHashedPasswordType` are defined the same way under `@app.auth.types`. The [`#[prefix(@path in DefaultNamespace)]`](../reference/macros/cgp_namespace.md) attribute on each files the component under a path — `@app.auth.types`, `@app.finance.types` — that the wiring section uses to organize the table; a first-time reader can ignore it until then, or read the [namespaces-and-prefixes guide](../guides/namespaces-and-prefixes.md) for why the types sit on a `types` sub-path apart from the logic. -## The dispatched handler component +## Status-coded errors + +Endpoints fail with an HTTP status code, but the handlers never construct one directly — they raise through an application-specific error component so the mapping from a domain failure to a status code lives in one place. `CanRaiseHttpError` is a [component](../concepts/modular-error-handling.md) that turns a marker code and a detail value into the context's abstract [error type](../reference/components/has_error_type.md): + +```rust +#[cgp_component(HttpErrorRaiser)] +#[prefix(@app.error in DefaultNamespace)] +#[use_type(HasErrorType.Error)] +pub trait CanRaiseHttpError { + fn raise_http_error(_code: Code, detail: Detail) -> Error; +} + +pub struct ErrUnauthorized; +pub struct ErrBadRequest; +pub struct ErrNotFound; +``` + +The code markers double as a table from marker to status code through an ordinary trait, so a provider can turn `ErrUnauthorized` into `401` without a match: + +```rust +pub trait IsStatusCode { + fn status_code() -> StatusCode; +} + +impl IsStatusCode for ErrUnauthorized { + fn status_code() -> StatusCode { + StatusCode::UNAUTHORIZED + } +} +// ErrBadRequest -> BAD_REQUEST, ErrNotFound -> NOT_FOUND, … + +#[cgp_impl(new DisplayHttpError)] +#[use_type(HasErrorType.{Error = AppError})] +impl HttpErrorRaiser +where + Code: IsStatusCode, + Detail: Display, +{ + fn raise_http_error(_code: Code, detail: Detail) -> AppError { + AppError { + status_code: Code::status_code(), + detail: anyhow!("{detail}"), + } + } +} +``` + +`DisplayHttpError` is generic over both the code and the detail, so one provider serves every `raise_http_error` call whose detail is `Display`. It pins the abstract error to the concrete `AppError` with the [`#[use_type]` equality form](../guides/importing-abstract-types.md) — `HasErrorType.{Error = AppError}` — which is why the method can build an `AppError` directly. A sibling `HandleHttpErrorWithAnyhow` handles details that are already an `anyhow::Error`; the wiring picks between them per detail type. + +## The dispatched API-handler component Every endpoint is one case of a single component that dispatches on a marker type naming the API. The consumer trait `CanHandleApi` takes the endpoint marker as a generic parameter and, for that endpoint, fixes a `Request` and `Response` type and an async method that turns one into the other: ```rust #[cgp_component(ApiHandler)] +#[prefix(@app.api in DefaultNamespace)] #[async_trait] -#[derive_delegate(UseDelegate)] #[use_type(HasErrorType.Error)] pub trait CanHandleApi { type Request; @@ -61,26 +113,13 @@ pub struct TransferApi; pub struct QueryBalanceApi; ``` -The three attributes each pull their weight. [`#[cgp_component]`](../reference/macros/cgp_component.md) makes `ApiHandler` a wireable component so each endpoint can bind a different provider; [`#[async_trait]`](../reference/macros/async_trait.md) keeps the async method's declaration lint-clean; and [`#[derive_delegate(UseDelegate)]`](../reference/attributes/derive_delegate.md) generates a [`UseDelegate`](../reference/providers/use_delegate.md) provider that routes each call to a per-`Api` entry in an inner table. The markers `TransferApi` and `QueryBalanceApi` are empty structs used only as keys; `PhantomData` carries the choice at the type level so the right provider is selected with no runtime branch. +[`#[cgp_component]`](../reference/macros/cgp_component.md) makes `ApiHandler` a wireable component so each endpoint can bind a different provider, and [`#[async_trait]`](../reference/macros/async_trait.md) keeps the async method's declaration lint-clean. Because the component is generic over `Api`, a context dispatches it per marker — `TransferApi` to one provider, `QueryBalanceApi` to another — through the [namespace path machinery](../guides/dispatching-per-type.md) shown in the wiring section, with no runtime branch: `PhantomData` carries the choice at the type level. ## Endpoint handlers -Each endpoint is a provider for `ApiHandler` that depends on business capabilities rather than on any concrete backend. The transfer endpoint, for instance, reads the logged-in sender and the transfer details from its request, then calls the `CanTransferMoney` capability — itself an abstract async component the context implements however it likes: +Each endpoint is a provider for `ApiHandler` that depends on business capabilities rather than on any concrete backend. The transfer endpoint reads the logged-in sender and the transfer details from its request, then calls the `CanTransferMoney` capability — itself an abstract async component the context implements however it likes: ```rust -#[cgp_component(MoneyTransferrer)] -#[async_trait] -#[use_type(HasUserIdType.UserId, HasCurrencyType.Currency, HasQuantityType.Quantity, HasErrorType.Error)] -pub trait CanTransferMoney { - async fn transfer_money( - &self, - sender: &UserId, - recipient: &UserId, - currency: &Currency, - quantity: &Quantity, - ) -> Result<(), Error>; -} - #[cgp_impl(new HandleTransfer)] #[uses(CanTransferMoney, CanRaiseHttpError)] #[use_type(HasErrorType.Error)] @@ -91,42 +130,70 @@ where type Request = Request; type Response = (); + async fn handle_api(&self, _api: PhantomData, request: Request) -> Result<(), Error> { + let sender = request.logged_in_user().as_ref().ok_or_else(|| { + Self::raise_http_error(ErrUnauthorized, "you must first login".into()) + })?; + + self.transfer_money(sender, request.recipient(), request.currency(), request.quantity()) + .await?; + + Ok(()) + } +} +``` + +The endpoint is generic over its request shape: `HandleTransfer` works for any `Request` that exposes a logged-in user and the transfer fields through the [getter traits](../reference/macros/cgp_auto_getter.md) named in its `where` clause, so the same logic serves whatever request struct a deployment decodes. The `Self: ...` capability bounds are [impl-side dependencies](../concepts/impl-side-dependencies.md) declared with [`#[uses]`](../guides/declaring-dependencies.md), holding the context to providing money-transfer and error-raising without those leaking into the consumer trait. + +The balance query has the same shape but returns a value, so it fixes a response type. Its `QueryBalanceResponse` stays generic over the context's abstract `Quantity`, and `#[derive(Serialize)]` lets the JSON wrapper encode it: + +```rust +#[derive(Serialize)] +pub struct QueryBalanceResponse +where + App: HasQuantityType, +{ + pub balance: App::Quantity, +} + +#[cgp_impl(new HandleQueryBalance)] +#[uses(CanQueryUserBalance, CanRaiseHttpError)] +#[use_type(HasErrorType.Error)] +impl ApiHandler +where + Request: HasLoggedInUser + HasQueryBalanceFields, +{ + type Request = Request; + type Response = QueryBalanceResponse; + async fn handle_api( &self, _api: PhantomData, request: Request, - ) -> Result<(), Error> { - let sender = request.logged_in_user().as_ref().ok_or_else(|| { + ) -> Result, Error> { + let user = request.logged_in_user().as_ref().ok_or_else(|| { Self::raise_http_error(ErrUnauthorized, "you must first login".into()) })?; - self.transfer_money( - sender, - request.recipient(), - request.currency(), - request.quantity(), - ) - .await?; + let balance = self.query_user_balance(user, request.currency()).await?; - Ok(()) + Ok(QueryBalanceResponse { balance }) } } ``` -The endpoint is generic over its request shape. `HandleTransfer` works for any `Request` type that exposes a logged-in user and the transfer fields through the [getter traits](../reference/macros/cgp_auto_getter.md) named in its `where` clause, so the same handler logic serves whatever request struct a deployment decodes from the wire. The `Self: ...` bounds are [impl-side dependencies](../concepts/impl-side-dependencies.md): they hold the context to providing money-transfer and HTTP-error capabilities without those leaking into the consumer trait. - ## Reusable handler wrappers -Cross-cutting concerns are handlers that wrap another handler, which makes them [higher-order providers](../concepts/higher-order-providers.md). Each takes an inner handler as a type parameter, implements `ApiHandler` itself, and threads the call through — transforming the request or response on the way. Three small wrappers cover decoding, authentication, and JSON encoding. +Cross-cutting concerns are handlers that wrap another handler, which makes them [higher-order providers](../concepts/higher-order-providers.md): each takes an inner handler as a type parameter, declared with [`#[use_provider]`](../reference/attributes/use_provider.md), implements `ApiHandler` itself, and threads the call through — transforming the request or response on the way. Three small wrappers cover decoding, authentication, and JSON encoding. `HandleFromRequest` adapts the request type, letting an endpoint that wants a clean domain request sit behind a handler whose request is the raw type the HTTP layer produces: ```rust #[cgp_impl(new HandleFromRequest)] #[use_type(HasErrorType.Error)] +#[use_provider(InHandler: ApiHandler)] impl ApiHandler where - InHandler: ApiHandler, Request: Into, { type Request = Request; @@ -142,17 +209,17 @@ where } ``` -`UseBasicAuth` performs authentication before delegating, resolving a basic-auth header into a logged-in user and mutating the request in place: +`UseBasicAuth` authenticates before delegating, resolving a basic-auth header into a logged-in user and mutating the request in place; it depends on the `CanQueryUserHashedPassword` and `CanCheckPassword` capabilities: ```rust #[cgp_impl(new UseBasicAuth)] #[uses(CanQueryUserHashedPassword, CanCheckPassword)] -#[use_type(HasUserIdType.UserId, HasErrorType.Error)] +#[use_type(HasErrorType.Error)] +#[use_provider(InHandler: ApiHandler)] impl ApiHandler where - InHandler: ApiHandler, InHandler::Request: HasLoggedInUserMut + HasBasicAuthHeader, - UserId: Clone, + Self::UserId: Clone, { type Request = InHandler::Request; type Response = InHandler::Response; @@ -165,8 +232,10 @@ where if request.logged_in_user().is_none() && let Some((user_id, password)) = request.basic_auth_header() { - if let Some(hashed) = self.query_user_hashed_password(user_id).await? - && Self::check_password(password, &hashed) + let m_hashed_password = self.query_user_hashed_password(user_id).await?; + + if let Some(hashed_password) = m_hashed_password + && Self::check_password(password, &hashed_password) { *request.logged_in_user() = Some(user_id.clone()); } @@ -177,44 +246,50 @@ where } ``` -`ResponseToJson` adapts in the other direction, wrapping whatever the inner handler returns in an Axum `Json` envelope: +`ResponseToJson` adapts in the other direction, wrapping whatever the inner handler returns in an Axum `Json` envelope. Because each wrapper is itself an `ApiHandler`, they nest into a pipeline: `HandleFromRequest>>>` reads outside-in as the stages a request passes through — decode the raw request, JSON-encode the response, authenticate, run the endpoint — with each layer adding exactly one concern and the endpoint at the center oblivious to all of them. + +## The backend behind the capabilities + +The business capabilities are satisfied by a provider that reads its data from context fields. `UseMockedApp` is an in-memory backend that implements `UserBalanceQuerier`, `MoneyTransferrer`, and the auth capabilities by reaching into maps stored on the context, pulled in as [`#[implicit]`](../reference/attributes/implicit.md) arguments — the balances map is read by reference (`&Arc>`) with no clone and no getter trait to declare: ```rust -#[cgp_impl(ResponseToJson)] -#[use_type(HasErrorType.Error)] -impl ApiHandler +#[cgp_impl(UseMockedApp)] +#[default_impl(@app.finance.UserBalanceQuerierComponent in MockNamespace)] +#[uses(CanRaiseHttpError)] +#[use_type(HasUserIdType.UserId, HasCurrencyType.Currency, HasQuantityType.Quantity, HasErrorType.Error)] +impl UserBalanceQuerier where - InHandler: ApiHandler, + UserId: Ord + Clone, + Currency: Ord + Clone, + Quantity: Clone, { - type Request = InHandler::Request; - type Response = Json; - - async fn handle_api( + async fn query_user_balance( &self, - api: PhantomData, - request: Self::Request, - ) -> Result { - let response = InHandler::handle_api(self, api, request).await?; - Ok(Json(response)) + user: &UserId, + currency: &Currency, + #[implicit] user_balances: &Arc>>, + ) -> Result { + let balances = user_balances.lock().await; + balances + .get(&(user.clone(), currency.clone())) + .cloned() + .ok_or_else(|| Self::raise_http_error(ErrNotFound, format!("user not found: {user}"))) } } ``` -Because each wrapper is itself an `ApiHandler`, they nest into a pipeline. Writing `HandleFromRequest>>>` reads outside-in as the stages a request passes through — decode the raw request, authenticate, run the endpoint, JSON-encode the response — with each layer adding exactly one concern and the endpoint at the center oblivious to all of them. +The `#[implicit]` argument reads the same `user_balances` field a getter would, but as a `&Arc>` bound at the top of the method — the [preferred form](../guides/reading-context-fields.md) for a field a provider reads from its own context. The request-field getters `HasLoggedInUser` and `HasBasicAuthHeader`, by contrast, stay [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) traits, because they read from the *request* type and are required as `where` bounds on it (`Request: HasBasicAuthHeader`) — a case an implicit argument, which reads only from `self`, cannot cover. The [`#[default_impl]`](../reference/traits/default_namespace.md) attribute registers this provider into the application's namespace; that is a wiring concern, explained next. -## The backend behind the capabilities - -The business capabilities are satisfied by a provider that reads its data from context fields. `UseMockedApp` is an in-memory backend that implements `MoneyTransferrer` and the other capabilities by reaching into maps stored on the context, pulled in as [`#[implicit]`](../reference/attributes/implicit.md) arguments — the balances map is read by reference (`&Arc>`) with no clone and no getter trait to declare: +A business capability can be wrapped the same way an API handler can. `NoTransferToSelf` is a [higher-order provider](../concepts/higher-order-providers.md) for `MoneyTransferrer` that rejects a self-transfer and otherwise delegates to an inner transfer provider: ```rust -#[cgp_impl(UseMockedApp)] -#[uses(CanRaiseHttpError, CanRaiseHttpError)] +#[cgp_impl(new NoTransferToSelf)] #[use_type(HasUserIdType.UserId, HasCurrencyType.Currency, HasQuantityType.Quantity, HasErrorType.Error)] -impl MoneyTransferrer +#[uses(CanRaiseHttpError)] +#[use_provider(InHandler: MoneyTransferrer)] +impl MoneyTransferrer where - Quantity: CheckedAdd + CheckedSub, - UserId: Ord + Clone, - Currency: Ord + Clone, + UserId: Eq, { async fn transfer_money( &self, @@ -222,22 +297,72 @@ where recipient: &UserId, currency: &Currency, quantity: &Quantity, - #[implicit] user_balances: &Arc>>, ) -> Result<(), Error> { - let mut balances = user_balances.lock().await; - /* debit the sender, credit the recipient, raising on overflow or missing accounts */ - Ok(()) + if sender != recipient { + InHandler::transfer_money(self, sender, recipient, currency, quantity).await + } else { + Err(Self::raise_http_error( + ErrBadRequest, + format!("cannot transfer with the same sender and recipient: {sender}"), + )) + } + } +} +``` + +A real deployment would swap `UseMockedApp` for a database-backed provider. Since the backend is selected per context in the wiring, replacing it with a `UsePostgres` provider that implements the same capabilities changes which backend runs without touching a single endpoint or wrapper. + +## Organizing the wiring with a namespace + +A concrete context becomes the running application by resolving every abstract type and component. Rather than spell all of that out on the context, the application lifts it into a reusable **namespace** — the [namespaces-and-prefixes guide](../guides/namespaces-and-prefixes.md) develops this technique in full; the essentials are shown here. `MockNamespace` inherits the built-in `DefaultNamespace` and wires the pieces that have no `#[cgp_impl]` block of their own — the concrete error type, the per-detail HTTP-error dispatch, and the abstract type choices — as [namespace body entries](../reference/macros/cgp_namespace.md) keyed by the paths the `#[prefix]` attributes established: + +```rust +cgp_namespace! { + new MockNamespace: DefaultNamespace { + @cgp.core.error.ErrorTypeProviderComponent: + UseType, + + @app.error.HttpErrorRaiserComponent. Code.String: + DisplayHttpError, + @app.error.HttpErrorRaiserComponent. Code.anyhow::Error: + HandleHttpErrorWithAnyhow, + + @app.auth.types.{ + UserIdTypeProviderComponent, + PasswordTypeProviderComponent, + HashedPasswordTypeProviderComponent, + }: + UseType, + @app.finance.types.QuantityTypeProviderComponent: + UseType, + @app.finance.types.CurrencyTypeProviderComponent: + UseType, } } ``` -The `#[implicit]` argument reads the same `user_balances` field a getter would, but as a `&Arc>` bound at the top of the method rather than through a declared accessor — the preferred form for a field a provider reads from its own context. The request-field getters `HasLoggedInUser` and `HasBasicAuthHeader`, by contrast, stay [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) traits, because they read from the *request* type and are required as `where` bounds on it (`Request: HasBasicAuthHeader`) — a case an implicit argument, which reads only from `self`, cannot cover. +The business-logic providers, which *do* have `#[cgp_impl]` blocks, register themselves into the same namespace from their own definition with the [`#[default_impl(@path in MockNamespace)]`](../reference/traits/default_namespace.md) attribute seen on `UseMockedApp`'s `UserBalanceQuerier` above — so `MockNamespace` resolves `@app.finance.UserBalanceQuerierComponent` to `UseMockedApp` without a line in its body. Registering the wiring next to the implementation it wires is what keeps the namespace body down to the handful of entries that have nowhere else to live. -A real deployment would swap this one provider for a database-backed one. Since `UseMockedApp` is selected per context in the wiring, replacing it with a `UsePostgres` provider that implements the same capabilities changes which backend runs without touching a single endpoint or wrapper. +The application's API surface is a separate reusable table keyed by the API marker, so any backend can pull it in: -## Wiring a context +```rust +cgp_namespace! { + new DefaultApiHandlers { + QueryBalanceApi: + HandleFromRequest< + AxumQueryBalanceRequest, + ResponseToJson>>, + >, + TransferApi: + HandleFromRequest< + AxumTransferRequest, + UseBasicAuth>, + >, + } +} +``` -A concrete context becomes the running application by binding every abstract type and component in one place. `MockApp` holds the in-memory state and wires it all together: the domain types resolve to concrete types via [`UseType`](../reference/providers/use_type.md), the business capabilities resolve to `UseMockedApp`, and each endpoint of `ApiHandler` resolves to its own wrapper pipeline through an inner [`UseDelegate`](../reference/providers/use_delegate.md) table keyed by the API marker: +With the backend in a namespace and the API surface in a table, the context's own wiring shrinks to three statements: join the namespace, pull the API handlers onto the `ApiHandler` dispatch path with a `for` loop, and override the one component it wants to treat specially — wrapping money transfers in `NoTransferToSelf`: ```rust #[derive(HasField, Default)] @@ -248,48 +373,28 @@ pub struct MockApp { delegate_components! { MockApp { - ErrorTypeProviderComponent: - UseType, - [ - UserIdTypeProviderComponent, - PasswordTypeProviderComponent, - HashedPasswordTypeProviderComponent, - ]: - UseType, - QuantityTypeProviderComponent: - UseType, - CurrencyTypeProviderComponent: - UseType, - [ - PasswordCheckerComponent, - UserHashedPasswordQuerierComponent, - UserBalanceQuerierComponent, - MoneyTransferrerComponent, - ]: - UseMockedApp, - ApiHandlerComponent: - UseDelegate>>>, - TransferApi: - HandleFromRequest>>, - }>, + namespace MockNamespace; + + for in DefaultApiHandlers { + @app.api.ApiHandlerComponent.Key: Value, + } + + @app.finance.MoneyTransferrerComponent: + NoTransferToSelf, } } ``` -The two endpoints assemble different pipelines from the same parts. Both decode an Axum request and authenticate, but the balance query also JSON-encodes its response while the transfer returns nothing, so only the query wraps in `ResponseToJson`. The nested `UseDelegate` builds the per-`Api` lookup table inline, so a call to `handle_api` with the `TransferApi` marker resolves to the transfer pipeline and one with `QueryBalanceApi` to the balance pipeline. +Each remaining line states a decision rather than a mechanical fact: `MockApp` uses the mock backend, serves the default API surface, and guards transfers. The override works because `MockNamespace` deliberately does *not* register `@app.finance.MoneyTransferrerComponent` — the base `MoneyTransferrer` provider is used only as the inner handler of `NoTransferToSelf` — so the context is free to wire that path directly; had the namespace claimed it, the two entries would conflict. The two endpoints assemble different pipelines from the same parts: both decode and authenticate, but only the balance query wraps its response in `ResponseToJson`, since the transfer returns nothing. Because CGP wiring is [checked lazily](../concepts/check-traits.md), a companion [`check_components!`](../reference/macros/check_components.md) block proves at compile time that every endpoint is fully satisfied, listing the API markers to verify for the generic `ApiHandler` component: ```rust check_components! { MockApp { - MoneyTransferrerComponent, + QuantityTypeProviderComponent, UserBalanceQuerierComponent, + MoneyTransferrerComponent, ApiHandlerComponent: [ QueryBalanceApi, TransferApi, @@ -323,15 +428,23 @@ impl CanHandleApiSend for MockApp { } } -impl CanHandleApiSend for MockApp { - async fn handle_api_send( - &self, - api: PhantomData, - request: Self::Request, - ) -> Result { - self.handle_api(api, request).await +// … and the same one-line forwarding impl for TransferApi. +``` + +Each impl just forwards to `handle_api`, but at a concrete context and API the awaited future is a concrete type whose `Send`-ness the compiler can confirm — which is why the impls cannot be folded into one generic blanket impl. The full reasoning, and why this is a stand-in for the Return Type Notation stable Rust lacks, is in [recovering `Send` bounds](../concepts/send-bounds.md). + +With `CanHandleApiSend` in hand, the routing layer bounds `App: CanHandleApiSend` and mounts each endpoint. An `add_route` on an Axum `Router` reads the request out of the HTTP layer, calls `handle_api_send`, and maps a raised `AppError` to its status code, so a single `add_main_api_routes` assembles the whole service: + +```rust +impl CanAddMainApiRoutes for Router> +where + Self: CanAddRoute + CanAddRoute, +{ + fn add_main_api_routes(self) -> Self { + self.add_route(PhantomData::<(QueryBalanceApi, GetMethod)>, "/balance") + .add_route(PhantomData::<(TransferApi, PostMethod)>, "/transfer") } } ``` -Each impl just forwards to `handle_api`, but at a concrete context and API the awaited future is a concrete type whose `Send`-ness the compiler can confirm — which is exactly why the impls cannot be folded into one generic blanket impl. The full reasoning, and why this is a stand-in for the Return Type Notation stable Rust lacks, is in [recovering `Send` bounds](../concepts/send-bounds.md). With `CanHandleApiSend` in hand, an Axum route handler can bound `App: CanHandleApiSend` and spawn the handler safely, completing the path from a request on the wire to one of the wired endpoint pipelines. +The `main` function then constructs a `MockApp`, builds the router with `add_main_api_routes`, and serves it — completing the path from a request on the wire, through the decode-authenticate-handle-encode pipeline the namespace wired, to a JSON response or a status-coded error. diff --git a/docs/guides/README.md b/docs/guides/README.md index 47de92ef..ed76da00 100644 --- a/docs/guides/README.md +++ b/docs/guides/README.md @@ -10,25 +10,22 @@ A guide leans on the other three rather than restating them. It links to the ref ## The catalog -The authoring rules for these documents live in [../AGENTS.md](../AGENTS.md). Each guide below names a decision you face when writing CGP and walks through how to make it. +The authoring rules for these documents live in [../AGENTS.md](../AGENTS.md). Each guide below names a decision you face when writing CGP and walks through how to make it; the [Summary](#summary) at the end condenses all of them into one cheat-sheet, so read it first for the recommendations and follow a link for the full before/after mapping and the rules. -- [Organizing wiring with namespaces and prefixes](namespaces-and-prefixes.md) — how to keep a growing `delegate_components!` table short: grouping components under path prefixes with `#[prefix]`, binding providers to a namespace with `#[default_impl]`, and merging multiple providers into one flattened table, worked as a refactoring of a real application. -- [Debugging CGP compile errors](debugging.md) — the playbook for tracing a wiring failure back to its cause: reading the error's shape, moving the error to the wiring site with checks, reducing to a minimal reproduction, inspecting the macro expansion, and a decoder for the errors you actually see. - -The **modern idioms** are a family of small, related choices — each a shift from an explicit form to a vanilla-looking one — so each has its own focused guide. The [Summary](#summary) below condenses these idioms *and* the two guides above into one cheat-sheet; go straight to a guide when you want the before/after mapping and the rules in full: - -- [Writing providers the modern way](writing-providers.md) — `#[cgp_impl]` in consumer-trait shape, omitting the context parameter. +- [Writing providers](writing-providers.md) — `#[cgp_impl]` in consumer-trait shape, omitting the context parameter, instead of the inside-out provider forms. - [Declaring a provider's dependencies](declaring-dependencies.md) — `#[uses]` and `#[use_provider]` instead of hand-written `where` bounds. - [Reading context fields](reading-context-fields.md) — `#[implicit]` arguments instead of getter traits. -- [Importing abstract types](importing-abstract-types.md) — `#[use_type]` aliases and the concrete-type equality form. +- [Importing abstract types](importing-abstract-types.md) — `#[use_type]` aliases and the concrete-type equality form instead of a supertrait plus `Self::Type`. - [Adding capability supertraits](capability-supertraits.md) — `#[extend]` instead of native `:` supertrait syntax. - [Dispatching a component per type](dispatching-per-type.md) — the `open` statement or a namespace instead of a `UseDelegate` table. +- [Organizing wiring with namespaces and prefixes](namespaces-and-prefixes.md) — keeping a growing `delegate_components!` table short with path prefixes, namespaces, and per-type defaults, worked as a refactoring of a real application. +- [Debugging CGP compile errors](debugging.md) — tracing a wiring failure back to its cause: reading the error's shape, moving the error to the wiring site with checks, reducing to a minimal reproduction, inspecting the macro expansion, and a decoder for the errors you actually see. ## Summary This section condenses every guide above into one quick reference. Read it for the recommendation and the reason; follow a link when you need the full before/after mapping, the corner cases, or a worked example. -**Write CGP that looks like ordinary Rust.** The explicit forms — an inside-out provider-trait `impl`, `where`-clause dependencies, `::Type` abstract types, `UseDelegate` dispatch tables — are exactly what the macros desugar to, so you keep reading them in generated code and older codebases, but you should *write* the modern idiom in all new code and reach for an explicit form only when a construct genuinely cannot express the case. Each row below is one such shift: +**Write CGP that looks like ordinary Rust.** The explicit forms — an inside-out provider-trait `impl`, `where`-clause dependencies, `::Type` abstract types, `UseDelegate` dispatch tables — are exactly what the macros desugar to, so you keep reading them in generated code and older codebases, but you should *write* the vanilla-looking form in all new code and reach for an explicit form only when a construct genuinely cannot express the case. Each row below is one such shift: | When you… | Prefer | Instead of | |---|---|---| diff --git a/docs/guides/capability-supertraits.md b/docs/guides/capability-supertraits.md index bdca3e49..fbf30990 100644 --- a/docs/guides/capability-supertraits.md +++ b/docs/guides/capability-supertraits.md @@ -2,7 +2,7 @@ A CGP component often depends on another capability, and this guide is about declaring that dependency with `#[extend]`, which reads as importing a capability, rather than the native `:` supertrait syntax, which reads as inheritance. -This is one of the [modern idioms](README.md#summary); it is the capability counterpart to [importing abstract types](importing-abstract-types.md), which handles a supertrait whose *type* the signature names. +This guide is the capability counterpart to [importing abstract types](importing-abstract-types.md), which handles a supertrait whose *type* the signature names. ## Add supertraits with `#[extend]`, not native `:` syntax @@ -31,4 +31,4 @@ pub trait CanGreet { - [Importing abstract types](importing-abstract-types.md) — use `#[use_type]` instead when the supertrait carries an associated type the signature names. - [Declaring dependencies](declaring-dependencies.md) — the `#[uses]` counterpart for a capability the implementation uses privately rather than re-exporting. -- [Modern idioms](README.md#summary) — the overview that ties the idioms together. +- [Guides summary](README.md#summary) — the cheat-sheet across all the guides. diff --git a/docs/guides/declaring-dependencies.md b/docs/guides/declaring-dependencies.md index 3126b1e8..a90e88ad 100644 --- a/docs/guides/declaring-dependencies.md +++ b/docs/guides/declaring-dependencies.md @@ -2,7 +2,7 @@ A provider states what it needs from its context in its `where` clause, and this guide is about writing those needs as attributes that read like imports rather than as hand-written trait bounds. -This is one of the [modern idioms](README.md#summary); it follows on from [writing the provider header](writing-providers.md), which leaves the bounds off the header for these attributes to supply. +This guide follows on from [writing the provider header](writing-providers.md), which leaves the bounds off the header for these attributes to supply. ## Declare dependencies with `#[uses]` and `#[use_provider]` @@ -37,4 +37,4 @@ Both attributes desugar to the same `where` predicates they replace. When a prov - [Writing providers](writing-providers.md) — the `#[cgp_impl]` header these attributes attach to. - [Importing abstract types](importing-abstract-types.md) — where an abstract-type pin belongs instead of `#[uses]`. -- [Modern idioms](README.md#summary) — the overview and the list of cases where an explicit `where` clause is still right. +- [Guides summary](README.md#summary) — the cheat-sheet across all the guides, and the list of cases where an explicit `where` clause is still right. diff --git a/docs/guides/dispatching-per-type.md b/docs/guides/dispatching-per-type.md index 2a2a24af..61fb02ab 100644 --- a/docs/guides/dispatching-per-type.md +++ b/docs/guides/dispatching-per-type.md @@ -2,7 +2,7 @@ A component that is generic over a type parameter often wants a different provider per value of that parameter, and this guide is about doing that with the `open` statement or a namespace rather than the legacy `UseDelegate` nested table. -This is one of the [modern idioms](README.md#summary), and it connects directly to [organizing wiring with namespaces and prefixes](namespaces-and-prefixes.md), which develops the namespace side of the choice in depth. +This guide connects directly to [organizing wiring with namespaces and prefixes](namespaces-and-prefixes.md), which develops the namespace side of the choice in depth. ## Dispatch per type with `open` and namespaces, not `UseDelegate` @@ -20,7 +20,7 @@ delegate_components! { } ``` -while the modern form dispatches inline with `open`: +while dispatching inline with `open`: ```rust delegate_components! { @@ -35,9 +35,9 @@ delegate_components! { Because `open` and namespaces ride `RedirectLookup`, **a new component you intend to dispatch this way does not need the [`#[derive_delegate(UseDelegate)]`](../reference/attributes/derive_delegate.md) attribute at all** — that attribute exists only to generate the `UseDelegate` provider the legacy nested-table form relies on. You will still see `#[derive_delegate]` on some CGP-shipped components, such as the error and handler families, which carry it so existing `UseDelegate`-based wiring keeps working; but code that dispatches only through `open` or a namespace can omit it. -Choose between the two modern forms by scope. Prefer `open` for a self-contained context wiring its own components directly — it folds the per-type entries into the context's own table with no separate type. Reach for a [namespace](namespaces-and-prefixes.md) when a reusable, inheritable dispatch table is worth sharing across contexts, or when a single generic component is served by several providers whose per-type entries you want to merge into one flat table. +Choose between `open` and a namespace by scope. Prefer `open` for a self-contained context wiring its own components directly — it folds the per-type entries into the context's own table with no separate type. Reach for a [namespace](namespaces-and-prefixes.md) when a reusable, inheritable dispatch table is worth sharing across contexts, or when a single generic component is served by several providers whose per-type entries you want to merge into one flat table. ## Related guides - [Organizing wiring with namespaces and prefixes](namespaces-and-prefixes.md) — the full namespace treatment, including flattening multi-provider dispatch that `open` alone cannot. -- [Modern idioms](README.md#summary) — the overview that ties the idioms together. +- [Guides summary](README.md#summary) — the cheat-sheet across all the guides. diff --git a/docs/guides/importing-abstract-types.md b/docs/guides/importing-abstract-types.md index d005bbed..48ad669e 100644 --- a/docs/guides/importing-abstract-types.md +++ b/docs/guides/importing-abstract-types.md @@ -2,7 +2,7 @@ CGP abstracts over types with associated types on components, and this guide is about bringing such a type into a definition as a plain alias rather than a supertrait plus a fully-qualified `Self::Type` at every use. -This is one of the [modern idioms](README.md#summary). It applies inside [`#[cgp_component]`](../reference/macros/cgp_component.md) definitions and [`#[cgp_impl]`](../reference/macros/cgp_impl.md)/[`#[cgp_fn]`](../reference/macros/cgp_fn.md) providers alike, and it is the recommended form for the built-in error type as much as for a domain type. +It applies inside [`#[cgp_component]`](../reference/macros/cgp_component.md) definitions and [`#[cgp_impl]`](../reference/macros/cgp_impl.md)/[`#[cgp_fn]`](../reference/macros/cgp_fn.md) providers alike, and is the recommended form for the built-in error type as much as for a domain type. ## Import abstract types with `#[use_type]` @@ -31,7 +31,7 @@ When a definition imports types from several traits, combine them into one `#[us ## Pinning an abstract type to a concrete one -On a `#[cgp_impl]` or `#[cgp_fn]`, `#[use_type]` also *pins* an abstract type to a concrete one with the equality form `{Assoc = Type}`, which is the modern replacement for a hand-written `where Self: HasXType` clause. Writing `#[use_type(HasErrorType.{Error = AppError})]` emits `Self: HasErrorType` (and rewrites any bare `Error`), so a provider fixed to a concrete error type moves that pin out of its `where` clause and into the import. The right-hand side may name another imported alias to *unify* two abstract types — `#[use_type(HasPasswordType.Password, HasHashedPasswordType.{HashedPassword = Password})]` emits `Self: HasHashedPasswordType::Password>`. The equality form is rejected on `#[cgp_component]`, since a trait definition cannot carry the impl-side constraint it produces — this is the one place a pin stays in a hand-written `where` clause, and only for equality on a trait you would never `#[use_type]` from. +On a `#[cgp_impl]` or `#[cgp_fn]`, `#[use_type]` also *pins* an abstract type to a concrete one with the equality form `{Assoc = Type}`, which is the replacement for a hand-written `where Self: HasXType` clause. Writing `#[use_type(HasErrorType.{Error = AppError})]` emits `Self: HasErrorType` (and rewrites any bare `Error`), so a provider fixed to a concrete error type moves that pin out of its `where` clause and into the import. The right-hand side may name another imported alias to *unify* two abstract types — `#[use_type(HasPasswordType.Password, HasHashedPasswordType.{HashedPassword = Password})]` emits `Self: HasHashedPasswordType::Password>`. The equality form is rejected on `#[cgp_component]`, since a trait definition cannot carry the impl-side constraint it produces — this is the one place a pin stays in a hand-written `where` clause, and only for equality on a trait you would never `#[use_type]` from. When a capability supertrait has no associated type to import — a plain capability like `HasName` — add it with [`#[extend]`](capability-supertraits.md) rather than `#[use_type]`. Use `#[use_type]` when the signature names the trait's associated type; use `#[extend]` when it only calls the trait's methods. @@ -39,4 +39,4 @@ When a capability supertrait has no associated type to import — a plain capabi - [Capability supertraits](capability-supertraits.md) — the companion for a supertrait that contributes a capability rather than a type. - [Declaring dependencies](declaring-dependencies.md) — where an abstract-type pin moves *from* (a `#[uses]` or hand-written `where`). -- [Modern idioms](README.md#summary) — the overview and the local-associated-type exception restated among the other still-explicit cases. +- [Guides summary](README.md#summary) — the cheat-sheet across all the guides, with the local-associated-type exception restated among the other still-explicit cases. diff --git a/docs/guides/namespaces-and-prefixes.md b/docs/guides/namespaces-and-prefixes.md index d0776c9d..19b1b048 100644 --- a/docs/guides/namespaces-and-prefixes.md +++ b/docs/guides/namespaces-and-prefixes.md @@ -311,4 +311,4 @@ The three techniques form a ladder, and most applications should climb only as f - [`DefaultNamespace`, `DefaultImpls1`, `DefaultImpls2`](../reference/traits/default_namespace.md) — the lookup traits behind namespaces and the `#[default_impl]` attribute. - [`delegate_components!`](../reference/macros/delegate_components.md) — the wiring table these techniques restructure, including the `open` statement for the self-contained case. - [Namespaces](../concepts/namespaces.md) — the mechanism (inheritance, `RedirectLookup`, and paths) that makes the preset pattern work. -- [Modern idioms summary](README.md#summary) — the condensed reference for the provider, dependency, and abstract-type idioms the code above uses. +- [Guides summary](README.md#summary) — the condensed cheat-sheet for the provider, dependency, and abstract-type idioms the code above uses. diff --git a/docs/guides/reading-context-fields.md b/docs/guides/reading-context-fields.md index 991be395..1ea2d85c 100644 --- a/docs/guides/reading-context-fields.md +++ b/docs/guides/reading-context-fields.md @@ -2,7 +2,7 @@ A provider reads values from its context's fields, and this guide is about doing that with an argument that looks like an ordinary parameter rather than a getter trait declared just to fetch it. -This is one of the [modern idioms](README.md#summary); it is the value-level counterpart to [writing providers](writing-providers.md) in vanilla-looking form. +This guide is the value-level counterpart to [writing providers](writing-providers.md) — the same move from visible machinery to an ordinary-looking parameter, applied to reading a field. ## Read context fields with implicit arguments, not getter traits @@ -43,4 +43,4 @@ Avoid [`#[cgp_getter]`](../reference/macros/cgp_getter.md) in ordinary code. It - [Writing providers](writing-providers.md) — the `#[cgp_impl]` provider these arguments live in. - [Importing abstract types](importing-abstract-types.md) — for a getter whose return type is an abstract type shared across contexts. -- [Modern idioms](README.md#summary) — the overview and the cases where a getter trait is still the right tool. +- [Guides summary](README.md#summary) — the cheat-sheet across all the guides, and the cases where a getter trait is still the right tool. diff --git a/docs/guides/writing-providers.md b/docs/guides/writing-providers.md index 457a4fbe..be3b46f9 100644 --- a/docs/guides/writing-providers.md +++ b/docs/guides/writing-providers.md @@ -1,8 +1,8 @@ -# Writing providers the modern way +# Writing providers A provider can be written at three levels of sugar over the same machinery, and this guide is about choosing the highest one — writing a provider that reads like an ordinary trait `impl` rather than the inside-out provider-trait form the macros desugar to. -This is one of the [modern idioms](README.md#summary); it pairs with [declaring a provider's dependencies](declaring-dependencies.md) and [reading its context's fields](reading-context-fields.md), which cover what goes inside the provider once its header is written this way. +This guide pairs with [declaring a provider's dependencies](declaring-dependencies.md) and [reading its context's fields](reading-context-fields.md), which cover what goes inside the provider once its header is written this way. ## Write providers with `#[cgp_impl]`, not the raw provider forms @@ -22,7 +22,7 @@ where } ``` -becomes, with the modern idiom and [`#[implicit]`](../reference/attributes/implicit.md) arguments: +becomes, with [`#[implicit]`](../reference/attributes/implicit.md) arguments: ```rust #[cgp_impl(new RectangleArea)] @@ -67,4 +67,4 @@ impl AreaCalculator { - [Declaring a provider's dependencies](declaring-dependencies.md) — state the `where` bounds this idiom leaves off the header with `#[uses]` and `#[use_provider]`. - [Reading context fields](reading-context-fields.md) — pull field values into a provider with `#[implicit]` arguments, as the first example does. -- [Modern idioms](README.md#summary) — the overview that ties these idioms together and lists when an explicit form is still right. +- [Guides summary](README.md#summary) — the cheat-sheet across all the guides, and the list of when an explicit form is still right. From 19a74a669c1a20a2eac54d10fd1530207edda8b8 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Sun, 5 Jul 2026 21:49:01 +0200 Subject: [PATCH 6/6] Fix CI non-determinism --- crates/tests/cgp-compile-fail-tests/README.md | 11 +++++++++++ rust-toolchain.toml | 9 +++++++++ 2 files changed, 20 insertions(+) diff --git a/crates/tests/cgp-compile-fail-tests/README.md b/crates/tests/cgp-compile-fail-tests/README.md index 49025d0d..883db815 100644 --- a/crates/tests/cgp-compile-fail-tests/README.md +++ b/crates/tests/cgp-compile-fail-tests/README.md @@ -65,3 +65,14 @@ when an intended change alters a diagnostic, regenerate the snapshots with `TRYBUILD=overwrite cargo test -p cgp-compile-fail-tests`, then review the diff before committing — an unexpected change to an `acceptable/` diagnostic, or a `problematic/` fixture that stops failing, is a signal worth reading closely. + +Regenerate with the pinned toolchain, because a snapshot can embed standard-library +source. When an error points into the standard library — `option_slice.stderr` is +the only current case — `rustc` prints the referenced source line (`pub enum +Option {`) if the `rust-src` component is installed and omits it otherwise, while +`trybuild` normalizes the path to `$RUST/...` either way. The difference is therefore +invisible in the path and shows up only as an added or missing snippet line, which +reads as spurious non-determinism between machines. The pinned toolchain declares +`rust-src` in [rust-toolchain.toml](../../../rust-toolchain.toml) precisely so this +renders the same everywhere; blessing under a toolchain that lacks the component +would silently strip the snippet and reintroduce the mismatch on CI. diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 10001973..f3db9c05 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,12 @@ [toolchain] channel = "1.96.1" profile = "default" + +# Pin rust-src so compiler diagnostics are deterministic across environments. When +# rust-src is present, rustc resolves the remapped `/rustc//...` std paths to +# on-disk source and renders standard-library snippets in errors (e.g. the `Option` +# definition); without it, the snippet is omitted. The cgp-compile-fail-tests +# trybuild `.stderr` snapshots (option_slice.stderr) are blessed with those +# snippets, so CI — which installs the toolchain from this file — must have rust-src +# too, or it mismatches. The `default` profile does not include rust-src on its own. +components = ["rust-src"]