diff --git a/AGENTS.md b/AGENTS.md index 5bcba628..7873cea5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -175,74 +175,89 @@ readability edit introduce a behavioral change. ### Scrutinize the macro codegen -A CGP macro is only as correct as the code it emits, so review the implementation against the ways -its input can be parsed and its output expanded, not just the happy path its tests exercise. Cover -every one of these concerns, since a gap in any of them is a latent miscompilation waiting for the -right input: - -- **Review every supported attribute.** Enumerate the attributes the macro accepts and confirm each - is parsed, validated, mutually constrained, and rejected-when-unknown exactly as documented — an - unrecognized attribute should fail with a spanned error, a mutually exclusive pair should error - when both appear, and a duplicate should not be silently accepted (or silently accepted for one - attribute while rejected for another). -- **Prefer `parse_internal!`/`parse_internal` over `parse2` or `parse_quote!`.** When constructing a - `syn` node from quasi-quoted tokens, build it with `parse_internal!` so a malformed fragment fails - with an error naming the target type and the offending tokens (prelude prefix stripped) rather than - a bare parse error. Reserve `parse2` for re-parsing tokens already known to be valid (a span - override, say), and treat every `parse_quote!` as an assertion that parsing can never fail. -- **Return `syn::Result` wherever parsing can fail.** A function that parses anything should thread - `syn::Result` and propagate the error, rather than `parse_quote!`-ing and risking a panic that - aborts the compiler with no usable diagnostic. Use the panicking `parse_quote!` only when it is - trivially obvious — from the surrounding, fully-controlled tokens — that the parse cannot fail. -- **Enumerate every way the input can be parsed.** For each parser and `parse_internal!` call, think - through the full range of inputs a user could write — path-qualified types, generic and lifetime - parameters, arrays, tuples, empty lists, turbofish, associated-type bindings — and confirm none - reaches a parser that fails with a confusing internal error. Reject malformed or unsupported input - early, at the macro's own parse stage with a clear spanned message, rather than letting the failure - surface deep inside internal fragment parsing or in the expanded code. -- **Enumerate every way the output can expand.** Walk the shapes the expansion can take across the - whole input space and confirm none can produce invalid Rust — no duplicate or conflicting `impl` - blocks from a cartesian expansion, no unbound or doubly-declared generic parameter, no empty - expansion that silently checks nothing, and no clash on a generated identifier. When you find a - case whose expansion fails to compile, capture it as a `trybuild` fixture in - `cgp-compile-fail-tests` — under `problematic/` when the macro should have rejected it or emitted - wrong code, or `acceptable/` when the failure is one CGP deliberately defers to the compiler — and - document it in the macro's implementation document, per - [crates/tests/AGENTS.md](crates/tests/AGENTS.md) and - [docs/implementation/AGENTS.md](docs/implementation/AGENTS.md). -- **Scrutinize generics with care.** Generic parameters take many forms — lifetimes, types, consts, - and the distinction between *impl* generics (`impl`) and *type* generics (the `` in - `Foo`) — and mixing them produces subtly wrong output. Confirm the macro keeps the kinds - separate, renders each in the right position, merges parameters from different sources without - colliding, and binds every parameter that appears in the generated header so nothing is left free. -- **Fully qualify every CGP construct in the expansion.** Any CGP item the expansion references must - be emitted through the `crate::exports` markers so it resolves as `::cgp::macro_prelude::`, - never as a bare or hand-written path — this is what lets a user with only `cgp` in scope compile - the output. Grep the codegen for any CGP name that is not interpolated from an `exports` marker. -- **Aim every generated item's span at the token the user wrote (watch for `call_site` leaks).** A - macro builds its output with `parse_internal!`/`quote!`, which stamp the structural tokens — the - `impl` keyword, the trait reference, the self type — with the macro's `call_site` span (the whole - invocation), while only the interpolated user fragments keep a narrower span. A compiler error that - reports on an item's header then underlines the *entire macro block* instead of the entry, - attribute, or impl the user actually wrote: a coherence conflict (`E0119`) between two generated - impls, an unsatisfied bound, or a name-resolution failure all read as "somewhere in this macro." - Re-span each generated item onto its originating token, following the - `delegate_components!`/`check_components!` pattern — the shared - [`override_span`](crates/macros/cgp-macro-core/src/functions/override_span.rs) helper re-spans an - item's tokens (restore the generics afterward so a per-entry generic's `E0207` keeps pointing at the - `` the user wrote), and where the originating token is *synthesized* and has lost its span (a - `PathCons<..>` nest, a `Symbol`'s `Chars` encoding), carry an explicit span field through the - evaluated form as `EvaluatedCheckEntry.span` and `EvaluatedDelegateEntry.span` do — mirroring - `Symbol`, which keeps its parse-time span and stamps its output with `quote_spanned!`. The same leak - lurks in the provider macros (`#[cgp_impl]`, `#[cgp_provider]`, `#[cgp_new_provider]`) and every - other expansion, so confirm a duplicated or conflicting generated item points at the impl or - attribute the user wrote, not the macro name. These spans are testable: a `trybuild` `.stderr` - fixture records the exact line and column of each caret, so a span regression changes the snapshot - (see the `acceptable/delegate_components/duplicate_*` fixtures). - -Beyond these, weigh the concerns that recur across the macro suite: the hygiene of the reserved -identifiers the expansion introduces (`__Component__`, `__Context__`, and the like) and the -idempotency of the expansion when the same entry is listed more than once. +A CGP macro is only as correct as the code it emits, so review it against the full space of inputs +it can parse and outputs it can expand, not the happy path its tests exercise. A gap in any area +below is a latent miscompilation — or a broken editor experience — waiting for the right input. +**Apply every area to every macro you review**, not only the ones that already have a fixture or a +worked example for it; these concerns recur across the whole suite, and a macro that has never been +audited for one of them (spans especially) most likely has the bug. The areas below are the *what to +check*; the [cross-cutting implementation notes](docs/implementation/README.md#cross-cutting-implementation-notes) +explain *why the code behaves this way*, and most areas link out to the note or process doc that +carries their full detail. + +#### Attributes + +Enumerate the attributes the macro accepts and confirm each is parsed, validated, mutually +constrained, and rejected when unknown: a bad attribute should fail with a spanned error, a mutually +exclusive pair should error when both appear, and a duplicate should not be silently accepted (nor +accepted for one attribute while rejected for another). + +#### Parsing the input + +Build every `syn` node from quasi-quoted tokens with +[`parse_internal!`](docs/implementation/macros/parse_internal.md) rather than `parse2`/`parse_quote!`, +and thread `syn::Result` so a malformed fragment propagates a named error instead of panicking; +reserve the panicking `parse_quote!` for tokens trivially guaranteed to parse and `parse2` for +re-parsing tokens already known valid. Reject malformed or unsupported input early, at the macro's +own parse stage with a spanned message, rather than letting it surface deep inside fragment parsing. +Walk the full input space — path-qualified types, generics, lifetimes, arrays, tuples, turbofish, +associated-type bindings — and confirm none reaches a parser that fails obscurely; since `syn` parses +far more leniently than Rust accepts, validate against CGP's restricted argument types. See +[Parsing](docs/implementation/README.md#parsing-build-with-parse_internal-and-distrust-syns-leniency). + +#### Expanding the output + +Walk the shapes the expansion can take across the whole input space and confirm none produces invalid +Rust — no conflicting `impl` blocks from a cartesian expansion, no unbound or doubly-declared generic, +no empty expansion that checks nothing, no clash on a generated identifier. When an expansion fails to +compile, capture it as a `trybuild` fixture in `cgp-compile-fail-tests` — `problematic/` when the +macro should have rejected it, `acceptable/` when CGP defers the failure to the compiler — and +document it in the macro's implementation document, per [crates/tests/AGENTS.md](crates/tests/AGENTS.md) +and [docs/implementation/AGENTS.md](docs/implementation/AGENTS.md). + +#### Generics + +Keep the *kinds* (lifetime, type, const) and the *roles* (impl generics `impl` versus type +generics, the `` in `Foo`) distinct, render each in the right position, merge parameters from +different sources without colliding, and bind every parameter that appears in a generated header so +nothing is left free. See +[Generic-parameter insertion](docs/implementation/README.md#generic-parameter-insertion-and-lifetime-ordering) +and [keeping the kinds distinct](docs/implementation/README.md#generics-keep-the-kinds-and-roles-distinct). + +#### Qualified paths and hygiene + +Emit every CGP item through a `crate::exports` marker so it resolves as `::cgp::macro_prelude::`, +never as a bare or hand-written path — grep the codegen for any CGP name not interpolated from an +`exports` marker. Give the reserved identifiers the expansion introduces the double-underscore form +(`__Context__`, `__Provider__`, `__Component__`, …) so they cannot clash with a user's names, and keep +the expansion idempotent when the same entry is listed more than once. See +[Hygiene](docs/implementation/README.md#hygiene-exports-markers-and-reserved-identifiers). + +#### Spans: for the compiler *and* the IDE + +Span placement is a correctness concern for two tools at once, and it applies to **every macro that +emits items** — `#[cgp_impl]`, `#[cgp_provider]`, `#[cgp_new_provider]`, `#[cgp_type]`, +`#[cgp_getter]`, and the derives — not just `delegate_components!`/`check_components!`. By default +`parse_internal!`/`quote!` stamp generated tokens with the macro's `call_site` span (the whole +invocation), which misleads both tools, so confirm both for each macro: + +- **Compiler carets.** An error on a generated item's header — a coherence conflict (`E0119`), an + unsatisfied bound, a name-resolution failure — must point at the entry the user wrote, not the whole + macro block. Re-span each generated item onto its originating token: with + [`override_item_span`](crates/macros/cgp-macro-core/src/functions/override_span.rs) for a generated + impl, a carried span field where the origin token is synthesized (`EvaluatedCheckEntry.span`, + `EvaluatedDelegateEntry.span`), or reuse of the user's own `for` token. +- **IDE go-to-definition.** rust-analyzer maps a source token to its expansion by source range alone, + ignoring hygiene, so never drag a resolvable reference (`IsProviderFor`, `DelegateComponent`) onto a + user token's range, and span a *derived name* (a component marker) on the user identifier it derives + from, not `Span::call_site()`. A leaked derived name makes go-to-definition on a reference offer the + defining macro alongside the item. + +Only the caret half is pinned by a test (a `trybuild` `.stderr` fixture records each caret's exact +position, so a regression changes the snapshot). The IDE half has no fixture and must be checked by +hand in an editor — and cross-crate, since the derived-name leak is invisible from inside the defining +crate. See [Spans](docs/implementation/README.md#spans-aim-generated-items-at-the-token-the-user-wrote) +for the full mechanism and the `delegate_components!`/`#[cgp_impl]` worked examples. ### Keep every view in sync and verify diff --git a/crates/macros/cgp-macro-core/src/functions/override_span.rs b/crates/macros/cgp-macro-core/src/functions/override_span.rs index 65d72f13..0aa6b3bc 100644 --- a/crates/macros/cgp-macro-core/src/functions/override_span.rs +++ b/crates/macros/cgp-macro-core/src/functions/override_span.rs @@ -1,22 +1,18 @@ -use proc_macro2::Span; +use proc_macro2::{Span, TokenTree}; use quote::ToTokens; use syn::parse::Parse; use syn::parse2; -/// Re-span every top-level token of `body` to `span` and re-parse it into `T`. +/// Force every top-level token of `body` onto `span`, unconditionally, and +/// re-parse it into `T`. /// -/// A macro builds its output from quasi-quoted tokens, which carry the whole -/// invocation's `call_site` span; a compiler error on such an item therefore -/// highlights the entire macro block. Re-spanning the tokens onto the specific -/// user-written token that produced the item (a component key, a wiring entry) -/// makes the diagnostic point there instead. `check_components!` uses this to -/// aim an unsatisfied-bound error at the checked component, and -/// `delegate_components!` to aim a coherence conflict at the offending entry. -/// -/// Only top-level tokens are re-spanned; tokens nested inside a delimiter group -/// (an impl body, say) keep their spans, which is why callers that must preserve -/// an inner span — the generic parameters of a generated impl, for instance — -/// restore it after re-spanning rather than relying on this to skip it. +/// This clobbers the span of *user-written* tokens too, so it is only for the +/// case where that is the intent: `check_components!` re-spans the context type +/// — a single user token, reused once per checked component — onto each +/// component in turn, so an unsatisfied-bound error is reported on the component +/// the user listed rather than on the one shared context token. To re-span a +/// *generated item* while leaving the user's tokens navigable, use +/// [`override_item_span`] instead. pub fn override_span(span: Span, body: &T) -> syn::Result where T: Parse + ToTokens, @@ -31,3 +27,62 @@ where .collect(), ) } + +/// Re-span a generated item's *boundary* tokens onto `span` — its leading token +/// and its trailing token, nothing in between — and re-parse into `T`. +/// +/// A macro builds an item from quasi-quoted tokens (`quote!`/`parse_internal!`), +/// which stamp every token they emit — the `impl` keyword, the trait reference, +/// the fully-qualified `exports` path, the `{ … }` body — with the macro's +/// `call_site` span, the whole invocation. A compiler error on the item's header +/// (a coherence conflict `E0119`, an unsatisfied bound) then underlines the +/// entire macro block instead of the entry the user wrote. +/// +/// The compiler derives a generated item's span — and therefore that caret — +/// from its *first and last* tokens joined together (`first.to(last)`): with the +/// whole item at `call_site` the caret is the whole invocation, and re-spanning +/// just those two boundary tokens onto `span` collapses it onto the entry. The +/// interior tokens are deliberately left alone, and that is what keeps an editor +/// working: rust-analyzer maps a source token to an expanded one purely by source +/// range (hygiene is ignored), so a synthesized identifier — `IsProviderFor`, +/// `DelegateComponent` — re-spanned onto the entry would collide with the key the +/// user wrote, and go-to-definition on that key would then offer every collided +/// construct. Leaving the interior untouched keeps each synthesized reference at +/// `call_site` (never coinciding with a narrower user token) and each user token +/// — the wired provider, the target type, a per-entry generic — at its own span, +/// so navigation resolves to the one right definition. +/// +/// The two boundary tokens re-spanned here are structural (a keyword and a +/// delimiter group), never references, so moving them onto the entry cannot +/// mislead the editor. Each is re-spanned only if it is itself synthesized, so a +/// hand-assembled item whose boundary token the user wrote is left untouched. +pub fn override_item_span(span: Span, body: &T) -> syn::Result +where + T: Parse + ToTokens, +{ + let call_site_text = Span::call_site().source_text(); + let mut trees: Vec = body.to_token_stream().into_iter().collect(); + + if let Some(first) = trees.first_mut() + && is_synthesized(first.span(), &call_site_text) + { + first.set_span(span); + } + if let Some(last) = trees.last_mut() + && is_synthesized(last.span(), &call_site_text) + { + last.set_span(span); + } + + parse2(trees.into_iter().collect()) +} + +/// Whether `token_span` was stamped by `quote!`/`parse_internal!` rather than +/// carried in from user input. A synthesized token carries the macro's +/// `call_site` span, whose source text is the entire invocation; a user token's +/// source text is just that token, so the two never coincide. Comparing the +/// source text works because a synthesized token *is* `call_site`, so it yields +/// the exact same result as `call_site_text` — even when that is `None`. +fn is_synthesized(token_span: Span, call_site_text: &Option) -> bool { + token_span.source_text() == *call_site_text +} diff --git a/crates/macros/cgp-macro-core/src/types/attributes/cgp_impl_attributes.rs b/crates/macros/cgp-macro-core/src/types/attributes/cgp_impl_attributes.rs index 3ac0b77d..597c859a 100644 --- a/crates/macros/cgp-macro-core/src/types/attributes/cgp_impl_attributes.rs +++ b/crates/macros/cgp-macro-core/src/types/attributes/cgp_impl_attributes.rs @@ -1,13 +1,12 @@ -use syn::Attribute; use syn::parse::Parse; use syn::punctuated::Punctuated; use syn::token::Comma; +use syn::{Attribute, TypeParamBound}; use crate::types::attributes::{ DefaultImplAttribute, DefaultImplAttributes, UseProviderAttribute, UseProviderAttributes, UseTypeAttribute, UseTypeAttributes, UsesAttributes, }; -use crate::types::ident::PathWithTypeArgs; #[derive(Default)] pub struct CgpImplAttributes { @@ -27,7 +26,7 @@ impl CgpImplAttributes { match ident.to_string().as_ref() { "uses" => { let uses = attribute.parse_args_with( - Punctuated::::parse_terminated, + Punctuated::::parse_terminated, )?; parsed_attributes.uses.imports.extend(uses); 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 ddb6ce94..e56e5fca 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 @@ -3,7 +3,7 @@ use syn::spanned::Spanned; use syn::token::In; use syn::{Generics, ItemImpl, Type}; -use crate::functions::override_span; +use crate::functions::override_item_span; use crate::parse_internal; use crate::types::ident::PathWithTypeArgs; use crate::types::path::UniPathOrType; @@ -41,17 +41,15 @@ impl DefaultImplAttribute { } }; - // The impl is built wholly from quasi-quoted tokens, so its `impl`/`for` - // structural tokens carry the macro `call_site` span. Re-span it onto the - // user-written key token so a coherence conflict (`E0119`) between two - // default impls for the same key is reported on that key rather than on - // the whole `#[cgp_impl]` attribute. Generics keep their own spans, - // restored afterward, mirroring `EvaluatedDelegateEntry::respan_impl`. - let generics = item_impl.generics.clone(); - let mut item_impl = override_span(key_type.span(), &item_impl)?; - item_impl.generics = generics; - - Ok(item_impl) + // The impl is built from quasi-quoted tokens, so its boundary carries the + // macro `call_site` span. Re-span it onto the user-written key token so a + // coherence conflict (`E0119`) between two default impls for the same key + // is reported on that key rather than on the whole `#[cgp_impl]` attribute. + // Only the boundary moves; the interior tokens — the provider type, a + // per-entry generic, each synthesized reference — keep their spans, so the + // user's tokens stay navigable in an IDE, mirroring + // `EvaluatedDelegateEntry::respan_impl`. + override_item_span(key_type.span(), &item_impl) } } diff --git a/crates/macros/cgp-macro-core/src/types/attributes/function.rs b/crates/macros/cgp-macro-core/src/types/attributes/function.rs index db64ac82..75e375bf 100644 --- a/crates/macros/cgp-macro-core/src/types/attributes/function.rs +++ b/crates/macros/cgp-macro-core/src/types/attributes/function.rs @@ -5,13 +5,12 @@ use syn::{Attribute, GenericParam, TypeParamBound, WherePredicate}; use crate::types::attributes::{ UseProviderAttribute, UseProviderAttributes, UseTypeAttribute, UseTypeAttributes, }; -use crate::types::ident::PathWithTypeArgs; #[derive(Default)] pub struct FunctionAttributes { pub extend: Vec, pub extend_where: Vec, - pub uses: Vec, + pub uses: Vec, pub use_type: UseTypeAttributes, pub use_provider: UseProviderAttributes, pub impl_generics: Vec, @@ -36,7 +35,7 @@ impl FunctionAttributes { parsed_attributes.extend_where.extend(where_predicates); } else if ident == "uses" { let uses = attribute - .parse_args_with(Punctuated::::parse_terminated)?; + .parse_args_with(Punctuated::::parse_terminated)?; parsed_attributes.uses.extend(uses); } else if ident == "use_type" { diff --git a/crates/macros/cgp-macro-core/src/types/attributes/uses.rs b/crates/macros/cgp-macro-core/src/types/attributes/uses.rs index a382c381..0ba9852a 100644 --- a/crates/macros/cgp-macro-core/src/types/attributes/uses.rs +++ b/crates/macros/cgp-macro-core/src/types/attributes/uses.rs @@ -2,23 +2,19 @@ use syn::TypeParamBound; use syn::punctuated::Punctuated; use syn::token::Plus; -use crate::parse_internal; use crate::traits::ToTypeParamBounds; -use crate::types::ident::PathWithTypeArgs; +/// The parsed `#[uses(...)]` attribute: the trait bounds a provider imports onto +/// `Self`. Each import is a full [`syn::TypeParamBound`], so an associated-type +/// binding (`HasErrorType`), an HRTB, or a lifetime bound +/// is accepted in addition to the plain `Trait` form. #[derive(Default)] pub struct UsesAttributes { - pub imports: Vec, + pub imports: Vec, } impl ToTypeParamBounds for UsesAttributes { fn to_type_param_bounds(&self) -> syn::Result> { - let mut bounds: Punctuated = Punctuated::default(); - - for import in &self.imports { - bounds.push(parse_internal! { #import }); - } - - Ok(bounds) + Ok(self.imports.iter().cloned().collect()) } } diff --git a/crates/macros/cgp-macro-core/src/types/cgp_component/args/component_args.rs b/crates/macros/cgp-macro-core/src/types/cgp_component/args/component_args.rs index d98e58d9..d29e8242 100644 --- a/crates/macros/cgp-macro-core/src/types/cgp_component/args/component_args.rs +++ b/crates/macros/cgp-macro-core/src/types/cgp_component/args/component_args.rs @@ -35,10 +35,19 @@ impl TryFrom for CgpComponentArgs { .context_ident .unwrap_or_else(|| Ident::new("__Context__", Span::call_site())); + // Span the derived marker on the provider identifier the user wrote, not + // `Span::call_site()`. A `call_site` span here covers the whole + // `#[cgp_component(..)]` attribute, so the generated `struct {Provider}Component` + // reports its definition on a range that also contains the `cgp_component` + // macro path — and an editor's go-to-definition on a delegate key naming + // this component then offers both the component and the macro as targets + // (visible cross-crate, where navigation relies on this span). Deriving the + // ident from `provider_ident.span()` points it at a single user token, the + // way `derive_check_trait_ident` spans its derived check trait. let component_name = raw_args.component_name.unwrap_or_else(|| { IdentWithTypeGenerics::from(Ident::new( &format!("{provider_ident}Component"), - Span::call_site(), + provider_ident.span(), )) }); diff --git a/crates/macros/cgp-macro-core/src/types/cgp_fn/preprocessed.rs b/crates/macros/cgp-macro-core/src/types/cgp_fn/preprocessed.rs index e5a0ebbf..1045c735 100644 --- a/crates/macros/cgp-macro-core/src/types/cgp_fn/preprocessed.rs +++ b/crates/macros/cgp-macro-core/src/types/cgp_fn/preprocessed.rs @@ -109,10 +109,7 @@ impl PreprocessedItemCgpFn { { let mut bounds: Punctuated = Punctuated::default(); bounds.extend(attributes.extend.clone()); - - for import in attributes.uses.iter() { - bounds.push(parse_internal! { #import }); - } + bounds.extend(attributes.uses.iter().cloned()); if !bounds.is_empty() { item_impl diff --git a/crates/macros/cgp-macro-core/src/types/cgp_provider/item.rs b/crates/macros/cgp-macro-core/src/types/cgp_provider/item.rs index a27134b5..14e50e03 100644 --- a/crates/macros/cgp-macro-core/src/types/cgp_provider/item.rs +++ b/crates/macros/cgp-macro-core/src/types/cgp_provider/item.rs @@ -1,3 +1,4 @@ +use proc_macro2::Span; use quote::ToTokens; use syn::spanned::Spanned; use syn::{Error, Ident, ItemImpl, Type}; @@ -31,6 +32,14 @@ impl ItemCgpProvider { } pub fn component_type(&self) -> syn::Result { + // An explicit `#[cgp_provider(Component)]` / `#[cgp_impl(Provider: Component)]` + // override names the component directly; it is a user-written type, so it + // keeps its own span. Only fall back to the `{Provider}Component` default + // when no override is given. + if let Some(component_type) = &self.args.component_type { + return Ok(component_type.clone()); + } + let item_impl = &self.item_impl; let (_, provider_trait_path, _) = item_impl.trait_.as_ref().ok_or_else(|| { @@ -40,9 +49,21 @@ impl ItemCgpProvider { let provider_trait: PathWithTypeArgs = parse_internal(provider_trait_path.to_token_stream())?; + // Span the synthesized `{Provider}Component` reference on `call_site`, not + // the provider trait's span. The derived component appears only as the + // first type argument of the generated `IsProviderFor<..>` impl — an + // interior token that anchors no error caret — so giving it the provider + // trait's span buys nothing at the compiler, and it actively harms the + // editor: rust-analyzer maps a source token to its expansion by source + // range, so a component reference sharing the provider trait's range makes + // go-to-definition on the provider trait offer the component struct too. + // A `call_site` span shares no narrow user token's range, so it stays out + // of the way. (This is the reference-side dual of the `#[cgp_component]` + // marker struct, whose *definition* is instead spanned on the provider + // identifier so navigation lands there cleanly.) let component_ident = Ident::new( &format!("{}Component", provider_trait.ident()), - provider_trait.span(), + Span::call_site(), ); parse_internal(component_ident.to_token_stream()) diff --git a/crates/macros/cgp-macro-core/src/types/delegate_component/mapping/eval.rs b/crates/macros/cgp-macro-core/src/types/delegate_component/mapping/eval.rs index fdd3e97d..fd4c1c62 100644 --- a/crates/macros/cgp-macro-core/src/types/delegate_component/mapping/eval.rs +++ b/crates/macros/cgp-macro-core/src/types/delegate_component/mapping/eval.rs @@ -2,7 +2,7 @@ use proc_macro2::Span; use syn::{Generics, ItemImpl, Type}; use crate::exports::{DelegateComponent, IsProviderFor}; -use crate::functions::{merge_generics, override_span, parse_internal}; +use crate::functions::{merge_generics, override_item_span, parse_internal}; /// The flat form every key, value, and statement collapses to; it renders the /// impl pair for one wiring entry. @@ -90,24 +90,25 @@ impl EvaluatedDelegateEntry { self.respan_impl(item_impl) } - /// Re-span a generated impl onto the entry's own span (`self.span`, the key's - /// source token) so a coherence conflict (`E0119`) between two entries mapping - /// the same key is reported on the offending entry rather than on the whole - /// `delegate_components!` block — the impl's `impl`/trait-ref/self-type tokens - /// otherwise carry the macro's `call_site` span, which spans the entire + /// Re-span the generated impl onto the entry's own span (`self.span`, the + /// key's source token) so a coherence conflict (`E0119`) between two entries + /// mapping the same key is reported on the offending entry rather than on the + /// whole `delegate_components!` block — the impl's `impl`/`{ … }` boundary + /// otherwise carries the macro's `call_site` span, which spans the entire /// invocation. Using the carried span rather than `self.key.span()` keeps a - /// synthesized key (an `@`-path's `PathCons<..>` nest, whose first token is a - /// `call_site`-spanned `PathCons`) pointing at what the user wrote. + /// synthesized key (an `@`-path's `PathCons<..>` nest, whose tokens are all + /// `call_site`-spanned) pointing at what the user wrote. /// - /// The impl's generics keep their original spans, restored after re-spanning, - /// so a diagnostic about a per-entry generic (an unconstrained parameter's - /// `E0207`, say) still points at the generic the user wrote rather than the key. + /// [`override_item_span`] moves only the impl's boundary tokens, so every + /// interior token — the wired provider (`self.value`), the target type, a + /// per-entry generic, and each synthesized reference like `IsProviderFor` — + /// keeps its own span. That keeps the user's tokens navigable in an IDE + /// (an editor maps by source range, so a synthesized reference re-spanned onto + /// the key would hijack go-to-definition on that key) and keeps a per-entry + /// generic's unconstrained-parameter `E0207` pointing at the `` the user + /// wrote rather than the key. fn respan_impl(&self, item_impl: ItemImpl) -> syn::Result { - let generics = item_impl.generics.clone(); - let mut item_impl = override_span(self.span, &item_impl)?; - item_impl.generics = generics; - - Ok(item_impl) + override_item_span(self.span, &item_impl) } /// Emit `impl namespace_trait for Key { type Delegate = Value; }`, used by diff --git a/crates/tests/cgp-tests/tests/basic_delegation/mod.rs b/crates/tests/cgp-tests/tests/basic_delegation/mod.rs index c645271e..750b88de 100644 --- a/crates/tests/cgp-tests/tests/basic_delegation/mod.rs +++ b/crates/tests/cgp-tests/tests/basic_delegation/mod.rs @@ -8,6 +8,7 @@ pub mod component_macro; pub mod delegate_array_key; pub mod delegate_components_macro; pub mod delegate_generic_table; +pub mod provider_component_override; pub mod provider_macro; // Behavioral wiring: a consumer trait becomes usable once its component is wired. diff --git a/crates/tests/cgp-tests/tests/basic_delegation/provider_component_override.rs b/crates/tests/cgp-tests/tests/basic_delegation/provider_component_override.rs new file mode 100644 index 00000000..f11e410e --- /dev/null +++ b/crates/tests/cgp-tests/tests/basic_delegation/provider_component_override.rs @@ -0,0 +1,58 @@ +//! `#[cgp_impl]`'s `: ComponentType` override targets a component whose marker +//! name does not follow the `{Provider}Component` convention. +//! +//! Here the component is declared with an explicit `name` (`HasFooComponent`) +//! that differs from the provider trait's name plus `Component` +//! (`FooProviderComponent`). A provider for it must name the component with the +//! `#[cgp_impl(new Provider: Component)]` override; without it the macro derives +//! `FooProviderComponent`, which does not exist here, so the generated +//! `IsProviderFor` impl fails to resolve. +//! +//! See docs/reference/macros/cgp_impl.md. + +use cgp::prelude::*; +use cgp_macro_test_util::snapshot_cgp_impl; + +#[cgp_component { + name: HasFooComponent, + provider: FooProvider, +}] +pub trait CanDoFoo { + fn foo(&self) -> u32; +} + +snapshot_cgp_impl! { + #[cgp_impl(new FortyTwo: HasFooComponent)] + impl FooProvider { + fn foo(&self) -> u32 { + 42 + } + } + + expand_forty_two(output) { + insta::assert_snapshot!(output, @" + impl<__Context__> FooProvider<__Context__> for FortyTwo { + fn foo(__context__: &__Context__) -> u32 { + 42 + } + } + impl<__Context__> IsProviderFor for FortyTwo {} + pub struct FortyTwo; + ") + } +} + +pub struct App; + +delegate_components! { + App { + HasFooComponent: FortyTwo, + } +} + +#[test] +fn test_component_override() { + // The `: HasFooComponent` override makes `FortyTwo` a provider for the + // non-conventionally-named component, so `App` implements `CanDoFoo`. + assert_eq!(App.foo(), 42); +} diff --git a/crates/tests/cgp-tests/tests/impl_side_dependencies/fn_uses_associated_type.rs b/crates/tests/cgp-tests/tests/impl_side_dependencies/fn_uses_associated_type.rs new file mode 100644 index 00000000..86b9943d --- /dev/null +++ b/crates/tests/cgp-tests/tests/impl_side_dependencies/fn_uses_associated_type.rs @@ -0,0 +1,52 @@ +//! `#[uses(...)]` accepts a full trait bound, not only the plain `Trait` +//! form: an associated-type-equality binding such as `HasErrorType` +//! is imported verbatim as `Self: HasErrorType` in the generated +//! impl's `where` clause — an impl-side dependency the consumer trait does not +//! expose. The `#[cgp_fn]` body here is deliberately trivial; what the snapshot +//! pins is that the equality binding lands intact. For pinning an *abstract type* +//! like the error type, the `#[use_type]` equality form +//! (`#[use_type(HasErrorType.{Error = String})]`) remains the preferred spelling; +//! this test exercises the more general bound `#[uses]` now accepts. +//! +//! The `App` context below hand-implements `HasErrorType`, so it +//! satisfies the imported bound and gains `AlwaysTrue` — a wrong pin (a different +//! `Error` type) would make `impl CheckApp for App` fail to compile. +//! +//! See docs/implementation/asts/attributes.md and +//! docs/reference/attributes/uses.md. + +use cgp::prelude::*; +use cgp_macro_test_util::snapshot_cgp_fn; + +snapshot_cgp_fn! { + #[cgp_fn] + #[uses(HasErrorType)] + pub fn always_true(&self) -> bool { + true + } + + expand_always_true(output) { + insta::assert_snapshot!(output, @" + pub trait AlwaysTrue { + fn always_true(&self) -> bool; + } + impl<__Context__> AlwaysTrue for __Context__ + where + Self: HasErrorType, + { + fn always_true(&self) -> bool { + true + } + } + ") + } +} + +pub struct App; + +impl HasErrorType for App { + type Error = String; +} + +pub trait CheckApp: AlwaysTrue {} +impl CheckApp for App {} diff --git a/crates/tests/cgp-tests/tests/impl_side_dependencies/impl_uses_associated_type.rs b/crates/tests/cgp-tests/tests/impl_side_dependencies/impl_uses_associated_type.rs new file mode 100644 index 00000000..c5185eef --- /dev/null +++ b/crates/tests/cgp-tests/tests/impl_side_dependencies/impl_uses_associated_type.rs @@ -0,0 +1,54 @@ +//! `#[uses(...)]` on a `#[cgp_impl]` provider accepts an associated-type-equality +//! bound the same way the `#[cgp_fn]` form does. Here `ValidateWithError` imports +//! `#[uses(HasErrorType)]`, which becomes the impl-side +//! dependency `Self: HasErrorType` — pinning the context's +//! abstract error type to a concrete one. The `delegate_and_check_components!` +//! check is what makes the pin load-bearing: it holds only because `App` +//! implements `HasErrorType` with exactly `Error = AppError`; a different error +//! type would fail the check. +//! +//! The `#[uses]` equality bound is the feature under test and is written plainly. +//! The component, the direct `HasErrorType` impl, and the wiring are incidental +//! scaffolding (their expansions are owned by the `basic_delegation`, +//! `abstract_types`, and `checking` concepts), so they are written as plain +//! macros. For pinning an abstract type, `#[use_type(HasErrorType.{Error = AppError})]` +//! is the preferred spelling; this test exercises the more general bound `#[uses]` +//! accepts. +//! +//! See docs/implementation/asts/attributes.md and +//! docs/reference/attributes/uses.md. + +use cgp::prelude::*; + +#[derive(Debug)] +pub struct AppError(pub String); + +#[cgp_component(Validator)] +pub trait CanValidate { + fn validate(&self) -> bool; +} + +#[cgp_impl(new ValidateWithError)] +#[uses(HasErrorType)] +impl Validator { + fn validate(&self) -> bool { + true + } +} + +pub struct App; + +impl HasErrorType for App { + type Error = AppError; +} + +delegate_and_check_components! { + App { + ValidatorComponent: ValidateWithError, + } +} + +#[test] +fn test_impl_uses_associated_type() { + assert!(App.validate()); +} diff --git a/crates/tests/cgp-tests/tests/impl_side_dependencies/mod.rs b/crates/tests/cgp-tests/tests/impl_side_dependencies/mod.rs index f6a251a2..3091b2ef 100644 --- a/crates/tests/cgp-tests/tests/impl_side_dependencies/mod.rs +++ b/crates/tests/cgp-tests/tests/impl_side_dependencies/mod.rs @@ -7,6 +7,11 @@ // lands on the generated impl's `where` clause. pub mod fn_uses; +// `#[uses(...)]` accepts a full trait bound, not only the plain `Trait` +// form: this pins the associated-type-equality variant (`HasErrorType`) +// on the `#[cgp_fn]` form, alongside `fn_uses`'s plain-import snapshot. +pub mod fn_uses_associated_type; + // `#[extend(...)]` on `#[cgp_fn]`: adds a supertrait bound to the generated // trait. This concept owns the snapshot showing how `#[extend]` lands on the // generated trait definition and impl. @@ -15,3 +20,8 @@ pub mod fn_extend; // `#[uses(...)]` on a `#[cgp_impl]` provider: imports a `Self` trait bound so the // provider can call another capability. The provider is written plainly. pub mod impl_uses; + +// `#[uses(...)]` with an associated-type-equality bound on a `#[cgp_impl]` +// provider: pins the context's abstract error type through the imported bound, +// verified by the wiring check. +pub mod impl_uses_associated_type; diff --git a/docs/concepts/implicit-arguments.md b/docs/concepts/implicit-arguments.md index 97490022..857daf20 100644 --- a/docs/concepts/implicit-arguments.md +++ b/docs/concepts/implicit-arguments.md @@ -35,10 +35,10 @@ These same rules govern getter access in [`#[cgp_auto_getter]`](../reference/mac An implicit argument is the default way to read a context field, and a getter trait is the exception reserved for publishing a reusable capability. Both read fields through `HasField` and share the same access rules, so the choice is not about mechanics but about what the value *is*. An implicit argument treats the value as a private input to one provider: it is bound as a local at the top of the method and used freely from there, which covers reading a field once, using it throughout a body, or declaring it on each of several methods that need it. This is the form to reach for whenever a provider simply needs a value from its context. -A getter trait written with [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) is worth defining only when the value is a *published capability* rather than a private input — a named `self.name()` accessor that other providers depend on through `#[uses(HasName)]`, or a getter whose associated type is inferred from the field so the type stays abstract. Declaring such a trait promotes the field to part of the context's capability surface. When all a provider wants is to read a field for its own computation, an implicit argument says exactly that with less ceremony, so prefer it and treat the getter trait as the deliberate step of exposing a shared capability. The full wireable getter, [`#[cgp_getter]`](../reference/macros/cgp_getter.md), is a further step still, reserved for the advanced case of choosing the source field per context at wiring time. +A getter trait written with [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) is worth defining only in the cases an implicit argument cannot reach. Because an implicit argument reads only from the provider's own `self` and takes a plain `&T` by reference without cloning, it covers every same-context read — even a field several providers each consume, declared as the same implicit argument in each. A getter trait earns its keep in three situations it cannot handle: the field 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); the accessor must exist as a *named* capability other code depends on through `#[uses(HasName)]` or a supertrait; or the getter carries an associated type inferred from the field so the type stays abstract for callers. Everywhere else, prefer the implicit argument. The full wireable getter, [`#[cgp_getter]`](../reference/macros/cgp_getter.md), is a further step still, reserved for the advanced case of choosing the source field per context at wiring time. ## Related constructs The [`#[implicit]`](../reference/attributes/implicit.md) attribute is the construct itself, usable inside [`#[cgp_fn]`](../reference/macros/cgp_fn.md) and [`#[cgp_impl]`](../reference/macros/cgp_impl.md) — the former producing a single-implementation capability with no wiring, the latter a provider for a [`#[cgp_component]`](../reference/macros/cgp_component.md). All of them desugar implicit arguments into [`HasField`](../reference/traits/has_field.md) bounds, which a context supplies by deriving [`#[derive(HasField)]`](../reference/derives/derive_has_field.md). -For publishing a field as a shared, reusable accessor, [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) is the getter-trait counterpart that shares the same `.clone()`/`.as_str()` access rules — but a provider reading a field for its own use should prefer an implicit argument. The whole model is value-level [impl-side dependencies](impl-side-dependencies.md): an implicit argument is a context dependency injected through a `where`-clause `HasField` bound and dressed as a function parameter. +For the narrow cases an implicit argument cannot reach — a field on another type, a named shared accessor, or a getter with an inferred associated type — [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) is the getter-trait counterpart that shares the same `.clone()`/`.as_str()` access rules; everywhere else, a provider reading a field should prefer an implicit argument. The whole model is value-level [impl-side dependencies](impl-side-dependencies.md): an implicit argument is a context dependency injected through a `where`-clause `HasField` bound and dressed as a function parameter. diff --git a/docs/concepts/modern-idioms.md b/docs/concepts/modern-idioms.md index 37593956..270c44c4 100644 --- a/docs/concepts/modern-idioms.md +++ b/docs/concepts/modern-idioms.md @@ -90,7 +90,7 @@ impl AreaCalculator { } ``` -`#[uses(...)]` accepts only the simple `Trait` form, so a bound with associated-type equality such as `Iterator` must still be written as an explicit `where` clause. Both attributes desugar to the same `where` predicates they replace. +`#[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 @@ -123,9 +123,9 @@ impl AreaCalculator { } ``` -Reserve `#[cgp_auto_getter]` for when you genuinely want to publish a reusable getter *capability* rather than read a field for your own use — a named `self.name()` accessor that other providers depend on through `#[uses(HasName)]`, or a getter whose associated type is inferred from the field (`type Name; fn name(&self) -> &Self::Name;`). Both idioms desugar to the same `HasField` bounds and share the same access rules — `.clone()` for an owned value, `.as_str()` for a `&str` — so choosing between them is about whether the value is a private input or a published capability, not about mechanics. +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 (or, for a published accessor, `#[cgp_auto_getter]`) is the form to write. +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]` @@ -150,6 +150,10 @@ pub trait CanLoad { 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: @@ -206,6 +210,6 @@ Because `open` and namespaces ride `RedirectLookup`, **a new component you inten ## 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 a bound `#[uses]` cannot express — anything with associated-type equality. 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](#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. 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. diff --git a/docs/examples/money-transfer-api.md b/docs/examples/money-transfer-api.md index 5b86bf39..650cbdb6 100644 --- a/docs/examples/money-transfer-api.md +++ b/docs/examples/money-transfer-api.md @@ -7,7 +7,7 @@ The concepts each step demonstrates are documented in full in the reference; thi - 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 [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.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 - restoring a `Send` bound for the HTTP server — the [recovering `Send` bounds concept](../concepts/send-bounds.md) @@ -204,23 +204,11 @@ Because each wrapper is itself an `ApiHandler`, they nest into a pipeline. Writi ## 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, retrieved through [`#[cgp_auto_getter]`](../reference/macros/cgp_auto_getter.md) traits: +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: ```rust -#[cgp_auto_getter] -#[use_type(HasUserIdType.UserId, HasCurrencyType.Currency, HasQuantityType.Quantity)] -pub trait HasMockedUserBalances { - fn user_balances( - &self, - ) -> &Arc>>; -} - #[cgp_impl(UseMockedApp)] -#[uses( - HasMockedUserBalances, - CanRaiseHttpError, - CanRaiseHttpError, -)] +#[uses(CanRaiseHttpError, CanRaiseHttpError)] #[use_type(HasUserIdType.UserId, HasCurrencyType.Currency, HasQuantityType.Quantity, HasErrorType.Error)] impl MoneyTransferrer where @@ -234,14 +222,17 @@ where recipient: &UserId, currency: &Currency, quantity: &Quantity, + #[implicit] user_balances: &Arc>>, ) -> Result<(), Error> { - let mut balances = self.user_balances().lock().await; + let mut balances = user_balances.lock().await; /* debit the sender, credit the recipient, raising on overflow or missing accounts */ Ok(()) } } ``` +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. + 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. ## Wiring a context diff --git a/docs/implementation/README.md b/docs/implementation/README.md index 2e65d205..0eb9a15d 100644 --- a/docs/implementation/README.md +++ b/docs/implementation/README.md @@ -119,7 +119,18 @@ A few rules recur across the suite and are worth stating plainly. A lifetime is ### Spans: aim generated items at the token the user wrote -A generated item's structural tokens — the `impl` keyword, the trait reference, the self type — carry whatever span the macro stamps on them, and by default `parse_internal!`/`quote!` stamp the macro's `call_site` span, which covers the whole invocation. The visible symptom is a compiler error on a generated item's *header* — a coherence conflict (`E0119`) between two generated impls, an unsatisfied bound, a name-resolution failure — underlining the entire macro block instead of the entry, attribute, or impl the user actually wrote. The fix is to re-span each generated item onto its originating token, and the codebase does this three ways: the shared [`override_span`](../../crates/macros/cgp-macro-core/src/functions/override_span.rs) helper re-spans an item's tokens (restoring the generics afterward so a per-entry generic's `E0207` still points at the user's ``); a carried span field threads the originating token's span through the evaluated form when the token is synthesized and has lost its span; and reusing the user's own `for` token, as the provider macros do, keeps the middle of a generated impl header off `call_site`. These spans are testable — a `trybuild` `.stderr` fixture pins the exact line and column of each caret, so a span regression changes the snapshot. The per-macro detail lives in each document's error-spans discussion; [`delegate_components!`](entrypoints/delegate_components.md) and [`#[cgp_impl]`](entrypoints/cgp_impl.md) are the worked examples. +A generated item's tokens carry whatever span the macro stamps on them, and by default `parse_internal!`/`quote!` stamp every token they synthesize with the macro's `call_site` span — the whole invocation. Placing those spans correctly matters for two separate tools, and a stray `call_site` span misleads each in its own way: the **compiler** aims an error caret at the wrong place, and **rust-analyzer** resolves go-to-definition to the wrong place. Both symptoms trace to the same cause — a token that should carry the narrow span of what the user wrote instead carries `call_site` — so the macros re-span their generated tokens onto the originating user token. A reviewer must weigh both consequences, not just the error carets, because only the carets are pinned by a test. + +The compiler derives an item's error span by joining that item's first and last tokens. A coherence conflict (`E0119`) between two generated impls, an unsatisfied bound, or a name-resolution failure on a generated header therefore underlines the *entire macro block* whenever the item sits at `call_site`, saying nothing about which entry to fix. The [`override_item_span`](../../crates/macros/cgp-macro-core/src/functions/override_span.rs) helper corrects this by re-spanning only the item's two *boundary* tokens — its leading `impl` keyword and its trailing `{ … }` body — onto the entry the user wrote; joining just those two collapses the caret onto the entry. It moves a boundary token only when that token is itself synthesized, telling a synthesized token from a user one by source text: a `call_site` token's source text is the whole invocation, which no narrower user token can equal. + +rust-analyzer maps a source token to its expanded counterpart purely by source range, ignoring hygiene, so a token re-spanned onto a user token's range becomes, as far as the editor is concerned, that user token — and go-to-definition on it offers every construct that now shares the range. Two distinct `call_site` leaks cause this, and both are subtle because the compiler is indifferent to them: + +- **Re-spanning a resolvable reference onto the entry.** If `override_item_span` moved the whole impl rather than its boundary, a synthesized reference such as `IsProviderFor` or `DelegateComponent` would land on the delegate key's range, and a jump on that key would offer those traits alongside the component the user meant. This is exactly why the helper moves only the boundary — a keyword and a delimiter, never a reference — and leaves the interior at its own spans. Leaving the interior alone is also what keeps a wired provider navigable and keeps a per-entry generic's unconstrained-parameter `E0207` pointing at the `` the user wrote. +- **A derived name stamped with `call_site`.** The `#[cgp_component]` marker struct `{Provider}Component`, when its name is not given explicitly, is derived from the provider identifier. Deriving it with `Span::call_site()` gave the generated `struct {Provider}Component` a definition span covering the whole `#[cgp_component(..)]` attribute — a range that also contains the `cgp_component` macro path — so go-to-definition on a delegate key naming that component offered both the component and the macro. Deriving the name from the provider identifier's own span, the way [`derive_check_trait_ident`](../../crates/macros/cgp-macro-core/src/types/check_components/table.rs) already spans its derived check trait, points the definition at a single user token and removes the ambiguity. It surfaced only across crates: within the defining crate rust-analyzer resolves from the live macro expansion, whereas a dependent crate relies on the recorded definition span, which is where the leak shows. + +Two more re-spanning techniques cover the rest. `override_item_span`'s sibling `override_span` re-spans *every* token unconditionally, which is right only for `check_components!`, where the intent is to clobber one shared user token — the context type — onto each checked component in turn. Where the originating token is itself synthesized and has lost its span (an `@`-path's `PathCons<..>` nest), the evaluated form carries an explicit span field forward, as `EvaluatedCheckEntry.span` and `EvaluatedDelegateEntry.span` do; and the provider macros reuse the user's own `for` token so the middle of a generated impl header never falls on `call_site`. + +Only the compiler side is pinned by a test: a `trybuild` `.stderr` fixture records the exact line and column of each caret, so an error-span regression changes a snapshot. The rust-analyzer behavior has no fixture and must be checked by hand in an editor — and checked across crates as well as within one, since the derived-name leak above is invisible from inside the defining crate. The per-macro detail lives in each document's error-spans discussion; [`delegate_components!`](entrypoints/delegate_components.md) and [`#[cgp_impl]`](entrypoints/cgp_impl.md) are the worked examples. ### Parsing: build with `parse_internal!`, and distrust `syn`'s leniency diff --git a/docs/implementation/asts/attributes.md b/docs/implementation/asts/attributes.md index 11e720ca..f32663b6 100644 --- a/docs/implementation/asts/attributes.md +++ b/docs/implementation/asts/attributes.md @@ -6,7 +6,7 @@ The modifiers do not parse themselves out of the token stream on their own; a ho ## `#[uses]` -`#[uses(TraitA, TraitB)]` imports `Self` trait bounds, reading like a `use` statement. It parses into `UsesAttributes`, which holds a `Vec` — one path per imported trait, accepting only the plain `Trait` form and no associated-type equality. Its `to_type_param_bounds` turns each path directly into a `TypeParamBound`, and the host appends those bounds to the generated impl's `where` clause (on `Self`), where they become impl-side dependencies. On `#[cgp_fn]` the same parsing runs through `FunctionAttributes`; on `#[cgp_impl]` through `CgpImplAttributes`. The bounds land only on the impl, never on the consumer trait, which is what keeps the dependency hidden from callers. +`#[uses(TraitA, TraitB)]` imports `Self` trait bounds, reading like a `use` statement. It parses into `UsesAttributes`, which holds a `Vec` — one bound per imported capability. Each entry is a full `syn::TypeParamBound`, so any bound a `where` clause accepts is allowed, including an associated-type-equality binding (`HasErrorType`), a higher-ranked bound, or a lifetime bound; the plain `Trait` form is the idiomatic one. Its `to_type_param_bounds` collects the bounds, and the host appends them to the generated impl's `where` clause (on `Self`), where they become impl-side dependencies. On `#[cgp_fn]` the bounds are parsed straight into `FunctionAttributes` (a `Vec` field mirroring `#[extend]`); on `#[cgp_impl]` into the `UsesAttributes` held by `CgpImplAttributes`. The bounds land only on the impl, never on the consumer trait, which is what keeps the dependency hidden from callers. ## `#[use_type]` @@ -36,7 +36,7 @@ The context type is inserted at index 0 of the trait's angle-bracketed arguments ## `#[extend_where]` -`#[extend_where(Bound)]` adds `where` predicates to a generated trait definition, and is `#[cgp_fn]`-only. It parses into the `extend_where` field of `FunctionAttributes` — a `Vec` — and its predicates are added to both the trait and the impl `where` clauses. It is the escape hatch for the bounds `#[uses]` cannot spell, chiefly associated-type-equality constraints. +`#[extend_where(Bound)]` adds `where` predicates to a generated trait definition, and is `#[cgp_fn]`-only. It parses into the `extend_where` field of `FunctionAttributes` — a `Vec` — and its predicates are added to both the trait and the impl `where` clauses. Where `#[uses]` adds a bound to `Self` on the impl alone, `#[extend_where]` is the way to make a bound part of the generated *trait*, and it takes a full predicate — a bound on any type, not only `Self`, including associated-type-equality constraints. ## `#[derive_delegate]` @@ -72,7 +72,7 @@ The namespace path gains a trailing `__Components__` type argument and the impl 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. -- **`#[uses]`** — [impl_side_dependencies/fn_uses.rs](../../../crates/tests/cgp-tests/tests/impl_side_dependencies/fn_uses.rs) pins the `#[cgp_fn]` form and [impl_side_dependencies/impl_uses.rs](../../../crates/tests/cgp-tests/tests/impl_side_dependencies/impl_uses.rs) the `#[cgp_impl]` form; [generic_components/fn_impl_generics.rs](../../../crates/tests/cgp-tests/tests/generic_components/fn_impl_generics.rs) exercises it alongside generic parameters. +- **`#[uses]`** — [impl_side_dependencies/fn_uses.rs](../../../crates/tests/cgp-tests/tests/impl_side_dependencies/fn_uses.rs) pins the `#[cgp_fn]` form and [impl_side_dependencies/impl_uses.rs](../../../crates/tests/cgp-tests/tests/impl_side_dependencies/impl_uses.rs) the `#[cgp_impl]` form; [generic_components/fn_impl_generics.rs](../../../crates/tests/cgp-tests/tests/generic_components/fn_impl_generics.rs) exercises it alongside generic parameters. The associated-type-equality bound (`HasErrorType`) that `#[uses]` also accepts is pinned on the `#[cgp_fn]` form in [impl_side_dependencies/fn_uses_associated_type.rs](../../../crates/tests/cgp-tests/tests/impl_side_dependencies/fn_uses_associated_type.rs) and exercised end-to-end on the `#[cgp_impl]` form in [impl_side_dependencies/impl_uses_associated_type.rs](../../../crates/tests/cgp-tests/tests/impl_side_dependencies/impl_uses_associated_type.rs). - **`#[use_type]`** — [abstract_types/use_type_component.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_component.rs) covers the `#[cgp_component]` supertrait form and [use_type_foreign.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_foreign.rs) the `@` foreign form on a component; [abstract_types/use_type_fn_alias.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_alias.rs), [use_type_fn_equality.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_equality.rs), and [use_type_fn_foreign.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign.rs) cover the alias, equality, and foreign-context (`@`) forms; [use_type_fn_equality_cross_trait.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_equality_cross_trait.rs), [use_type_fn_foreign_equality.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign_equality.rs), and [use_type_fn_foreign_equality_cross_trait.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign_equality_cross_trait.rs) cover cross-spec and nested-foreign equality; [use_type_generic_param.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_generic_param.rs) covers an alias that collides with a generic parameter, [use_type_path_qualified.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_path_qualified.rs) the path-qualified trait form the `.` separator enables, and [implicit_arguments/cgp_fn_multi_and_use_type.rs](../../../crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_multi_and_use_type.rs) the generic-argument trait form (`HasFooType.Foo`). Rejections are pinned in [parser_rejections/use_type.rs](../../../crates/tests/cgp-macro-tests/tests/parser_rejections/use_type.rs): a `=` equality on a component, and a duplicate identifier or alias across specs, within one braced list, and on a component. - **`#[use_provider]`** — [higher_order_providers/use_provider_fn.rs](../../../crates/tests/cgp-tests/tests/higher_order_providers/use_provider_fn.rs) pins the `#[cgp_fn]` form and [higher_order_providers/use_provider_impl.rs](../../../crates/tests/cgp-tests/tests/higher_order_providers/use_provider_impl.rs) the `#[cgp_impl]` form; [higher_order_providers/scaled_area.rs](../../../crates/tests/cgp-tests/tests/higher_order_providers/scaled_area.rs) wires a full higher-order provider through it. - **`#[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. diff --git a/docs/implementation/asts/cgp_provider.md b/docs/implementation/asts/cgp_provider.md index 922ebb63..26d5a8b9 100644 --- a/docs/implementation/asts/cgp_provider.md +++ b/docs/implementation/asts/cgp_provider.md @@ -35,6 +35,8 @@ It packages the original `item_impl` (emitted verbatim), the derived `IsProvider // for FirstNameToString where … {} ``` +The derived **component reference** — the `Component` in that `IsProviderFor` — is spanned on `call_site`, not on the provider trait it is derived from. It appears only as this interior type argument, which anchors no error caret (a coherence conflict on the marker reports on the `impl` header, an unmet dependency on the `where`-clause bound), so a narrower span buys nothing at the compiler. It would, though, mislead the editor: rust-analyzer maps a source token to its expansion by source range, so had this reference borrowed the provider trait's span it would share that trait token's range, and go-to-definition on the provider trait a user wrote (in a `#[cgp_impl]` block or a hand-written provider impl) would then offer the component struct as a spurious second target. `call_site` shares no narrow user token's range, keeping the reference out of the editor's way. This is the reference-side dual of the [`#[cgp_component]` marker struct](../entrypoints/cgp_component.md#behavior-and-corner-cases), whose *definition* is instead spanned on the provider identifier so navigation to it lands cleanly; see the [Spans note](../README.md#spans-aim-generated-items-at-the-token-the-user-wrote). + The trait arguments come from `ProviderImplArgs::from_generic_args`, and the derivation ends by running [`replace_provider_in_generics`](../../../crates/macros/cgp-macro-core/src/visitors/replace_provider.rs) with a map from the provider identifier to the component type, which rewrites a `Provider: SomeTrait` `where`-bound into an `IsProviderFor<…>` bound so a higher-order provider's inner-provider dependency shows up as an `IsProviderFor` obligation. ## `ProviderImplArgs` diff --git a/docs/implementation/asts/delegate_component.md b/docs/implementation/asts/delegate_component.md index 7517f491..d9e5fb1f 100644 --- a/docs/implementation/asts/delegate_component.md +++ b/docs/implementation/asts/delegate_component.md @@ -52,7 +52,7 @@ Only Normal and Direct mappings can carry a nested inner table (their values are ## `EvaluatedDelegateEntry` -`EvaluatedDelegateEntry` is the lowered form every key/value/statement collapses to: the target type, the merged generics, the key type, the value (`Delegate`) type, and the diagnostic `span`. It owns the rendering: `build_delegate_component_impl` emits the `DelegateComponent for TableType { type Delegate = Value; }` impl, and `build_is_provider_for_impl` emits the forwarding `IsProviderFor` impl bounded on `Value: IsProviderFor`, appending the reserved `__Context__` and `__Params__` generics. Both then pass through `respan_impl`, which re-spans the finished impl onto the entry's `span` (restoring the generics) so a coherence conflict points at the entry rather than the whole block — see [Error spans](../entrypoints/delegate_components.md#error-spans). (`build_namespace_impl`, on the same type, is used by the namespace preset machinery to emit a `Namespace for Key` impl instead.) +`EvaluatedDelegateEntry` is the lowered form every key/value/statement collapses to: the target type, the merged generics, the key type, the value (`Delegate`) type, and the diagnostic `span`. It owns the rendering: `build_delegate_component_impl` emits the `DelegateComponent for TableType { type Delegate = Value; }` impl, and `build_is_provider_for_impl` emits the forwarding `IsProviderFor` impl bounded on `Value: IsProviderFor`, appending the reserved `__Context__` and `__Params__` generics. Both then pass through `respan_impl`, which re-spans the finished impl's boundary tokens (its `impl` keyword and `{ … }` body) onto the entry's `span` — leaving the interior, including the value and any per-entry generic, at its own spans — so a coherence conflict points at the entry rather than the whole block, while an unconstrained generic's `E0207` still lands on the user's `` — see [Error spans](../entrypoints/delegate_components.md#error-spans). (`build_namespace_impl`, on the same type, is used by the namespace preset machinery to emit a `Namespace for Key` impl instead.) ## Tests diff --git a/docs/implementation/entrypoints/cgp_component.md b/docs/implementation/entrypoints/cgp_component.md index 7009aaf3..86d4d1cd 100644 --- a/docs/implementation/entrypoints/cgp_component.md +++ b/docs/implementation/entrypoints/cgp_component.md @@ -62,6 +62,8 @@ A **const generic parameter** on the component is rejected with a spanned `syn:: The **reserved identifiers** appear literally in the output: the context parameter is `__Context__` (unless the `context` key overrides it), the provider parameter is `__Provider__`, and the `RedirectLookup` impl introduces `__Components__` and `__Path__`. These names are chosen so they never clash with a user's own type parameters. +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. + ## Snapshots Every `snapshot_cgp_component!` invocation across the suite is indexed here, since these snapshots all belong to this entrypoint: diff --git a/docs/implementation/entrypoints/cgp_impl.md b/docs/implementation/entrypoints/cgp_impl.md index e642882c..412195af 100644 --- a/docs/implementation/entrypoints/cgp_impl.md +++ b/docs/implementation/entrypoints/cgp_impl.md @@ -62,7 +62,9 @@ The **`#[cgp_impl(Self)]` passthrough** bypasses the whole rewrite: when the pro Because the macro starts from the user's own `syn::ItemImpl` and rewrites it in place, the provider impl and its derived `IsProviderFor` impl keep the user's spans on their `impl` keyword, generics, self type, and body — so a coherence conflict (`E0119`) between two providers already underlines the offending `#[cgp_impl]` block rather than the macro name, exactly as two hand-written impls would. Two tokens are synthesized rather than cloned, and both are aimed at what the user wrote so they cannot leak the `call_site` span: `to_raw_item_impl` gives the provider impl's `for` token the original `for` token's span (explicit `impl Trait for Context` form) or the provider trait path's span (bare `impl Trait` form), and the [`IsProviderFor` derivation](../asts/cgp_provider.md#itemproviderimpl-and-the-isproviderfor-derivation) reuses that same `for` token for its own header. -The one wholly-synthesized item is each `#[default_impl]` delegation impl, built entirely from quasi-quoted tokens whose `impl`/`for` keywords carry `call_site`. `DefaultImplAttribute::to_item_impl` re-spans it onto the user-written key token — mirroring the `override_span` technique that [`delegate_components!`](delegate_components.md#error-spans) uses — while restoring the impl's generics afterward, so a conflict between two default impls for the same key points at the key inside `#[default_impl(Key in …)]` rather than at the whole `#[cgp_impl]` attribute. +The one token in the derived `IsProviderFor` impl that stays on `call_site` is the derived component reference, and deliberately so. It anchors no caret, so a narrower span gains nothing, and giving it the provider trait's span would make go-to-definition on the provider trait a user wrote also offer the component struct — because rust-analyzer maps a source token to its expansion by source range. The [`IsProviderFor` derivation](../asts/cgp_provider.md#itemproviderimpl-and-the-isproviderfor-derivation) keeps it on `call_site`, off every user token's range. + +The one wholly-synthesized item is each `#[default_impl]` delegation impl, built entirely from quasi-quoted tokens whose `impl`/`{ … }` boundary carries `call_site`. `DefaultImplAttribute::to_item_impl` re-spans that boundary onto the user-written key token — with [`override_item_span`](delegate_components.md#error-spans), the same helper the `delegate_components!` impls use — so a conflict between two default impls for the same key points at the key inside `#[default_impl(Key in …)]` rather than at the whole `#[cgp_impl]` attribute. Only the boundary moves; the interior tokens — the provider type, a per-entry generic, each synthesized reference — keep their spans, so the user's tokens stay navigable in an IDE. ## Failure modes @@ -84,8 +86,9 @@ Every `snapshot_cgp_impl!` invocation across the suite is indexed here, since th - [implicit_arguments/cgp_impl_implicit.rs](../../../crates/tests/cgp-tests/tests/implicit_arguments/cgp_impl_implicit.rs) — `#[implicit]` arguments dropped from the signature and turned into `HasField` reads, with the implicit `__Context__` inserted (the `for` clause omitted). - [higher_order_providers/use_provider_impl.rs](../../../crates/tests/cgp-tests/tests/higher_order_providers/use_provider_impl.rs) — a generic higher-order provider `ScaledArea` with `#[use_provider]` completing the inner provider's bound. - [namespaces/default_impls.rs](../../../crates/tests/cgp-tests/tests/namespaces/default_impls.rs) — several `#[cgp_impl]` blocks (`ShowString`, `ShowWithDisplay`, `ShowU32`) providing a generic component, exercising the `#[default_impl]` companion attribute alongside the provider rewrite. +- [basic_delegation/provider_component_override.rs](../../../crates/tests/cgp-tests/tests/basic_delegation/provider_component_override.rs) — the `: ComponentType` override: `#[cgp_impl(new FortyTwo: HasFooComponent)]` targets a component whose marker name (`HasFooComponent`) is not the derived `FooProviderComponent`, so the `IsProviderFor` impl names the overriding component rather than the default. -The `#[cgp_impl(Self)]` bare-impl passthrough has no snapshot yet, and neither does a `#[cgp_impl]` carrying an explicit `: ComponentType` override. +The `#[cgp_impl(Self)]` bare-impl passthrough has no snapshot yet. ## Tests @@ -97,6 +100,7 @@ The behavioral tests confirm the lowered wiring works: - [basic_delegation/impl_self.rs](../../../crates/tests/cgp-tests/tests/basic_delegation/impl_self.rs) exercises the `#[cgp_impl(Self)]` passthrough: a consumer trait implemented directly on a concrete context, forwarding to a provider via `#[use_provider]`. - [basic_delegation/self_in_macro.rs](../../../crates/tests/cgp-tests/tests/basic_delegation/self_in_macro.rs) confirms the token-level `self` rewrite inside a macro body distinguishes a bare `self` value (rewritten) from a `self::` module path (left intact). - [basic_delegation/self_in_nested_item.rs](../../../crates/tests/cgp-tests/tests/basic_delegation/self_in_nested_item.rs) confirms the rewrite stops at a nested item: a local `impl Display for Wrapper` inside a provider method keeps its own `&self` receiver and `self.0` access while the outer `self.name()` is still rewritten to the context. +- [basic_delegation/provider_component_override.rs](../../../crates/tests/cgp-tests/tests/basic_delegation/provider_component_override.rs) wires the overriding component (`HasFooComponent`) to the generated `FortyTwo` and confirms `App` implements `CanDoFoo` — proving the `: ComponentType` override is honored rather than ignored in favor of the derived name. The rejection cases confirm the macro refuses malformed input: diff --git a/docs/implementation/entrypoints/cgp_namespace.md b/docs/implementation/entrypoints/cgp_namespace.md index 70f2653b..af95c152 100644 --- a/docs/implementation/entrypoints/cgp_namespace.md +++ b/docs/implementation/entrypoints/cgp_namespace.md @@ -71,7 +71,7 @@ A **`for` loop's optional `where` clause** is applied. `ForDelegateStatement` pa ## Error spans -Each generated namespace impl is re-spanned onto the entry that produced it, so a coherence conflict (`E0119`) between two entries mapping the same key points at the offending entry rather than at the whole `cgp_namespace!` block. The impls are built with `parse_internal!`, whose structural tokens carry the macro's `call_site` span; `build_namespace_impl` therefore re-spans each finished impl onto the entry's `span` (restoring the generics afterward), the same `respan_impl` technique the `DelegateComponent`/`IsProviderFor` impls use — see [delegate_components.md](delegate_components.md#error-spans). This matters most for a `=>` prefix-rewrite entry, whose key type is a synthesized `PathCons<..>` nest at `call_site`; the entry instead sources its span from the path segments the user wrote, so the caret lands on the duplicated path leaf. The `#[prefix(...)]` attribute impl is built separately (`PrefixAttribute::to_namespace_impl`) and keyed on the component name, which already carries the trait's own span. +Each generated namespace impl is re-spanned onto the entry that produced it, so a coherence conflict (`E0119`) between two entries mapping the same key points at the offending entry rather than at the whole `cgp_namespace!` block. The impls are built with `parse_internal!`, whose tokens carry the macro's `call_site` span; `build_namespace_impl` therefore re-spans each finished impl's boundary tokens (its `impl` keyword and `{ … }` body) onto the entry's `span` — leaving the interior, including the mapped value and any per-entry generic, at its own spans — the same `respan_impl` technique the `DelegateComponent`/`IsProviderFor` impls use — see [delegate_components.md](delegate_components.md#error-spans). This matters most for a `=>` prefix-rewrite entry, whose key type is a synthesized `PathCons<..>` nest at `call_site`; the entry instead sources its span from the path segments the user wrote, so the caret lands on the duplicated path leaf. The `#[prefix(...)]` attribute impl is built separately (`PrefixAttribute::to_namespace_impl`) and keyed on the component name, which already carries the trait's own span. ## Failure modes diff --git a/docs/implementation/entrypoints/check_components.md b/docs/implementation/entrypoints/check_components.md index bb384cf7..3859bc9e 100644 --- a/docs/implementation/entrypoints/check_components.md +++ b/docs/implementation/entrypoints/check_components.md @@ -78,6 +78,6 @@ The behavioral coverage for `check_components!` is the compile-time assertion it - Entry point: `check_components` in [cgp-macro-lib/src/check_components.rs](../../../crates/macros/cgp-macro-lib/src/check_components.rs). - Tables, entries, keys, and values: [cgp-macro-core/src/types/check_components/](../../../crates/macros/cgp-macro-core/src/types/check_components/), documented together with the `delegate_and_check_components!` stack in [asts/check_components.md](../asts/check_components.md). -- The check trait, the `#[check_trait]`/`#[check_providers]` attributes, the `__Check{Context}` name derivation, the supertrait choice, and the span override are all in `table.rs`; the cartesian-product expansion is in `entry.rs`. The span override applies the shared [`override_span`](../../../crates/macros/cgp-macro-core/src/functions/override_span.rs) helper (also used by `delegate_components!`). +- The check trait, the `#[check_trait]`/`#[check_providers]` attributes, the `__Check{Context}` name derivation, the supertrait choice, and the span override are all in `table.rs`; the cartesian-product expansion is in `entry.rs`. The span override applies the [`override_span`](../../../crates/macros/cgp-macro-core/src/functions/override_span.rs) helper, which clobbers *every* token's span unconditionally — here that is the point, since the one shared context type is forced onto each checked component in turn. This is the opposite of what `delegate_components!` needs: its sibling `override_item_span` in the same file re-spans only an impl's boundary tokens so a wired provider's own span survives for IDE navigation. - Fragment construction: [parse_internal!](../macros/parse_internal.md). - The `delegate_and_check_components!` macro reuses this stack; see its [entrypoint document](delegate_and_check_components.md). diff --git a/docs/implementation/entrypoints/delegate_components.md b/docs/implementation/entrypoints/delegate_components.md index 166feaaa..34ba90ca 100644 --- a/docs/implementation/entrypoints/delegate_components.md +++ b/docs/implementation/entrypoints/delegate_components.md @@ -62,7 +62,9 @@ An **`@`-path key** carries a leading `__Wildcard__` generic and lowers the path Each generated impl is re-spanned onto the entry that produced it, so a compiler error about that impl points at the entry the user wrote rather than at the whole `delegate_components!` block. The impls are built with `parse_internal!`, which quasi-quotes their tokens; those tokens carry the macro invocation's `call_site` span, and the only tokens with a narrower span are the interpolated key, value, and target type. A coherence conflict (`E0119`) between two entries mapping the same key is reported on the impl header — its `impl` keyword and trait reference — which is exactly the part that starts at `call_site`, so without correction the error underlines the entire block, and two overlapping entries produce two block-wide carets that say nothing about which entry to fix. -`build_delegate_component_impl` and `build_is_provider_for_impl` fix this through `respan_impl`, which mirrors the `override_span` technique that `check_components!` uses to aim an unsatisfied-bound error at the checked component. It re-spans every token of the finished impl onto the entry's diagnostic span, then restores the impl's original generics. The re-span moves the header — and therefore the `E0119` caret — onto what the user wrote, while restoring the generics keeps a diagnostic about a per-entry generic pointing where the user declared it: an unconstrained parameter's `E0207`, for instance, still lands on the `` rather than being dragged onto the key. The shared helper lives in [functions/override_span.rs](../../../crates/macros/cgp-macro-core/src/functions/override_span.rs). +`build_delegate_component_impl` and `build_is_provider_for_impl` fix this through `respan_impl`, which re-spans only the impl's two *boundary* tokens — its leading `impl` keyword and its trailing `{ … }` body — onto the entry's diagnostic span, and leaves everything between them alone. That is enough because the compiler derives a generated item's span, and therefore the `E0119` caret, by joining its first and last tokens (`first.to(last)`): with the whole impl at `call_site` the caret is the whole invocation, and pulling just those two ends onto the entry collapses it onto what the user wrote. The interior — the trait reference, the reserved `__Context__`/`__Params__` generics, the key, the wired provider, the target type, a per-entry generic — keeps its own spans. + +Leaving the interior in place is not just an economy; it is what keeps the result usable in two ways the whole-impl re-span could not. A diagnostic about a per-entry generic still points where the user declared it — an unconstrained parameter's `E0207` lands on the `` the user wrote rather than being dragged onto the key — because that `` is an interior token the re-span never touches. And a type written in the block stays navigable: rust-analyzer maps a source token to its expanded counterpart purely by source range, ignoring hygiene, so a synthesized reference like `IsProviderFor` or `DelegateComponent` re-spanned onto the key would land on the key's exact range, and go-to-definition on that key would then offer every such collided construct as a candidate. Keeping the interior untouched leaves each synthesized reference at `call_site` (a range no narrower user token shares) and each user token — the wired provider, the target type — at its own span, so navigation resolves to the one right definition. The two tokens that *do* move are a keyword and a delimiter group, never references, so moving them onto the key misleads neither the compiler nor the editor. (An earlier fix re-spanned *every* token of the impl onto the entry; that put `IsProviderFor` and the target type on the key's range and broke go-to-definition, which is why the re-span is now scoped to the boundary.) The [`override_item_span`](../../../crates/macros/cgp-macro-core/src/functions/override_span.rs) helper it uses re-spans a boundary token only when that token is itself synthesized, recognized by its `call_site` span. `check_components!` aims its own error the opposite way, with the unconditional `override_span` helper in the same file, because there the intent *is* to clobber a user token — one shared context type, forced onto each checked component in turn. The diagnostic span is carried explicitly rather than read back from the generated key, because a key type may be *synthesized* and so no longer carry a useful span. This follows the [`EvaluatedCheckEntry.span`](check_components.md) pattern: `EvaluatedDelegateKey`, `EvaluatedDelegateEntry`, and the `namespace`/`for` intermediary `EvaluatedForEntry` each hold a `span` field, populated at eval time from the token the user actually wrote and threaded through to `respan_impl`. Every entry form sources its span this way, so none falls back to `call_site`: a plain `Key: Provider`, `Key -> Value`, or `Key => @path` mapping (and each element of an array key) takes the key type's own span; the `open` header takes the opened component's span; a `namespace`/`for` statement takes the namespace name or the loop mapping's key; and a nested `UseDelegate` table lowers through the same per-entry path, so its inner keys are spanned too. diff --git a/docs/reference/attributes/extend.md b/docs/reference/attributes/extend.md index fc1b715e..bb5fddc7 100644 --- a/docs/reference/attributes/extend.md +++ b/docs/reference/attributes/extend.md @@ -6,7 +6,7 @@ `#[extend(...)]` exists to add supertraits to a CGP trait through an import-like attribute, and in [`#[cgp_fn]`](../macros/cgp_fn.md) it is the only way to do so. A supertrait is a bound that every implementor of a trait must also satisfy, and that every user of the trait may rely on. In `#[cgp_fn]`, the `where` clauses written in the function body are treated as impl-side dependencies and deliberately kept out of the generated trait definition — so there is no place to write a supertrait by hand. `#[extend(...)]` fills that gap: the bounds it lists are promoted onto the trait itself. -The distinction from [`#[uses]`](uses.md) is the whole point. Both attributes accept the same simplified trait-path syntax and both feel like imports, but they import into different places. `#[uses(...)]` adds a hidden `Self` bound to the impl only — a private dependency that callers never see. `#[extend(...)]` adds a supertrait to the trait — a public requirement that becomes part of the contract. The natural way to describe the pair is that `#[extend(...)]` is the `pub use` equivalent of `#[uses(...)]`: where `#[uses(...)]` imports a capability for the implementation's own use, `#[extend(...)]` re-exports it as part of what the trait guarantees. +The distinction from [`#[uses]`](uses.md) is the whole point. Both attributes accept the same trait-bound syntax and both feel like imports, but they import into different places. `#[uses(...)]` adds a hidden `Self` bound to the impl only — a private dependency that callers never see. `#[extend(...)]` adds a supertrait to the trait — a public requirement that becomes part of the contract. The natural way to describe the pair is that `#[extend(...)]` is the `pub use` equivalent of `#[uses(...)]`: where `#[uses(...)]` imports a capability for the implementation's own use, `#[extend(...)]` re-exports it as part of what the trait guarantees. This framing also explains which supertraits `#[extend(...)]` is for and why it is the preferred way to declare them. `#[extend(...)]` is the tool for a **non-type capability supertrait** — a trait like `HasName` or `CanCalculateArea` that a component depends on but whose associated types it does not name in its own signatures. In [`#[cgp_component]`](../macros/cgp_component.md), prefer `#[extend(HasName)]` over the native `pub trait CanGreet: HasName` form: the native `:` syntax reads as inheritance to programmers coming from object-oriented languages, suggesting an is-a relationship to a parent class that a CGP component does not have, whereas `#[extend(...)]` reads as importing a capability the trait re-exports — which is what a CGP supertrait actually is. When the supertrait is instead an **abstract-type component** whose associated type the signatures reference — such as [`HasErrorType`](../components/has_error_type.md) through its `Error` — prefer [`#[use_type]`](use_type.md), which adds the supertrait *and* rewrites the bare type; `#[use_type]` is the recommended form for abstract-type components. In [`#[cgp_fn]`](../macros/cgp_fn.md), where direct supertrait syntax is unavailable because the body's `where` clauses are impl-side dependencies, `#[extend(...)]` is the only mechanism for a plain capability supertrait. diff --git a/docs/reference/attributes/extend_where.md b/docs/reference/attributes/extend_where.md index 663bf22a..a42bf4fb 100644 --- a/docs/reference/attributes/extend_where.md +++ b/docs/reference/attributes/extend_where.md @@ -16,7 +16,7 @@ It complements [`#[extend]`](extend.md). Where `#[extend(...)]` adds *supertrait #[extend_where(Scalar: Clone)] ``` -Unlike [`#[uses]`](uses.md) and [`#[extend]`](extend.md), which accept only the simplified trait-path form, `#[extend_where(...)]` accepts arbitrary predicates — the same things a Rust `where` clause allows, including associated-type-equality bounds. Each predicate is added verbatim to the generated trait's `where` clause. +Unlike [`#[uses]`](uses.md), whose entries are bounds attached to `Self` on the generated impl, and [`#[extend]`](extend.md), whose entries become supertraits of the generated trait, `#[extend_where(...)]` accepts arbitrary `where` predicates — a bound on any type, not only `Self` — added verbatim to the generated trait's `where` clause. Each predicate can carry anything a Rust `where` clause allows, including associated-type-equality bounds. `#[extend_where(...)]` is supported only in [`#[cgp_fn]`](../macros/cgp_fn.md). It has no meaning in [`#[cgp_impl]`](../macros/cgp_impl.md) or [`#[cgp_component]`](../macros/cgp_component.md), because in those macros the `where` clause you write is already part of the trait definition — there is nothing to promote, so write the bound as a normal `where` clause directly. diff --git a/docs/reference/attributes/implicit.md b/docs/reference/attributes/implicit.md index a137ef35..35e735c0 100644 --- a/docs/reference/attributes/implicit.md +++ b/docs/reference/attributes/implicit.md @@ -111,7 +111,7 @@ fn print_area(rect: &Rectangle) { ## Related constructs -`#[implicit]` is most often used inside [`#[cgp_fn]`](../macros/cgp_fn.md), which turns a function into a single-implementation capability, and inside [`#[cgp_impl]`](../macros/cgp_impl.md), which writes a provider for an existing component. It relies on [`#[derive(HasField)]`](../derives/derive_has_field.md) on the context to supply the field accessors that the generated bounds require. Its access rules — `.clone()` for owned values, `.as_str()` for `&str` — are shared with [`#[cgp_auto_getter]`](../macros/cgp_auto_getter.md), which defines a reusable getter *capability* trait; prefer an implicit argument for reading a field as a provider's own input, and reserve `#[cgp_auto_getter]` for the case where the field should be published as a `self.name()` accessor other providers depend on. To bring in other CGP capabilities alongside implicit arguments, combine `#[implicit]` with [`#[uses]`](uses.md). +`#[implicit]` is most often used inside [`#[cgp_fn]`](../macros/cgp_fn.md), which turns a function into a single-implementation capability, and inside [`#[cgp_impl]`](../macros/cgp_impl.md), which writes a provider for an existing component. It relies on [`#[derive(HasField)]`](../derives/derive_has_field.md) on the context to supply the field accessors that the generated bounds require. Its access rules — `.clone()` for owned values, `.as_str()` for `&str`, and a plain `&T` read by reference with no clone — are shared with [`#[cgp_auto_getter]`](../macros/cgp_auto_getter.md), which defines a reusable getter *capability* trait. An implicit argument is the preferred, default way to read any field from a provider's own context; reserve `#[cgp_auto_getter]` for the cases an implicit argument cannot cover — a field that lives on a type other than the provider's context (a getter required as a `where` bound on that type), an accessor that must exist as a named capability other code depends on, or a getter carrying an associated type inferred from the field. To bring in other CGP capabilities alongside implicit arguments, combine `#[implicit]` with [`#[uses]`](uses.md). ## Source diff --git a/docs/reference/attributes/use_provider.md b/docs/reference/attributes/use_provider.md index 602b31ea..4ddf0328 100644 --- a/docs/reference/attributes/use_provider.md +++ b/docs/reference/attributes/use_provider.md @@ -18,7 +18,7 @@ The body of such a provider still calls the inner provider as an associated func #[use_provider(InnerCalculator: AreaCalculator)] ``` -`InnerCalculator` is the provider type — usually a generic parameter of the impl — and `AreaCalculator` is the provider trait whose `Self`/context argument the macro fills in. The trait may carry its own further generic arguments after the context slot, and these are preserved in order behind the inserted `Self`. Several `#[use_provider]` bounds may be supplied — separated by commas inside one attribute or split across stacked attributes — to bind more than one inner provider. +`InnerCalculator` is the provider type — usually a generic parameter of the impl — and `AreaCalculator` is the provider trait whose `Self`/context argument the macro fills in. The trait may carry its own further generic arguments after the context slot, and these are preserved in order behind the inserted `Self`. When a provider binds more than one inner provider, prefer supplying all the bounds in a single `#[use_provider]` attribute separated by commas — `#[use_provider(Inner1: TraitA, Inner2: TraitB)]` — since one attribute reads as a single dependency list. Splitting the bounds across stacked attributes behaves identically, but reach for a second attribute only when a real reason calls for it rather than as the default. ## Expansion diff --git a/docs/reference/attributes/use_type.md b/docs/reference/attributes/use_type.md index 4859395f..6e866dbf 100644 --- a/docs/reference/attributes/use_type.md +++ b/docs/reference/attributes/use_type.md @@ -24,7 +24,9 @@ Because the `.` is the only separator the macro looks for, the trait may be writ A leading `@` changes the rewrite target from `Self` to a named type, which is how foreign abstract types are imported. The form `#[use_type(@Types.HasScalarType.Scalar)]` treats the first segment, `Types`, as the context type and rewrites `Scalar` to `::Scalar`. `Types` is typically a generic parameter of the function or impl rather than `Self`, which lets a trait pull an abstract type from a parameter instead of from the implementing context. -Several types from the same trait can be imported in one attribute using a braced list, and each entry may be renamed with `as` or constrained with `=`. The braced form `#[use_type(HasFooType.{Foo, Bar as Baz})]` imports `Foo` under its own name and `Bar` under the local alias `Baz`. The equality form `#[use_type(HasScalarType.{Scalar = f64})]` imports `Scalar` and additionally constrains it, emitting `Self: HasScalarType` in the `where` clause. Multiple `#[use_type]` attributes may also be stacked, and several trait paths may be separated by commas inside one attribute, as in `#[use_type(HasBarType.{Bar as Baz = Foo}, HasFooType.Foo)]`. +Several types from the same trait can be imported in one attribute using a braced list, and each entry may be renamed with `as` or constrained with `=`. The braced form `#[use_type(HasFooType.{Foo, Bar as Baz})]` imports `Foo` under its own name and `Bar` under the local alias `Baz`. The equality form `#[use_type(HasScalarType.{Scalar = f64})]` imports `Scalar` and additionally constrains it, emitting `Self: HasScalarType` in the `where` clause. + +When a definition imports types from several traits at once, prefer combining them into a single `#[use_type]` attribute by separating the trait paths with commas — `#[use_type(HasUserIdType.UserId, HasCurrencyType.Currency, HasErrorType.Error)]` — since one attribute reads as a single import list. Stacking multiple `#[use_type]` attributes is also accepted and behaves identically, as in `#[use_type(HasBarType.{Bar as Baz = Foo}, HasFooType.Foo)]` written across two lines, but reach for a second attribute only when a real reason calls for it rather than as the default. Two restrictions guard against ambiguous imports. No two imports may resolve to the same bare identifier or alias — whether they appear in different specs or in the same braced list, and on `#[cgp_component]` as well as on `#[cgp_fn]` and `#[cgp_impl]` — because the substitution could then only pick one and would silently drop the rest; a collision is a compile error. And the `= ...` type-equality form is rejected on `#[cgp_component]` specifically, because a trait definition cannot carry the impl-side equality constraint the equality form produces; equality constraints belong on `#[cgp_fn]` and `#[cgp_impl]`, where they become `where` bounds. diff --git a/docs/reference/attributes/uses.md b/docs/reference/attributes/uses.md index 7e63e7b2..aa24cdfd 100644 --- a/docs/reference/attributes/uses.md +++ b/docs/reference/attributes/uses.md @@ -12,15 +12,15 @@ This framing is the reason `#[uses(...)]` is recommended over hand-written `Self ## Syntax -`#[uses(...)]` takes a comma-separated list of trait references, each in the simplified form `TraitIdent`: +`#[uses(...)]` takes a comma-separated list of trait bounds, each of which becomes a `Self:` predicate on the generated impl: ```rust #[uses(RectangleArea, CanCalculateArea)] ``` -Each entry is the name of a capability, optionally with generic type arguments. A bare `RectangleArea` becomes `Self: RectangleArea`; a parameterized `CanCompute` becomes `Self: CanCompute`. The entries may be split across multiple `#[uses(...)]` attributes on the same item, and they accumulate. +Each entry is the name of a capability, optionally with generic type arguments. A bare `RectangleArea` becomes `Self: RectangleArea`; a parameterized `CanCompute` becomes `Self: CanCompute`. When a provider depends on several capabilities, prefer listing them all in a single `#[uses(...)]` attribute — `#[uses(RectangleArea, CanCalculateArea)]` — since one combined import reads as a single dependency list. The entries may also be split across multiple `#[uses(...)]` attributes on the same item, and they accumulate, but stack a second attribute only when a genuine reason calls for it rather than as the default. -The syntax deliberately supports only this simplified trait-path form. Because the attribute is meant to read like an import, it does not accept the more complex bound forms that Rust allows in a `where` clause — in particular, associated-type-equality bounds such as `Iterator` are not expressible here. When a dependency genuinely needs such a bound, write it as an explicit `where` clause in the function body instead; `#[uses(...)]` is for the common, import-shaped case. +The idiomatic entry is the simple `Trait` form, since the attribute is meant to read like an import. An entry may nonetheless be any bound a Rust `where` clause accepts, including an associated-type-equality binding (`HasErrorType`), a higher-ranked bound, or a lifetime bound; each lands on the impl's `where` clause verbatim. Use that generality sparingly. To pin an abstract type to a concrete one, prefer the [`#[use_type]`](use_type.md) equality form `#[use_type(HasErrorType.{Error = anyhow::Error})]`, which adds the bound and also rewrites bare mentions of the type; and to place a bound on the generated *trait* rather than only its impl, use [`#[extend_where]`](extend_where.md) (on `#[cgp_fn]`). `#[uses(...)]` is accepted in both [`#[cgp_fn]`](../macros/cgp_fn.md) and [`#[cgp_impl]`](../macros/cgp_impl.md). In either case it imports capabilities into the provider being defined, and those capabilities may themselves be defined with either [`#[cgp_fn]`](../macros/cgp_fn.md) or [`#[cgp_component]`](../macros/cgp_component.md) — the attribute does not care how the imported capability was produced, only that it is a trait the context can implement. @@ -101,7 +101,7 @@ The `#[uses(CanCalculateArea)]` attribute adds `Self: CanCalculateArea` to the g ## Related constructs -`#[uses(...)]` is the impl-side counterpart to [`#[extend]`](extend.md): both add trait bounds to a generated construct, but `#[uses(...)]` adds hidden impl-side `where` bounds while `#[extend]` adds public supertraits — the `use` versus `pub use` distinction. It is used inside [`#[cgp_fn]`](../macros/cgp_fn.md) and [`#[cgp_impl]`](../macros/cgp_impl.md), and the capabilities it imports are typically defined with [`#[cgp_fn]`](../macros/cgp_fn.md) or [`#[cgp_component]`](../macros/cgp_component.md). To import an abstract associated type rather than a method capability, use [`#[use_type]`](use_type.md); to bring in a context field as a function argument, use [`#[implicit]`](implicit.md). For bounds too complex for the simplified syntax — anything with associated-type equality — fall back to an explicit `where` clause in the body. +`#[uses(...)]` is the impl-side counterpart to [`#[extend]`](extend.md): both add trait bounds to a generated construct, but `#[uses(...)]` adds hidden impl-side `where` bounds while `#[extend]` adds public supertraits — the `use` versus `pub use` distinction. It is used inside [`#[cgp_fn]`](../macros/cgp_fn.md) and [`#[cgp_impl]`](../macros/cgp_impl.md), and the capabilities it imports are typically defined with [`#[cgp_fn]`](../macros/cgp_fn.md) or [`#[cgp_component]`](../macros/cgp_component.md). To import an abstract associated type rather than a method capability, use [`#[use_type]`](use_type.md); to bring in a context field as a function argument, use [`#[implicit]`](implicit.md). To pin an abstract type, prefer the [`#[use_type]`](use_type.md) equality form over spelling the equality in `#[uses(...)]`; to make a bound part of the generated trait rather than only its impl, use [`#[extend_where]`](extend_where.md). ## Source diff --git a/docs/reference/macros/cgp_auto_getter.md b/docs/reference/macros/cgp_auto_getter.md index e09c2d68..03a5aaf2 100644 --- a/docs/reference/macros/cgp_auto_getter.md +++ b/docs/reference/macros/cgp_auto_getter.md @@ -6,11 +6,13 @@ `#[cgp_auto_getter]` exists to publish a context field as a reusable getter *capability* — a named `self.name()` accessor that other providers depend on. Reading a value out of a context is the most basic form of dependency injection in CGP, and the underlying mechanism — `HasField` — is precise but verbose and unfamiliar to most Rust developers. The macro lets you state the intent in ordinary trait-method syntax (`fn name(&self) -> &str;`) and generates the `HasField`-based plumbing for you. -Reach for `#[cgp_auto_getter]` when the field is a shared capability, not when a single provider needs a value for its own use. For the common case of reading a field inside one provider, an [`#[implicit]`](../attributes/implicit.md) argument is the preferred, lighter form: it injects the value as an ordinary-looking parameter with no separate trait to declare. A getter trait earns its keep when the accessor is genuinely shared — depended on by several providers through [`#[uses]`](../attributes/uses.md), or carrying an associated type inferred from the field so the type stays abstract. Both desugar to the same `HasField` bound and share the same access rules, so the choice is about whether the value is a published capability or a private input. +Prefer an [`#[implicit]`](../attributes/implicit.md) argument over `#[cgp_auto_getter]`, and reach for the getter trait only when an implicit argument genuinely cannot do the job. For the ordinary case — a provider reading a field from its own context — an implicit argument injects the value as an ordinary-looking parameter with no separate trait to declare, and it does so with the same `HasField` bound and the same access rules a getter would use, so the getter trait adds a declaration without buying anything. Because an implicit argument reads only from the provider's own context (`self`), and reads a plain `&T` argument by reference without cloning, it covers the common read directly, including a field shared by several providers that each declare the same implicit argument. + +A getter trait earns its keep in the cases an implicit argument cannot reach. The sharpest one is a field that lives on a type *other* than the provider's own context: a getter such as `HasBasicAuthHeader` reads from a request or payload type and is required as a `where` bound on that type (`Request: HasBasicAuthHeader`), so there is no `self` field for an implicit argument to read. The other cases are an accessor that must exist as a *named capability* other code depends on through [`#[uses]`](../attributes/uses.md) or a supertrait, and a getter that carries an *associated type inferred from the field* so the type stays abstract for callers to name. Use `#[cgp_auto_getter]` sparingly, for these situations; reach for an implicit argument everywhere else. The defining trade-off of `#[cgp_auto_getter]` is that it produces exactly one implementation and no CGP wiring. Unlike a full component, there is no provider trait, no component name, and no delegation table; the macro emits a blanket impl over a generic context, and any context that derives `HasField` with a matching field automatically satisfies the trait. This makes it the lightest-weight getter construct — ideal when the field name in the context always matches the method name and you never need an alternative implementation. -The cost of that simplicity is rigidity. Because the field tag is derived directly from the method name, the context *must* expose a field of exactly that name. When you need the field name to differ from the method name, or you want the getter to be swappable through wiring, [`#[cgp_getter]`](cgp_getter.md) builds the same convenience on top of a real CGP component — but that is an advanced tool reserved for when a context needs full control over which field a getter reads from, and most getters do not. Prefer `#[cgp_auto_getter]` for a published accessor and an implicit argument for a private read; reach for `#[cgp_getter]` only when field-name decoupling is specifically wanted. +The cost of that simplicity is rigidity. Because the field tag is derived directly from the method name, the context *must* expose a field of exactly that name. When you need the field name to differ from the method name, or you want the getter to be swappable through wiring, [`#[cgp_getter]`](cgp_getter.md) builds the same convenience on top of a real CGP component — but that is an advanced tool reserved for when a context needs full control over which field a getter reads from, and most getters do not. So among the getter constructs, `#[cgp_auto_getter]` is the default and `#[cgp_getter]` the advanced fallback for field-name decoupling — but a getter trait at all is the sparing choice, taken only when an implicit argument cannot reach the field, as the Purpose section describes. ## Syntax diff --git a/docs/skills/cgp/SKILL.md b/docs/skills/cgp/SKILL.md index 076c1fbf..ad99c126 100644 --- a/docs/skills/cgp/SKILL.md +++ b/docs/skills/cgp/SKILL.md @@ -149,11 +149,12 @@ wrong, it is what you *read* in generated code and legacy wiring, not what you * | To… | Prefer | Not (legacy / advanced / read-only) | |---|---|---| | write a provider | `#[cgp_impl]`, header `impl Trait` (omit `for Context`) | raw `#[cgp_provider]` / `#[cgp_new_provider]` | -| read a context field for your own use | an `#[implicit]` argument | a getter trait declared just to read it | -| publish a shared accessor | `#[cgp_auto_getter]` | `#[cgp_getter]` (only for per-context field choice) | +| read a field from your own context | an `#[implicit]` argument | `#[cgp_auto_getter]` / any getter trait declared just to read it | +| declare a getter (field on *another* type, or a named shared capability) | `#[cgp_auto_getter]`, used sparingly | `#[cgp_getter]` (only for per-context field choice) | | require a capability | `#[uses(Trait)]` | `where Self: Trait` | | require an inner provider | `#[use_provider(P: Trait)]` | `where P: Trait` | | name an abstract type (e.g. `Error`) | `#[use_type(Trait.Type)]` + the bare alias | `: Trait` supertrait + `Self::Type` | +| pass several args to `#[uses]` / `#[use_type]` / `#[use_provider]` | one attribute, comma-separated | repeating the same attribute | | add a capability supertrait | `#[extend(Trait)]` | native `pub trait …: Supertrait` | | dispatch a component per type | the `open` statement (or a namespace) | `#[derive_delegate]` + `UseDelegate` tables | | verify a context is fully wired | separate `check_components!` (or `delegate_and_check_components!` for a basic starter context) | leaving a context's wiring unchecked | @@ -294,25 +295,43 @@ visible machinery for syntax that reads like ordinary Rust: - **Declare dependencies with attributes, not hand-written bounds:** capability dependencies with [`#[uses(...)]`](references/functions-and-getters.md), inner-provider dependencies with [`#[use_provider(...)]`](references/higher-order-providers.md), instead of raw `Self:` / - `Provider: …` `where` clauses. + `Provider: …` `where` clauses. When one of these attributes — or + [`#[use_type]`](references/abstract-types.md) — carries several arguments, put them all in one + attribute separated by commas (`#[uses(A, B)]`, `#[use_type(T.X, U.Y)]`) rather than stacking the + same attribute repeatedly; one attribute reads as a single dependency list. - **Read context fields with [`#[implicit]`](references/functions-and-getters.md) arguments** rather - than a getter trait. Reserve `#[cgp_auto_getter]` for a *published* accessor several providers - share, and `#[cgp_getter]` for the advanced case of choosing the source field per context. + than a getter trait — this is the default for *any* field a provider reads from its own context, + including one several providers each read. An implicit argument reads only from `self` and takes a + plain `&T` by reference without cloning. Use `#[cgp_auto_getter]` sparingly, only where an implicit + argument cannot reach: a getter for a field on *another* type required as a `where` bound on it + (`Request: HasBasicAuthHeader`), an accessor other code depends on as a named capability, or a + getter carrying an associated type inferred from the field. Reserve `#[cgp_getter]` for the advanced + case of choosing the source field per context. - **Add non-type capability supertraits with [`#[extend(...)]`](references/functions-and-getters.md)** rather than native `: Supertrait` syntax, which reads as OOP-style inheritance rather than a capability import. - **Import abstract types with [`#[use_type]`](references/abstract-types.md)**, writing the bare alias (`Scalar`, `Error`) instead of a hand-written `: HasScalarType` supertrait and a qualified `Self::Scalar` at every use. This holds even in `#[cgp_component]` definitions: prefer - `#[use_type(HasErrorType.Error)]` over `: HasErrorType` + `Self::Error`. + `#[use_type(HasErrorType.Error)]` over `: HasErrorType` + `Self::Error`. When a provider *pins* an + abstract type to a concrete one — a `where Self: HasErrorType` clause — express + that with the equality form `#[use_type(HasErrorType.{Error = AppError})]`, which emits the same + `Self: HasErrorType` bound; the right-hand side may even name another imported + alias (`#[use_type(HasPasswordType.Password, HasHashedPasswordType.{HashedPassword = Password})]` + unifies two abstract types). The equality form is a `#[cgp_impl]`/`#[cgp_fn]` tool — it is rejected + on `#[cgp_component]`. - **Dispatch a generic-parameter component with the `open` statement or a namespace**, skipping `#[derive_delegate]`/`UseDelegate` when defining a new component. The explicit forms remain correct and are what you *read* in generated code and desugaring; the -exceptions that still need them are narrow — an associated-type-equality bound (`Iterator`) -that `#[uses]` cannot spell, a lifetime or HRTB that forces a named context, or a **local** -associated type such as `Self::Output`, which stays qualified because it is the trait's own type, -not an imported abstract one. For the full legacy-to-modern before/after mapping of each idiom — the +exceptions that still need them are narrow — an associated-type-equality bound on a **non-abstract-type** +trait (`Iterator`, `From`), which `#[use_type]` cannot spell and which reads more clearly +as an explicit `where` clause than crammed into an import-shaped `#[uses]` (which now accepts it), a +lifetime or HRTB that forces a named context, or a **local** associated type such as `Self::Output`, +which stays qualified because it is the trait's own type, not an imported abstract one. Do *not* leave an +equality bound on an **abstract-type** trait (`Self: HasErrorType`) as a hand-written +`where` clause — that is exactly what the `#[use_type]` equality form `#[use_type(HasErrorType.{Error = AppError})]` +replaces; only equality on a trait you would never `#[use_type]` from stays an explicit `where`. For the full legacy-to-modern before/after mapping of each idiom — the reference to load whenever you read or modernize existing CGP — see [modern-idioms](references/modern-idioms.md). @@ -527,8 +546,9 @@ Prefer implicit arguments for basic code — they make CGP look like ordinary fu ## `#[uses]`, `#[extend]`, `#[extend_where]` `#[uses(TraitA, TraitB)]` (on `#[cgp_fn]` or `#[cgp_impl]`) imports `Self` trait bounds, read -like a `use` statement — it accepts only the simple `Trait` form (no associated-type -equality; write those as explicit `where` clauses): +like a `use` statement. The simple `Trait` form is idiomatic, but any `where`-clause bound is +accepted, including associated-type equality (`HasErrorType`) — prefer `#[use_type]`'s +equality form for abstract-type pins: ```rust #[cgp_fn] @@ -553,13 +573,18 @@ expose as a trait parameter (e.g. `#[impl_generics(Name: Display)]` over an `#[i ## Getters: `#[cgp_auto_getter]`, `#[cgp_getter]`, `UseField` -Getter traits are for *publishing* a context field as a shared capability, not for a provider -reading a value for its own use — for that, prefer an `#[implicit]` argument (above), which needs no -separate trait. Reach for a getter trait only when the accessor is genuinely shared: depended on by -several providers through `#[uses(HasName)]`, or carrying an associated type inferred from the field. +An `#[implicit]` argument (above) is the default way to read a context field, so a getter trait is +used *sparingly* — only where an implicit argument cannot reach. Because an implicit argument reads +only from the provider's own `self` (and takes a plain `&T` by reference, no clone), it covers every +same-context read, even a field several providers each consume. A getter trait earns its keep in +three cases it cannot handle: a field that lives on a type *other* than the provider's context, where +the getter is required as a `where` bound on that type (`Request: HasBasicAuthHeader`, so there +is no `self` field to read); an accessor other code depends on as a *named* capability through +`#[uses(HasName)]` or a supertrait; and a getter carrying an *associated type inferred from the +field* so the type stays abstract for callers. `#[cgp_auto_getter]` generates a blanket getter impl over `HasField`, with the field name taken from -the method name, and is the getter form to prefer: +the method name, and is the getter form to prefer for those cases: ```rust #[cgp_auto_getter] diff --git a/docs/skills/cgp/references/abstract-types.md b/docs/skills/cgp/references/abstract-types.md index 139b02cf..1a8e0b5f 100644 --- a/docs/skills/cgp/references/abstract-types.md +++ b/docs/skills/cgp/references/abstract-types.md @@ -147,7 +147,7 @@ where The substitution is purely textual at the type level — it matches single-segment, argument-free type paths whose identifier equals the imported name — so a bare `Scalar` in the return type, an implicit-argument annotation, or a `let` binding inside the body is all rewritten the same way. Beyond saving keystrokes, the always-qualified rewrite removes the ambiguity the bare form cannot express, which is why this is the default way to import abstract types in all three macros. -The attribute has a few richer forms worth knowing. A leading `@` changes the rewrite target from `Self` to a named type, which imports a *foreign* abstract type from a generic parameter: `#[use_type(@Types.HasScalarType.Scalar)]` rewrites `Scalar` to `::Scalar` and adds `Types: HasScalarType` as a `where` bound rather than a supertrait. A braced list imports several types from one trait, each optionally renamed with `as` or constrained with `=`: `#[use_type(HasScalarType.{Scalar = f64})]` both imports `Scalar` and emits `Self: HasScalarType`, pinning it. The `= ...` equality form is rejected on `#[cgp_component]`, since a trait definition cannot carry the impl-side equality constraint it produces; it belongs on `#[cgp_fn]` and `#[cgp_impl]`. Two imports may not resolve to the same identifier or alias — across specs or within one braced list — since the substitution could then match only one; a collision is a compile error on every host macro. +The attribute has a few richer forms worth knowing. A leading `@` changes the rewrite target from `Self` to a named type, which imports a *foreign* abstract type from a generic parameter: `#[use_type(@Types.HasScalarType.Scalar)]` rewrites `Scalar` to `::Scalar` and adds `Types: HasScalarType` as a `where` bound rather than a supertrait. A braced list imports several types from one trait, each optionally renamed with `as` or constrained with `=`: `#[use_type(HasScalarType.{Scalar = f64})]` both imports `Scalar` and emits `Self: HasScalarType`, pinning it. This equality form is the modern replacement for a hand-written `where Self: HasScalarType` clause on a `#[cgp_impl]` or `#[cgp_fn]` — prefer moving such a pin into the attribute rather than leaving it as an explicit `where`. The right-hand side of `=` may name another imported alias to *unify* two abstract types: `#[use_type(HasPasswordType.Password, HasHashedPasswordType.{HashedPassword = Password})]` emits `Self: HasHashedPasswordType::Password>`, tying the two together. To import types from *several* traits, separate the trait paths with commas inside one attribute — `#[use_type(HasUserIdType.UserId, HasCurrencyType.Currency, HasErrorType.Error)]` — and prefer that combined form over stacking one `#[use_type]` attribute per trait, since it reads as a single import list; stacked attributes behave identically but are for when a real reason calls for it. The `= ...` equality form is rejected on `#[cgp_component]`, since a trait definition cannot carry the impl-side equality constraint it produces; it belongs on `#[cgp_fn]` and `#[cgp_impl]`. Two imports may not resolve to the same identifier or alias — across specs or within one braced list — since the substitution could then match only one; a collision is a compile error on every host macro. ## Sharing one type across contexts diff --git a/docs/skills/cgp/references/functions-and-getters.md b/docs/skills/cgp/references/functions-and-getters.md index 4657aedf..26777466 100644 --- a/docs/skills/cgp/references/functions-and-getters.md +++ b/docs/skills/cgp/references/functions-and-getters.md @@ -138,7 +138,7 @@ pub fn scaled_rectangle_area(&self, #[implicit] scale_factor: f64) -> f64 { } ``` -This adds `Self: RectangleArea` to the generated impl's `where` clause, alongside the `HasField` bound from the implicit `scale_factor` — the imported bound lands on the impl only, never on the trait, exactly like writing `where Self: RectangleArea` by hand. The syntax accepts only the simplified `TraitIdent` form, deliberately: because it is meant to read like an import, it does not accept associated-type-equality bounds such as `Iterator` — write those as an explicit `where` clause in the body instead. `#[uses(...)]` works in both `#[cgp_fn]` and `#[cgp_impl]`, and the imported capability may itself be defined either way. +This adds `Self: RectangleArea` to the generated impl's `where` clause, alongside the `HasField` bound from the implicit `scale_factor` — the imported bound lands on the impl only, never on the trait, exactly like writing `where Self: RectangleArea` by hand. The simple `TraitIdent` form is the idiomatic one, since the attribute is meant to read like an import, but an entry may be any bound a `where` clause accepts, including an associated-type-equality binding such as `HasErrorType`. Reach for that sparingly: to pin an *abstract type*, prefer the [`#[use_type]` equality form](abstract-types.md) `#[use_type(HasErrorType.{Error = AppError})]`, which adds the bound *and* rewrites the type, over spelling the equality in `#[uses]`. When a provider imports several capabilities, list them all in one attribute separated by commas — `#[uses(CanQueryUserBalance, CanRaiseHttpError)]` — rather than stacking `#[uses(...)]` repeatedly; the combined form reads as a single dependency list. `#[uses(...)]` works in both `#[cgp_fn]` and `#[cgp_impl]`, and the imported capability may itself be defined either way. ## Adding supertraits and trait bounds: `#[extend]` and `#[extend_where]` @@ -177,7 +177,7 @@ The `Scalar: Mul` bound from the body stays an impl-side dependency, while `Scal ## Getter traits: `#[cgp_auto_getter]` -A getter trait exposes a context field as a reusable `self.name()` accessor, and `#[cgp_auto_getter]` generates its single blanket impl by reading the field whose name matches the method name. Reserve it for the case where the field is a *published capability* — an accessor other providers depend on through `#[uses(HasName)]`, or one carrying an associated type inferred from the field — not for a provider reading a value for its own use. For that common case, prefer an [implicit argument](#field-access-dressed-as-a-function-argument-implicit): it injects the value as an ordinary-looking parameter with no separate trait to declare, and a value used throughout a body is simply bound once at the top. The macro takes no arguments and re-emits the trait verbatim, adding a blanket impl over `__Context__` keyed by the method name as a `Symbol!`: +A getter trait exposes a context field as a reusable `self.name()` accessor, and `#[cgp_auto_getter]` generates its single blanket impl by reading the field whose name matches the method name. Use it *sparingly* — an [implicit argument](#field-access-dressed-as-a-function-argument-implicit) is the default for reading a field, since it injects the value as an ordinary-looking parameter with no separate trait to declare, reads only from the provider's own `self`, and takes a plain `&T` by reference without cloning. Because that covers every same-context read — even a field several providers each consume, written as the same implicit argument in each — a getter trait earns its keep only in the cases an implicit argument cannot reach: the field 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); the accessor must exist as a *named* capability other code depends on through `#[uses(HasName)]` or a supertrait; or the getter carries an associated type inferred from the field so the type stays abstract for callers. The macro takes no arguments and re-emits the trait verbatim, adding a blanket impl over `__Context__` keyed by the method name as a `Symbol!`: ```rust #[cgp_auto_getter] @@ -282,7 +282,7 @@ The explicit form is more verbose but requires no understanding of `HasField` or ## Choosing between the constructs -The constructs here divide along two axes: whether the value is a private input or a published capability, and how much control the implementation needs. For reading a field into a provider — the common case — an `#[implicit]` argument is the default: it keeps the access local and the code reading like a plain function, whether the value is used once or throughout the body. Promote a field to a getter trait only when it is a shared capability: `#[cgp_auto_getter]` when the field name matches the method name (the usual getter), and `#[cgp_getter]` (with `UseField`) as the advanced tool reserved for when the source field name must be chosen per context at wiring time. For a whole capability rather than a single field, `#[cgp_fn]` defines one with no wiring when a single implementation suffices, and a full [component](components.md) when many providers must coexist. All of them rest on the same `HasField` machinery and the same access rules, so mixing them carries no conceptual overhead. +For reading a field into a provider — the common case — an `#[implicit]` argument is the default: it keeps the access local and the code reading like a plain function, whether the value is used once or throughout the body, and it applies to any field on the provider's own context, even one several providers each read. Promote a field to a getter trait only when an implicit argument cannot reach it — the field lives on another type and the getter is a `where` bound on it, the accessor must be a named capability other code depends on, or it carries an associated type inferred from the field — and then use `#[cgp_auto_getter]` when the field name matches the method name (the usual getter), and `#[cgp_getter]` (with `UseField`) as the advanced tool reserved for when the source field name must be chosen per context at wiring time. For a whole capability rather than a single field, `#[cgp_fn]` defines one with no wiring when a single implementation suffices, and a full [component](components.md) when many providers must coexist. All of them rest on the same `HasField` machinery and the same access rules, so mixing them carries no conceptual overhead. ## Further reference diff --git a/docs/skills/cgp/references/higher-order-providers.md b/docs/skills/cgp/references/higher-order-providers.md index cde2eed4..5224ebe7 100644 --- a/docs/skills/cgp/references/higher-order-providers.md +++ b/docs/skills/cgp/references/higher-order-providers.md @@ -37,7 +37,7 @@ impl AreaCalculator { } ``` -The author writes `InnerCalculator: AreaCalculator` and the macro inserts the context type at index 0 of the trait's generic arguments, emitting `InnerCalculator: AreaCalculator` into the `where` clause — the two snippets above are equivalent. The shape it parses is `Provider: Trait`: a provider type, a colon, and one or more provider-trait bounds joined by `+`; the trait may carry further generic arguments after the context slot, preserved in order behind the inserted `Self`. Bind several inner providers by separating bounds with commas in one attribute or by stacking multiple attributes. The same attribute works on `#[cgp_fn]`. +The author writes `InnerCalculator: AreaCalculator` and the macro inserts the context type at index 0 of the trait's generic arguments, emitting `InnerCalculator: AreaCalculator` into the `where` clause — the two snippets above are equivalent. The shape it parses is `Provider: Trait`: a provider type, a colon, and one or more provider-trait bounds joined by `+`; the trait may carry further generic arguments after the context slot, preserved in order behind the inserted `Self`. To bind several inner providers, list them all in one attribute separated by commas — `#[use_provider(A: TraitA, B: TraitB)]` — rather than stacking a separate attribute per provider; the combined form reads as a single dependency list, and stacked attributes behave identically but are for when a real reason calls for it. The same attribute works on `#[cgp_fn]`. What `#[use_provider]` does *not* do is rewrite the body. It completes the bound only; the inner provider is still invoked as the associated function `InnerCalculator::area(self)`, with `self` passed as the explicit context. There is no call-site form — a bare `#[use_provider(InnerCalculator)]` on an expression is not accepted, and no pass turns `receiver.method(args)` into `Provider::method(receiver, args)`. The body must spell the associated-function call out itself. Calling the inner provider as a method (`self.area()`) would instead route through whatever provider the context has wired for the component, a different dispatch and usually not what a higher-order provider wants. @@ -141,7 +141,7 @@ pub trait CanCalculateAreaOfShape { } ``` -Because the shared capability — `HasScalarType` and any value-level injection — lives on the common context, the individual shape types (`Rectangle`, `Circle`) need not implement it themselves; they only know their own geometry. The context provides the shared abstract type ([abstract types](abstract-types.md)): one app might wire `Scalar` to `f64`, another to a fixed-point type, and every shape provider produces that scalar. The context also provides value-level injection — a `GloballyScaledArea` provider can read a global scale factor through a getter on the context and multiply every shape's area by it, so the scale is configured once per context rather than per shape. Most importantly, provider binding is *lazy and per-context*: a `BaseApp` and a `ScaledApp` can wire the very same `Shape` to different providers — `BaseApp` opening `AreaOfShapeCalculatorComponent` and mapping `@AreaOfShapeCalculatorComponent.Rectangle: RectangleArea` to a plain `RectangleArea`, `ScaledApp` mapping `@AreaOfShapeCalculatorComponent.Rectangle: GloballyScaledArea` instead — so the same generic capability resolves to different behavior in each context without either app's shapes knowing about the other. Each context writes its per-shape choices with the `open` statement detailed in [wiring](wiring.md); the legacy `derive_delegate`/`UseDelegate` nested-table form wires the same dispatch and still appears in existing code. Each layer of such nesting can be verified independently with the `#[check_providers]` form of `check_components!`, which is what makes higher-order wiring debuggable; see [checking](checking.md). +Because the shared capability — `HasScalarType` and any value-level injection — lives on the common context, the individual shape types (`Rectangle`, `Circle`) need not implement it themselves; they only know their own geometry. The context provides the shared abstract type ([abstract types](abstract-types.md)): one app might wire `Scalar` to `f64`, another to a fixed-point type, and every shape provider produces that scalar. The context also provides value-level injection — a `GloballyScaledArea` provider can read a global scale factor from a field on the context through an implicit argument and multiply every shape's area by it, so the scale is configured once per context rather than per shape. Most importantly, provider binding is *lazy and per-context*: a `BaseApp` and a `ScaledApp` can wire the very same `Shape` to different providers — `BaseApp` opening `AreaOfShapeCalculatorComponent` and mapping `@AreaOfShapeCalculatorComponent.Rectangle: RectangleArea` to a plain `RectangleArea`, `ScaledApp` mapping `@AreaOfShapeCalculatorComponent.Rectangle: GloballyScaledArea` instead — so the same generic capability resolves to different behavior in each context without either app's shapes knowing about the other. Each context writes its per-shape choices with the `open` statement detailed in [wiring](wiring.md); the legacy `derive_delegate`/`UseDelegate` nested-table form wires the same dispatch and still appears in existing code. Each layer of such nesting can be verified independently with the `#[check_providers]` form of `check_components!`, which is what makes higher-order wiring debuggable; see [checking](checking.md). ## Related constructs diff --git a/docs/skills/cgp/references/macro-grammar.md b/docs/skills/cgp/references/macro-grammar.md index 81d0c03b..b9967a51 100644 --- a/docs/skills/cgp/references/macro-grammar.md +++ b/docs/skills/cgp/references/macro-grammar.md @@ -114,7 +114,7 @@ The rest of each function macro's contract is read from the item, not the argume These attributes refine what a host macro generates; none is a standalone macro. The table names each one's form, what it contributes, and which host macros accept it. - **`#[implicit]`** — a bare marker on a typed, plain-identifier function argument (`#[implicit] width: f64`); no argument of its own, and `#[implicit(x)]` or `#[implicit = "x"]` is rejected. Removes the argument, adds a `HasField` bound, and binds the value at the top of the body. A *mutable* implicit (its type carries a `&mut`) must be the sole implicit and needs `&mut self`. Host: `#[cgp_fn]`, `#[cgp_impl]`. -- **`#[uses(Trait, Trait

, …)]`** — a comma-separated list of simple trait paths, accumulating across repeats. Adds each as a `Self: Trait` impl-side bound. No associated-type-equality forms (`Iterator`); write those as a body `where` clause. Host: `#[cgp_fn]`, `#[cgp_impl]`. +- **`#[uses(Trait, Trait

, …)]`** — a comma-separated list of trait bounds, accumulating across repeats. Adds each as a `Self:` impl-side bound. The simple `Trait` form reads like an import and is idiomatic, but any `where`-clause bound is accepted, including associated-type equality (`HasErrorType`); prefer `#[use_type]`'s equality form to pin an abstract type. Host: `#[cgp_fn]`, `#[cgp_impl]`. - **`#[extend(Trait, Trait

, …)]`** — same simple-path list. Adds each as a *supertrait* of the generated trait (and, in `#[cgp_fn]`, also as an impl bound). Host: `#[cgp_fn]`, `#[cgp_component]`. Not `#[cgp_impl]` (a provider impl has no trait to attach supertraits to). - **`#[extend_where(Pred, …)]`** — a list of full `where`-clause predicates, including associated-type equality, added verbatim to the generated trait's `where` clause. Host: `#[cgp_fn]` only. - **`#[impl_generics(Param: Bound, …)]`** — bounded generic parameters added to the generated impl **only**, never the trait. Host: `#[cgp_fn]` only. diff --git a/docs/skills/cgp/references/modern-idioms.md b/docs/skills/cgp/references/modern-idioms.md index 6892641a..7e10474a 100644 --- a/docs/skills/cgp/references/modern-idioms.md +++ b/docs/skills/cgp/references/modern-idioms.md @@ -86,11 +86,11 @@ impl AreaCalculator { } ``` -`#[uses]` accepts only the simple `Trait` form, so a bound carrying associated-type equality (`Iterator`) stays an explicit `where` clause. +`#[uses]` accepts any bound a `where` clause allows, including one carrying associated-type equality, though the simple `Trait` form is idiomatic. Where that bound pins an **abstract type** — `Self: HasErrorType` — express it with the [`#[use_type]` equality form](#abstract-types-use_type-over-supertrait--selftype) below, rather than spelling the equality in `#[uses]` or writing a hand-written `where` clause; only equality on a trait you would never import from with `#[use_type]` (`Iterator`) stays an explicit `where` clause. When a provider imports several capabilities or binds several inner providers, combine them into one attribute separated by commas — `#[uses(CanQueryUserBalance, CanRaiseHttpError)]`, `#[use_provider(A: TraitA, B: TraitB)]` — rather than stacking the same attribute repeatedly; one combined attribute reads as a single dependency list. Both attributes also accept being split across repeated attributes, but do that only when a real reason calls for it. ## Field reads: `#[implicit]` over a getter trait -Read a value from a context field with an [`#[implicit]`](functions-and-getters.md) argument — in a `#[cgp_impl]` method exactly as in `#[cgp_fn]` — rather than declaring a getter trait and importing it. An implicit argument names both a local and the field it comes from, keeping the `HasField` machinery out of sight. The getter-trait version: +Read a value from a context field with an [`#[implicit]`](functions-and-getters.md) argument — in a `#[cgp_impl]` method exactly as in `#[cgp_fn]` — rather than declaring a getter trait and importing it. This is the default for *any* field a provider reads from its own context, since an implicit argument reads from `self` and takes a plain `&T` by reference without cloning — even a field several providers each read is written as the same implicit argument in each, not promoted to a getter. An implicit argument names both a local and the field it comes from, keeping the `HasField` machinery out of sight. The getter-trait version: ```rust #[cgp_auto_getter] @@ -117,7 +117,7 @@ impl AreaCalculator { } ``` -Reserve `#[cgp_auto_getter]` for a *published* accessor that other providers depend on through `#[uses(HasName)]`, or one carrying an associated type inferred from the field. Avoid `#[cgp_getter]` in ordinary code — its full wireable component is for the advanced case of choosing the source field per context at wiring time. Choosing between an implicit argument and a getter is about whether the value is a private input or a published capability, not about mechanics. +Use `#[cgp_auto_getter]` sparingly — only where an implicit argument cannot reach. There are three such cases. The field lives on a type *other* than the provider's own context, so the getter is required as a `where` bound on that type (`Request: HasBasicAuthHeader`) and there is no `self` field for an implicit argument to read; the accessor must exist as a *named* capability other code depends on through `#[uses(HasName)]` or a supertrait; or the getter carries an *associated type inferred from the field* so the type stays abstract for callers. Everywhere else — including a same-context field read shared by several providers — prefer the implicit argument. Avoid `#[cgp_getter]` in ordinary code, since its full wireable component is for the advanced case of choosing the source field per context at wiring time. ## Abstract types: `#[use_type]` over supertrait + `Self::Type` @@ -142,6 +142,38 @@ pub trait CanLoad { One rule bounds the rewrite: it fires only on the bare identifier of an *imported* type. A construct's own **local associated type stays qualified as `Self::Assoc`** — a handler that declares `type Output` writes `Self::Output`, never a bare `Output`. So a mixed signature like `Result` is exactly right: the local `Self::Output` stays qualified, the imported `Error` is bare. +When a definition imports types from several traits, combine them into one `#[use_type]` attribute with the trait paths comma-separated — `#[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 the braced list (`#[use_type(HasFooType.{Foo, Bar})]`). Stacking repeated `#[use_type]` attributes behaves identically, but reach for it only when a real reason calls for it. + +`#[use_type]` also *pins* an abstract type with the equality form `{Assoc = Type}`, which is the modern replacement for a hand-written `where Self: HasXType` clause — reach for it whenever a provider constrains an abstract type to a concrete one, rather than leaving the equality as an explicit `where`. On a `#[cgp_impl]`, the legacy + +```rust +#[cgp_impl(new DisplayHttpError)] +impl HttpErrorRaiser +where + Self: HasErrorType, + Code: IsStatusCode, + Detail: Display, +{ + fn raise_http_error(_code: Code, detail: Detail) -> AppError { /* ... */ } +} +``` + +becomes, with the equality moved into the attribute: + +```rust +#[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 { /* ... */ } +} +``` + +The attribute emits the same `Self: HasErrorType` bound (and would rewrite any bare `Error`, though here the body names the concrete `AppError` directly). The right-hand side of `=` may even name *another* imported alias, which unifies two abstract types: `#[use_type(HasPasswordType.Password, HasHashedPasswordType.{HashedPassword = Password})]` emits `Self: HasHashedPasswordType::Password>`. Because the equality form produces an impl-side bound, it belongs on `#[cgp_impl]` and `#[cgp_fn]` only — it is rejected on `#[cgp_component]`. The one equality bound that *stays* a hand-written `where` clause is one on a trait you would never `#[use_type]` from, such as `Iterator`. + ## Supertraits: `#[extend]` over native `:` syntax Add a non-type capability supertrait to a `#[cgp_component]` trait with [`#[extend(...)]`](functions-and-getters.md) rather than native `pub trait CanDoX: Supertrait` syntax. Both produce the same trait, but `#[extend]` reads as importing a capability the trait re-exports, which is what a CGP supertrait actually is — a declared dependency, not a base class. It pairs symmetrically with `#[uses]`: `#[uses]` imports a capability for private use, `#[extend]` re-exports one as part of the trait's contract. The native @@ -198,7 +230,7 @@ Because `open` and namespaces ride `RedirectLookup`, a **new** component you dis ## When the explicit forms are still right -A handful of cases genuinely need an explicit form, and choosing one there is not a regression. Keep an explicit `where` clause for a bound `#[uses]` cannot express — anything with associated-type equality. Name the context explicitly, `impl Trait for Context`, to attach a lifetime or higher-ranked bound the sugar cannot carry, or when `Self` must be a concrete context (the `#[cgp_impl(Self)]` passthrough is the direct-impl case). Reach for `#[cgp_getter]` when you specifically want to choose which field a getter reads per context at wiring time. Write a raw provider-trait `impl` when you need the inside-out shape directly. And keep a local associated type qualified as `Self::Output` always — it is never a `#[use_type]` import. +A handful of cases genuinely need an explicit form, and choosing 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`, and the like — which reads more clearly as a `where` clause than in an import-shaped `#[uses]` (which does accept it); but note the exception's own exception: an equality bound on an **abstract-type** trait (`Self: HasErrorType`) is *not* one of these, because the [`#[use_type]` equality form](#abstract-types-use_type-over-supertrait--selftype) `#[use_type(HasErrorType.{Error = AppError})]` does express it and is preferred; leave only equality on a non-`#[use_type]` trait as a hand-written `where`. Name the context explicitly, `impl Trait for Context`, to attach a lifetime or higher-ranked bound the sugar cannot carry, or when `Self` must be a concrete context (the `#[cgp_impl(Self)]` passthrough is the direct-impl case). Reach for `#[cgp_getter]` when you specifically want to choose which field a getter reads per context at wiring time. Write a raw provider-trait `impl` when you need the inside-out shape directly. And keep a local associated type qualified as `Self::Output` always — it is never a `#[use_type]` import. ## Reading pre-0.7 code: renamed and removed names