diff --git a/CLAUDE.md b/CLAUDE.md index bc780d74..4f6b7137 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -85,3 +85,122 @@ inward (core/macros) when changing fundamentals, outward (main) only to adjust t - See [CHANGELOG.md](CHANGELOG.md) for the evolution of macro syntax — it is the most reliable record of which macro forms are current vs. removed (e.g. `#[cgp_context]` was removed, `ProvideType` → `TypeProvider`). + +## Macro review workflow + +This section defines the standing process for reviewing one CGP macro implementation at a time, +hardening it until no further issue is found. The goal of an iteration is a macro whose +implementation, tests, and documentation are correct, complete, mutually consistent, and as simple +as the behavior allows. + +### Orient before touching anything + +Load the fundamentals first, every iteration. Invoke the `/cgp` skill to reload CGP's mental model +and vocabulary, and the `/dual-reader-prose` skill to reload the writing convention the docs must +follow. Re-invoke `/cgp` whenever the review moves into an unfamiliar construct — the macros and +core traits are the ground truth the skill describes, so read the two together. + +Then read the documentation for the macro under review, in [docs/](docs). Read its reference +document under [docs/reference/](docs/reference), its implementation documents under +[docs/implementation/](docs/implementation) (the `entrypoints/` document, the `asts/` stack it +drives, and any `functions/` helpers it relies on), and the governing `CLAUDE.md` files that define +how those documents stay in sync with the code: [docs/CLAUDE.md](docs/CLAUDE.md), +[docs/implementation/CLAUDE.md](docs/implementation/CLAUDE.md), and +[crates/macros/cgp-macro-core/CLAUDE.md](crates/macros/cgp-macro-core/CLAUDE.md). These establish +that the source is the single source of truth and that reference, implementation, snapshot, and +skill are four views of it that must never drift. + +Next, study the implementation itself in [crates/macros/](crates/macros). Start from the +`cgp-macro-lib` entry function, follow it into the `cgp-macro-core` `types//` AST stack +and the `functions/` helpers it calls, and read closely enough to reason about corner cases, not +just the happy path. Finally, study the tests in [crates/tests/](crates/tests) — the behavioral +tests in `cgp-tests` and the failure cases and expansion snapshots in `cgp-macro-tests` — and read +[crates/tests/CLAUDE.md](crates/tests/CLAUDE.md) to learn how the suite is organized and how to run +and update it. + +### Harden the implementation and its tests + +With the macro understood, work through the review in these areas. Each is a distinct concern; +treat correctness as non-negotiable and simplification as a judgment call, and never let a +readability edit introduce a behavioral change. + +- **Fix bugs and corner cases.** Identify potential bugs and unhandled corner cases in the + implementation and fix them. When a corner case cannot be fixed in this iteration, capture it as a + failure case in `cgp-macro-tests` and record it under the construct's Known issues, per + [crates/tests/CLAUDE.md](crates/tests/CLAUDE.md). +- **Close test gaps.** Add tests for corner cases that are not yet covered, placing each in the + concept target that owns the behavior and snapshotting only in the macro's owning target. +- **Verify existing tests.** Confirm each existing test really exercises the behavior it claims to, + that the corner case it checks makes sense, and that it makes appropriate assertions wherever an + assertion is possible rather than relying on compilation alone. +- **Deduplicate and simplify tests.** Merge or remove tests that check the same or overlapping + behavior, and factor common boilerplate into shared test helpers. +- **Improve the documentation and inline docs.** Update the reference document, the implementation + documents, and any README when they are inconsistent with the code or when something is worth + explaining or clarifying; add a brief `///` to any public struct, trait, or function that lacks + one; and simplify existing inline docs, removing facts that are obvious from reading the code. + +### 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. +- **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. + +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), the span +attached to each generated item so a downstream type error points at the token the user actually +wrote, and the idempotency of the expansion when the same entry is listed more than once. + +### Keep every view in sync and verify + +Every change propagates to all four views in the same change, per the synchronization rule. When +you alter the macro's behavior, syntax, expansion, or defaults, update the reference document's +Expansion, the implementation document's Pipeline and Generated items, the affected snapshots, and +the `/cgp` skill. When you move or rename a test, update the implementation document's Tests or +Snapshots section. Then verify the work: run `cargo +nightly fmt --all`, the clippy invocations, and +`cargo nextest run` for the affected crates (and `cargo insta` to review any snapshot diffs) as +described in the Commands section above, confirming a green suite before considering the iteration +done. + +### Ask when in doubt + +During the review, ask the user for clarification whenever something should be settled before the +next step is taken — an ambiguous intended behavior, a corner case whose correct outcome is unclear, +or a design choice with more than one defensible answer. Surface the question rather than guessing, +since a wrong assumption baked into the source, tests, and four documentation views is expensive to +unwind. diff --git a/Cargo.lock b/Cargo.lock index aacb84e3..2f7beed7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -58,6 +58,13 @@ dependencies = [ name = "cgp-base-types" version = "0.7.0" +[[package]] +name = "cgp-compile-fail-tests" +version = "0.7.0" +dependencies = [ + "cgp", +] + [[package]] name = "cgp-component" version = "0.7.0" diff --git a/Cargo.toml b/Cargo.toml index 4e206d33..0c1e94a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ members = [ "crates/tests/cgp-tests", "crates/tests/cgp-macro-tests", + "crates/tests/cgp-compile-fail-tests", "crates/tests/cgp-test-crate-a", "crates/tests/cgp-test-crate-b", ] diff --git a/crates/core/cgp-error/src/traits/can_raise_error.rs b/crates/core/cgp-error/src/traits/can_raise_error.rs index 4d177329..1b75b20a 100644 --- a/crates/core/cgp-error/src/traits/can_raise_error.rs +++ b/crates/core/cgp-error/src/traits/can_raise_error.rs @@ -10,7 +10,7 @@ use crate::traits::has_error_type::HasErrorType; #[cgp_component(ErrorRaiser)] #[prefix(@cgp.core.error in DefaultNamespace)] #[derive_delegate(UseDelegate)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] pub trait CanRaiseError { fn raise_error(error: SourceError) -> Error; } diff --git a/crates/core/cgp-error/src/traits/can_wrap_error.rs b/crates/core/cgp-error/src/traits/can_wrap_error.rs index e75c5695..21a3e157 100644 --- a/crates/core/cgp-error/src/traits/can_wrap_error.rs +++ b/crates/core/cgp-error/src/traits/can_wrap_error.rs @@ -6,7 +6,7 @@ use crate::traits::HasErrorType; #[cgp_component(ErrorWrapper)] #[prefix(@cgp.core.error in DefaultNamespace)] #[derive_delegate(UseDelegate)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] pub trait CanWrapError { fn wrap_error(error: Error, detail: Detail) -> Error; } diff --git a/crates/extra/cgp-handler/src/components/handler.rs b/crates/extra/cgp-handler/src/components/handler.rs index 45d7d652..7cc341f9 100644 --- a/crates/extra/cgp-handler/src/components/handler.rs +++ b/crates/extra/cgp-handler/src/components/handler.rs @@ -10,7 +10,7 @@ use crate::UseInputDelegate; #[prefix(@cgp.extra.handler in DefaultNamespace)] #[derive_delegate(UseDelegate)] #[derive_delegate(UseInputDelegate)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] pub trait CanHandle { type Output; @@ -22,7 +22,7 @@ pub trait CanHandle { #[prefix(@cgp.extra.handler in DefaultNamespace)] #[derive_delegate(UseDelegate)] #[derive_delegate(UseInputDelegate)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] pub trait CanHandleRef { type Output; diff --git a/crates/extra/cgp-handler/src/components/try_compute.rs b/crates/extra/cgp-handler/src/components/try_compute.rs index ef996759..9cc67e8e 100644 --- a/crates/extra/cgp-handler/src/components/try_compute.rs +++ b/crates/extra/cgp-handler/src/components/try_compute.rs @@ -9,7 +9,7 @@ use crate::UseInputDelegate; #[prefix(@cgp.extra.handler in DefaultNamespace)] #[derive_delegate(UseDelegate)] #[derive_delegate(UseInputDelegate)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] pub trait CanTryCompute { type Output; @@ -20,7 +20,7 @@ pub trait CanTryCompute { #[prefix(@cgp.extra.handler in DefaultNamespace)] #[derive_delegate(UseDelegate)] #[derive_delegate(UseInputDelegate)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] pub trait CanTryComputeRef { type Output; diff --git a/crates/extra/cgp-run/src/lib.rs b/crates/extra/cgp-run/src/lib.rs index ecc6832b..4ba74646 100644 --- a/crates/extra/cgp-run/src/lib.rs +++ b/crates/extra/cgp-run/src/lib.rs @@ -8,7 +8,7 @@ use cgp::prelude::*; #[cgp_component(Runner)] #[async_trait] #[derive_delegate(UseDelegate)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] pub trait CanRun { async fn run(&self, _code: PhantomData) -> Result<(), Error>; } @@ -16,7 +16,7 @@ pub trait CanRun { #[cgp_component(SendRunner)] #[async_trait] #[derive_delegate(UseDelegate)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] pub trait CanSendRun { fn send_run(&self, _code: PhantomData) -> impl Future> + Send; } diff --git a/crates/extra/cgp-runtime/src/traits/has_runtime.rs b/crates/extra/cgp-runtime/src/traits/has_runtime.rs index fa59dc17..d2081408 100644 --- a/crates/extra/cgp-runtime/src/traits/has_runtime.rs +++ b/crates/extra/cgp-runtime/src/traits/has_runtime.rs @@ -3,7 +3,7 @@ use cgp::prelude::*; use crate::HasRuntimeType; #[cgp_getter] -#[use_type(HasRuntimeType::Runtime)] +#[use_type(HasRuntimeType.Runtime)] pub trait HasRuntime { fn runtime(&self) -> &Runtime; } diff --git a/crates/macros/cgp-macro-core/src/functions/delegated_impls/item_type.rs b/crates/macros/cgp-macro-core/src/functions/delegated_impls/item_type.rs index 616aded2..4f97d580 100644 --- a/crates/macros/cgp-macro-core/src/functions/delegated_impls/item_type.rs +++ b/crates/macros/cgp-macro-core/src/functions/delegated_impls/item_type.rs @@ -1,6 +1,8 @@ use syn::token::Eq; use syn::{ImplItemType, TraitItemType, Type, Visibility}; +/// Build an associated-type impl item that keeps `trait_type`'s name, generics, +/// and attributes but binds it to `delegated_type`. pub fn trait_to_impl_item_type(trait_type: &TraitItemType, delegated_type: Type) -> ImplItemType { ImplItemType { attrs: trait_type.attrs.clone(), diff --git a/crates/macros/cgp-macro-core/src/functions/delegated_impls/signature.rs b/crates/macros/cgp-macro-core/src/functions/delegated_impls/signature.rs index 4ca0652a..a724dda9 100644 --- a/crates/macros/cgp-macro-core/src/functions/delegated_impls/signature.rs +++ b/crates/macros/cgp-macro-core/src/functions/delegated_impls/signature.rs @@ -6,6 +6,8 @@ use syn::{FnArg, Ident, ImplItemFn, Signature, Type, Visibility}; use crate::functions::parse_internal; +/// Build an impl method that keeps `signature` and forwards its arguments to the +/// same method on `delegate_type`, adding `.await` when the signature is `async`. pub fn signature_to_delegated_impl_item_fn( signature: &Signature, delegate_type: &Type, diff --git a/crates/macros/cgp-macro-core/src/functions/delegated_impls/trait_items.rs b/crates/macros/cgp-macro-core/src/functions/delegated_impls/trait_items.rs index d1ebbeb0..7b024817 100644 --- a/crates/macros/cgp-macro-core/src/functions/delegated_impls/trait_items.rs +++ b/crates/macros/cgp-macro-core/src/functions/delegated_impls/trait_items.rs @@ -22,6 +22,9 @@ pub fn trait_items_to_delegated_impl_items( .collect() } +/// Forward a single trait item (method, associated type, or const) to +/// `delegate_type`, projecting associated types and consts through +/// `provider_trait_path`. Other trait-item kinds are rejected. pub fn trait_item_to_delegated_impl_items( trait_item: &TraitItem, delegate_type: &Type, diff --git a/crates/macros/cgp-macro-core/src/functions/implicits/parse.rs b/crates/macros/cgp-macro-core/src/functions/implicits/parse.rs index d62b3e25..6ffaa3fc 100644 --- a/crates/macros/cgp-macro-core/src/functions/implicits/parse.rs +++ b/crates/macros/cgp-macro-core/src/functions/implicits/parse.rs @@ -3,7 +3,7 @@ use std::mem; use syn::punctuated::Punctuated; use syn::token::Comma; use syn::visit::{self, Visit}; -use syn::{Attribute, FnArg, Meta, Pat, PatIdent, PatType, Receiver}; +use syn::{Attribute, FnArg, Meta, Pat, PatIdent, PatType, Receiver, Type}; use crate::functions::parse_field_type; use crate::types::implicits::{ImplicitArgField, ImplicitArgFields}; @@ -24,13 +24,6 @@ pub fn extract_and_parse_implicit_args( )); }; - if receiver.mutability.is_some() && implicit_fn_args.len() > 1 { - return Err(syn::Error::new_spanned( - &args, - "Only one mutable implicit argument is allowed when self is mutable", - )); - } - let mut implicit_args = Vec::new(); for arg in implicit_fn_args { @@ -38,6 +31,21 @@ pub fn extract_and_parse_implicit_args( implicit_args.push(spec); } + // A `&mut` implicit reads through `get_field_mut`, which borrows the whole + // context exclusively for the rest of the body, so it cannot coexist with any + // other implicit read — the emitted impl would borrow the context mutably and + // (im)mutably at once and fail to compile. Purely immutable implicits are all + // shared borrows and combine freely, so the constraint applies only once a + // mutable one is present. + let has_mutable = implicit_args.iter().any(|field| field.field_mut.is_some()); + + if has_mutable && implicit_args.len() > 1 { + return Err(syn::Error::new_spanned( + &args, + "a `&mut` implicit argument must be the only implicit argument, since its mutable borrow of the context conflicts with reading any other field", + )); + } + Ok(ImplicitArgFields::new(implicit_args)) } @@ -55,9 +63,16 @@ pub fn parse_implicit_arg(receiver: &Receiver, arg: &PatType) -> syn::Result type_ref.mutability, + _ => None, + }; let spec = ImplicitArgField { field_name: pat_ident.ident.clone(), diff --git a/crates/macros/cgp-macro-core/src/functions/is_provider_params.rs b/crates/macros/cgp-macro-core/src/functions/is_provider_params.rs index 79e93c45..2e6e5290 100644 --- a/crates/macros/cgp-macro-core/src/functions/is_provider_params.rs +++ b/crates/macros/cgp-macro-core/src/functions/is_provider_params.rs @@ -1,33 +1,36 @@ use syn::punctuated::Punctuated; use syn::token::Comma; -use syn::{GenericParam, Generics, Type}; +use syn::{Error, GenericParam, Generics, Type}; use crate::exports::Life; use crate::parse_internal; -use crate::types::generics::TypeGenerics; /// Convert a trait's generics into the `Params` tuple types of an `IsProviderFor` -/// bound: type params pass through, lifetimes are lifted into `Life<'a>`. +/// bound: type parameters pass through by name and lifetimes are lifted into +/// `Life<'a>`. Bounds and defaults are dropped, since the tuple only names the +/// parameters positionally. /// -/// Panics on a const generic parameter — see the const-generic limitation in -/// docs/implementation/entrypoints/cgp_component.md. +/// Const generic parameters are rejected with a spanned error: the tuple holds +/// *types*, and CGP's type-based wiring cannot key on a const value, so a const +/// parameter has no representation here. pub fn parse_is_provider_params(generics: &Generics) -> syn::Result> { - let params = TypeGenerics::try_from(generics)?.generics.params; - let mut res = Punctuated::new(); - for param in params { + for param in &generics.params { let out = match param { GenericParam::Type(type_param) => { - let ident = type_param.ident; + let ident = &type_param.ident; parse_internal! { #ident } } GenericParam::Lifetime(life_param) => { let life = &life_param.lifetime; parse_internal! { #Life<#life> } } - GenericParam::Const(_) => { - unimplemented!("const generic parameters are not yet supported in CGP traits") + GenericParam::Const(const_param) => { + return Err(Error::new_spanned( + const_param, + "const generic parameters are not supported on CGP component traits", + )); } }; res.push(out) diff --git a/crates/macros/cgp-macro-core/src/types/attributes/use_type/attribute.rs b/crates/macros/cgp-macro-core/src/types/attributes/use_type/attribute.rs index 187d691e..63a041cb 100644 --- a/crates/macros/cgp-macro-core/src/types/attributes/use_type/attribute.rs +++ b/crates/macros/cgp-macro-core/src/types/attributes/use_type/attribute.rs @@ -1,11 +1,13 @@ use syn::parse::{Parse, ParseStream}; -use syn::token::{At, Brace, Colon, Comma, Gt, Lt}; -use syn::{Ident, Type, braced}; +use syn::token::{At, Brace, Comma, Dot}; +use syn::{Ident, Type}; use crate::parse_internal; use crate::types::attributes::UseTypeIdent; -use crate::types::ident::{IdentWithTypeArgs, PathWithTypeArgs}; +use crate::types::ident::PathWithTypeArgs; +/// One `#[use_type(...)]` import spec: a rewrite target (`Self` or an `@Context`), +/// the owning trait path, and one or more associated types to import from it. #[derive(Clone)] pub struct UseTypeAttribute { pub context_type: Type, @@ -29,59 +31,32 @@ impl UseTypeAttribute { impl Parse for UseTypeAttribute { fn parse(input: ParseStream) -> syn::Result { - let body; - - let (context_type, body) = if input.peek(At) { + // A `.` (not `::`) separates the context, trait, and associated type. This + // keeps the trait unambiguous even when it is a full path such as + // `foo::bar::HasScalarType`: `::` stays inside the path, and the trailing + // `.` marks where the associated type begins. + let context_type: Type = if input.peek(At) { let _: At = input.parse()?; - - // The context type is followed by a `::`-separated trait path, so it - // must parse only a single identifier head. This is the one place - // that deliberately keeps `IdentWithTypeArgs` rather than the - // otherwise-dominant `PathWithTypeArgs`: a path parser is greedy - // across `::` and would silently consume the trailing `::Trait::Type` - // here, with no parse error. Do NOT swap this for `PathWithTypeArgs`. - let context_type: Type = input.parse::()?.into(); - - let _: Colon = input.parse()?; - let _: Colon = input.parse()?; - - if input.peek(Brace) { - braced!(body in input); - (context_type, &body) - } else { - (context_type, input) - } + let context: PathWithTypeArgs = input.parse()?; + let _: Dot = input.parse()?; + context.into() } else { - (parse_internal! { Self }, input) + parse_internal! { Self } }; - let trait_path = if body.peek(Lt) { - let _: Lt = body.parse()?; - let trait_path: PathWithTypeArgs = body.parse()?; - let _: Gt = body.parse()?; - trait_path - } else { - let name: Ident = body.parse()?; - name.into() - }; + let trait_path: PathWithTypeArgs = input.parse()?; - let _: Colon = body.parse()?; - let _: Colon = body.parse()?; + let _: Dot = input.parse()?; - let type_idents: Vec = if body.peek(Brace) { + let type_idents: Vec = if input.peek(Brace) { let content; - braced!(content in body); + syn::braced!(content in input); content .parse_terminated(UseTypeIdent::parse, Comma)? .into_iter() .collect() } else { - let ident: Ident = body.parse()?; - vec![UseTypeIdent { - type_ident: ident, - as_alias: None, - equals: None, - }] + vec![input.parse()?] }; Ok(Self { diff --git a/crates/macros/cgp-macro-core/src/types/attributes/use_type/attributes.rs b/crates/macros/cgp-macro-core/src/types/attributes/use_type/attributes.rs index afc6f230..93cfaa3a 100644 --- a/crates/macros/cgp-macro-core/src/types/attributes/use_type/attributes.rs +++ b/crates/macros/cgp-macro-core/src/types/attributes/use_type/attributes.rs @@ -4,7 +4,9 @@ use syn::{ItemImpl, ItemTrait}; use crate::functions::parse_internal; use crate::types::attributes::UseTypeAttribute; -use crate::types::attributes::use_type::type_predicates::derive_use_type_predicates; +use crate::types::attributes::use_type::type_predicates::{ + derive_use_type_predicates, forbid_duplicate_aliases, +}; use crate::visitors::SubstituteAbstractType; #[derive(Default, Clone)] @@ -30,6 +32,8 @@ impl UseTypeAttributes { return Ok(()); } + forbid_duplicate_aliases(&self.attributes)?; + self.substitute_abstract_types_in_item_trait(item_trait); for use_type in self.attributes.iter() { @@ -50,6 +54,8 @@ impl UseTypeAttributes { return Ok(()); } + forbid_duplicate_aliases(&self.attributes)?; + self.substitute_abstract_types_in_item_impl(item_impl); let predicates = derive_use_type_predicates(&self.attributes)?; diff --git a/crates/macros/cgp-macro-core/src/types/attributes/use_type/type_predicates.rs b/crates/macros/cgp-macro-core/src/types/attributes/use_type/type_predicates.rs index def943b0..c83538f1 100644 --- a/crates/macros/cgp-macro-core/src/types/attributes/use_type/type_predicates.rs +++ b/crates/macros/cgp-macro-core/src/types/attributes/use_type/type_predicates.rs @@ -68,6 +68,30 @@ fn find_type_alias(specs: &[UseTypeAttribute], context_type: &Type) -> syn::Resu Ok(None) } +/// Reject two imports that resolve to the same bare identifier or alias, across +/// every spec *and within a single braced list*. A shared alias would make the +/// substitution silently pick the first match and drop the rest, so it is an +/// error regardless of which host macro drives the import. +pub fn forbid_duplicate_aliases(specs: &[UseTypeAttribute]) -> syn::Result<()> { + let idents: Vec<&UseTypeIdent> = specs + .iter() + .flat_map(|spec| spec.type_idents.iter()) + .collect(); + + for (index, current) in idents.iter().enumerate() { + for other in idents.iter().skip(index + 1) { + if current.alias_ident() == other.alias_ident() { + return Err(syn::Error::new_spanned( + &other.type_ident, + "Multiple abstract types cannot share the same identifier or alias", + )); + } + } + } + + Ok(()) +} + fn find_type_equalities( current_spec: &UseTypeAttribute, specs: &[UseTypeAttribute], @@ -75,8 +99,6 @@ fn find_type_equalities( let mut equalities = Vec::new(); for current_type_ident in current_spec.type_idents.iter() { - forbid_same_alias(current_type_ident, current_spec, specs)?; - if let Some(equality) = find_type_equality(current_type_ident, current_spec, specs)? { equalities.push(equality); } @@ -85,30 +107,6 @@ fn find_type_equalities( Ok(equalities) } -fn forbid_same_alias( - current_ident: &UseTypeIdent, - current_spec: &UseTypeAttribute, - specs: &[UseTypeAttribute], -) -> syn::Result<()> { - for spec in specs.iter() { - if core::ptr::eq(spec, current_spec) { - // Skip the current spec - continue; - } - - for type_ident in spec.type_idents.iter() { - if current_ident.alias_ident() == type_ident.alias_ident() { - return Err(syn::Error::new_spanned( - ¤t_ident.type_ident, - "Multiple abstract types cannot share the same identifier or alias", - )); - } - } - } - - Ok(()) -} - fn find_type_equality( current_ident: &UseTypeIdent, current_spec: &UseTypeAttribute, 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 6b77e8b8..d98e58d9 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 @@ -29,7 +29,7 @@ impl TryFrom for CgpComponentArgs { fn try_from(raw_args: CgpComponentRawArgs) -> Result { let provider_ident = raw_args .provider_ident - .ok_or_else(|| Error::new(Span::call_site(), "`provider_ident` key must be given"))?; + .ok_or_else(|| Error::new(Span::call_site(), "the `provider` key must be given"))?; let context_ident = raw_args .context_ident diff --git a/crates/macros/cgp-macro-core/src/types/cgp_component/evaluated/item.rs b/crates/macros/cgp-macro-core/src/types/cgp_component/evaluated/item.rs index 3f0b7692..bc20d02d 100644 --- a/crates/macros/cgp-macro-core/src/types/cgp_component/evaluated/item.rs +++ b/crates/macros/cgp-macro-core/src/types/cgp_component/evaluated/item.rs @@ -37,6 +37,8 @@ impl EvaluatedCgpComponent { Ok(items) } + /// The standard provider impls (`UseContext`, `RedirectLookup`, per-attribute + /// `UseDelegate`) followed by one namespace prefix impl per `#[prefix]`. pub fn to_item_impls(&self) -> syn::Result> { let mut item_impls = self.to_provider_impls()?.to_item_impls()?; @@ -45,6 +47,8 @@ impl EvaluatedCgpComponent { Ok(item_impls) } + /// The `UseContext` and `RedirectLookup` impls always emitted for a component, + /// plus one `UseDelegate` impl per `#[derive_delegate]` attribute. pub fn to_provider_impls(&self) -> syn::Result { let mut provider_impls = ItemProviderImpls::default(); diff --git a/crates/macros/cgp-macro-core/src/types/cgp_impl/args.rs b/crates/macros/cgp-macro-core/src/types/cgp_impl/args.rs index 89427b21..14a848a7 100644 --- a/crates/macros/cgp-macro-core/src/types/cgp_impl/args.rs +++ b/crates/macros/cgp-macro-core/src/types/cgp_impl/args.rs @@ -6,6 +6,8 @@ use crate::traits::ParseOptionalKeyword; use crate::types::keyword::Keyword; use crate::types::keywords::New; +/// The parsed `#[cgp_impl(...)]` attribute argument: an optional `new` keyword, +/// the provider type, and an optional `: ComponentType` override. #[derive(Clone)] pub struct ImplArgs { pub new: Option>, diff --git a/crates/macros/cgp-macro-core/src/types/cgp_impl/item.rs b/crates/macros/cgp-macro-core/src/types/cgp_impl/item.rs index 747350b5..0ec9b61b 100644 --- a/crates/macros/cgp-macro-core/src/types/cgp_impl/item.rs +++ b/crates/macros/cgp-macro-core/src/types/cgp_impl/item.rs @@ -7,12 +7,17 @@ use crate::types::attributes::CgpImplAttributes; use crate::types::cgp_impl::{ImplArgs, LoweredCgpImpl}; use crate::types::implicits::ImplicitArgFields; +/// The raw input stage of `#[cgp_impl]`: the parsed attribute args and the +/// `impl` block, before any lowering. pub struct ItemCgpImpl { pub args: ImplArgs, pub item_impl: ItemImpl, } impl ItemCgpImpl { + /// Processes the companion attributes and normalizes the impl header, + /// resolving the provider trait path and context type (inserting `__Context__` + /// when the `for` clause is omitted), and produces a [`LoweredCgpImpl`]. pub fn lower(&self) -> syn::Result { let mut item_impl = self.item_impl.clone(); diff --git a/crates/macros/cgp-macro-core/src/types/cgp_impl/lowered.rs b/crates/macros/cgp-macro-core/src/types/cgp_impl/lowered.rs index 906f5ef8..2f3bbdc7 100644 --- a/crates/macros/cgp-macro-core/src/types/cgp_impl/lowered.rs +++ b/crates/macros/cgp-macro-core/src/types/cgp_impl/lowered.rs @@ -13,6 +13,8 @@ use crate::visitors::{ ReplaceSelfReceiverVisitor, ReplaceSelfTypeVisitor, ReplaceSelfValueVisitor, }; +/// The header-normalized stage of `#[cgp_impl]`, carrying the resolved context +/// type and provider trait path. It owns the consumer-to-provider rewrite. pub struct LoweredCgpImpl { pub args: ImplArgs, pub item_impl: ItemImpl, @@ -22,6 +24,9 @@ pub struct LoweredCgpImpl { } impl LoweredCgpImpl { + /// Rewrites the block into provider-trait form and hands it to + /// [`ItemCgpProvider`], or — for the `#[cgp_impl(Self)]` case — returns the + /// block unchanged as a bare consumer impl (requiring a `for` clause). pub fn lower(&self) -> syn::Result { if self.args.provider_type == parse_internal!(Self) { if self.item_impl.trait_.is_none() { @@ -51,6 +56,9 @@ impl LoweredCgpImpl { } } + /// Performs the consumer-to-provider rewrite: swaps `Self` to the provider, + /// inserts the context as the provider trait's leading argument, and runs the + /// three `replace_self` visitors to rewrite `self`/`Self` in the body. pub fn to_raw_item_impl(&self) -> syn::Result { let item_impl = &self.item_impl; let context_type = &self.context_type; diff --git a/crates/macros/cgp-macro-core/src/types/cgp_impl/provider_or_bare.rs b/crates/macros/cgp-macro-core/src/types/cgp_impl/provider_or_bare.rs index 34b38b80..92aef56d 100644 --- a/crates/macros/cgp-macro-core/src/types/cgp_impl/provider_or_bare.rs +++ b/crates/macros/cgp-macro-core/src/types/cgp_impl/provider_or_bare.rs @@ -4,6 +4,9 @@ use syn::ItemImpl; use crate::types::cgp_provider::LoweredCgpProvider; +/// The output of `#[cgp_impl]`'s second lowering: either a rewritten provider +/// (`Provider`) or, for the `#[cgp_impl(Self)]` passthrough, the untouched +/// consumer impl (`Bare`). pub enum CgpProviderOrBareImpl { Bare(Box), Provider(Box), diff --git a/crates/macros/cgp-macro-core/src/types/check_components/table.rs b/crates/macros/cgp-macro-core/src/types/check_components/table.rs index 93fdbf1f..184972ad 100644 --- a/crates/macros/cgp-macro-core/src/types/check_components/table.rs +++ b/crates/macros/cgp-macro-core/src/types/check_components/table.rs @@ -11,7 +11,7 @@ use crate::functions::merge_generics; use crate::parse_internal; use crate::types::check_components::{CheckEntries, EvaluatedCheckEntry, TypeWithGenerics}; use crate::types::generics::ImplGenerics; -use crate::types::ident::IdentWithTypeArgs; +use crate::types::ident::PathWithTypeArgs; pub struct CheckComponentsTable { pub check_providers: Option>, @@ -108,9 +108,21 @@ impl Parse for CheckComponentsTable { let provider_types: Punctuated = attribute.parse_args_with(Punctuated::parse_terminated)?; - check_providers - .get_or_insert_default() - .extend(provider_types); + if provider_types.is_empty() { + return Err(syn::Error::new( + attribute.span(), + "`#[check_providers(...)]` requires at least one provider type.", + )); + } + + if check_providers.is_some() { + return Err(syn::Error::new( + attribute.span(), + "Multiple `#[check_providers]` attributes found. Expected at most one.", + )); + } + + check_providers = Some(provider_types); } else if attribute.path().is_ident("check_trait") { let check_trait_name: Ident = attribute.parse_args()?; @@ -168,15 +180,18 @@ impl Parse for CheckComponentsTable { } /// Derive a check trait identifier from a context type by prepending `prefix` -/// to the context type's leading identifier, e.g. `__CheckPerson` or -/// `__CanUsePerson` for the context type `Person`. +/// to the identifier of the context type's final path segment, e.g. `__CheckPerson` +/// or `__CanUsePerson` for `Person`, and `__CheckPerson` for `some_mod::Person`. +/// +/// The context type is parsed through [`PathWithTypeArgs`] rather than a bare +/// identifier so that a path-qualified context (`some_mod::Person`) — which +/// [`delegate_components!`](crate::types::delegate_component) accepts and uses +/// verbatim — is accepted here too, instead of failing at parse time. pub fn derive_check_trait_ident(context_type: &Type, prefix: &str) -> syn::Result { - let context_type: IdentWithTypeArgs = parse2(context_type.to_token_stream())?; + let context_path: PathWithTypeArgs = parse2(context_type.to_token_stream())?; + let ident = context_path.ident(); - Ok(Ident::new( - &format!("{prefix}{}", context_type.ident), - context_type.span(), - )) + Ok(Ident::new(&format!("{prefix}{ident}"), ident.span())) } fn override_span(span: &Span, body: &T) -> syn::Result diff --git a/crates/macros/cgp-macro-core/src/types/delegate_component/entries.rs b/crates/macros/cgp-macro-core/src/types/delegate_component/entries.rs index 919ad512..f21e41ff 100644 --- a/crates/macros/cgp-macro-core/src/types/delegate_component/entries.rs +++ b/crates/macros/cgp-macro-core/src/types/delegate_component/entries.rs @@ -8,6 +8,9 @@ use crate::types::delegate_component::{ InnerDelegateTable, }; +/// A table body: leading statements (`open`/`namespace`/`for`) followed by the +/// comma-separated mappings. Statements must lead, which is why one written +/// after a mapping fails to parse. #[derive(Debug, Clone)] pub struct DelegateEntries { pub statements: Vec, @@ -52,6 +55,8 @@ impl EvalDelegateEntries for DelegateEntries { } impl DelegateEntries { + /// Lower every statement and mapping to evaluated entries and render each + /// into its `DelegateComponent`/`IsProviderFor` impl pair. pub fn build_impls( &self, table_type: &Type, diff --git a/crates/macros/cgp-macro-core/src/types/delegate_component/key/combined.rs b/crates/macros/cgp-macro-core/src/types/delegate_component/key/combined.rs index 05b9e0db..9df468f0 100644 --- a/crates/macros/cgp-macro-core/src/types/delegate_component/key/combined.rs +++ b/crates/macros/cgp-macro-core/src/types/delegate_component/key/combined.rs @@ -7,6 +7,8 @@ use crate::types::delegate_component::{ }; use crate::types::generics::ImplGenerics; +/// The left side of a mapping, dispatched on a fork: a leading `@` is a `Path` +/// key, a leading `[` a `Multi` (array) key, otherwise a `Single` key. #[derive(Debug, Clone)] pub enum DelegateKey { Single(SingleDelegateKey), diff --git a/crates/macros/cgp-macro-core/src/types/delegate_component/key/eval.rs b/crates/macros/cgp-macro-core/src/types/delegate_component/key/eval.rs index 99159104..f5755565 100644 --- a/crates/macros/cgp-macro-core/src/types/delegate_component/key/eval.rs +++ b/crates/macros/cgp-macro-core/src/types/delegate_component/key/eval.rs @@ -1,10 +1,13 @@ use syn::{Generics, Type}; +/// A lowered key: the key type plus the generics it introduces. One source key +/// can yield several of these (array keys, brace groups in a path). pub struct EvaluatedDelegateKey { pub generics: Generics, pub key: Type, } +/// Lower a key form into its evaluated keys. pub trait EvalDelegateKey { fn eval(&self) -> syn::Result>; } diff --git a/crates/macros/cgp-macro-core/src/types/delegate_component/key/multi.rs b/crates/macros/cgp-macro-core/src/types/delegate_component/key/multi.rs index c6e86b3a..251ffb5c 100644 --- a/crates/macros/cgp-macro-core/src/types/delegate_component/key/multi.rs +++ b/crates/macros/cgp-macro-core/src/types/delegate_component/key/multi.rs @@ -5,6 +5,8 @@ use syn::{Attribute, bracketed}; use crate::types::delegate_component::{EvalDelegateKey, EvaluatedDelegateKey, SingleDelegateKey}; +/// The array-key form `[A, B]`, evaluating to one key per bracketed element so +/// several components share a single value. #[derive(Debug, Clone)] pub struct MultiDelegateKey { pub attributes: Vec, diff --git a/crates/macros/cgp-macro-core/src/types/delegate_component/key/path.rs b/crates/macros/cgp-macro-core/src/types/delegate_component/key/path.rs index 17702b92..3fedb132 100644 --- a/crates/macros/cgp-macro-core/src/types/delegate_component/key/path.rs +++ b/crates/macros/cgp-macro-core/src/types/delegate_component/key/path.rs @@ -8,6 +8,9 @@ use crate::types::delegate_component::{EvalDelegateKey, EvaluatedDelegateKey}; use crate::types::generics::ImplGenerics; use crate::types::path::PathHead; +/// The `@`-prefixed open key. It lowers each path to a prefix type terminated by +/// a `__Wildcard__` generic, into which the dispatch parameter slots at lookup +/// time; a brace group in the path expands to one key per element. #[derive(Debug, Clone)] pub struct PathDelegateKey { pub attributes: Vec, diff --git a/crates/macros/cgp-macro-core/src/types/delegate_component/key/single.rs b/crates/macros/cgp-macro-core/src/types/delegate_component/key/single.rs index 7655a038..75c5e3ac 100644 --- a/crates/macros/cgp-macro-core/src/types/delegate_component/key/single.rs +++ b/crates/macros/cgp-macro-core/src/types/delegate_component/key/single.rs @@ -4,6 +4,8 @@ use syn::{Attribute, Type}; use crate::types::delegate_component::{EvalDelegateKey, EvaluatedDelegateKey}; use crate::types::generics::ImplGenerics; +/// A single component key: optional generics and a type, evaluating to one +/// keyed impl carrying those generics. #[derive(Debug, Clone)] pub struct SingleDelegateKey { pub attributes: Vec, diff --git a/crates/macros/cgp-macro-core/src/types/delegate_component/mapping/combined.rs b/crates/macros/cgp-macro-core/src/types/delegate_component/mapping/combined.rs index 7a1a8354..9f0db22a 100644 --- a/crates/macros/cgp-macro-core/src/types/delegate_component/mapping/combined.rs +++ b/crates/macros/cgp-macro-core/src/types/delegate_component/mapping/combined.rs @@ -6,6 +6,8 @@ use crate::types::delegate_component::{ ExtractInnerDelegateTables, InnerDelegateTable, NormalDelegateMapping, RedirectDelegateMapping, }; +/// One `Key OP Value` entry, its variant chosen by the operator: `:` is Normal, +/// `->` is Direct, `=>` is Redirect. #[derive(Debug, Clone)] pub enum DelegateMapping { Normal(NormalDelegateMapping), diff --git a/crates/macros/cgp-macro-core/src/types/delegate_component/mapping/direct.rs b/crates/macros/cgp-macro-core/src/types/delegate_component/mapping/direct.rs index e98a7c0b..48429817 100644 --- a/crates/macros/cgp-macro-core/src/types/delegate_component/mapping/direct.rs +++ b/crates/macros/cgp-macro-core/src/types/delegate_component/mapping/direct.rs @@ -8,6 +8,9 @@ use crate::types::delegate_component::{ EvaluatedDelegateEntry, ExtractInnerDelegateTables, InnerDelegateTable, }; +/// A `Key -> Value` mapping that forwards to the value's own entry for the key: +/// `Delegate` becomes `>::Delegate`, with a +/// matching `Value: DelegateComponent` bound. #[derive(Debug, Clone)] pub struct DirectDelegateMapping { pub key: DelegateKey, 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 fa77d167..96fa9677 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 @@ -3,6 +3,8 @@ use syn::{Generics, ItemImpl, Type}; use crate::exports::{DelegateComponent, IsProviderFor}; use crate::functions::{merge_generics, parse_internal}; +/// The flat form every key, value, and statement collapses to; it renders the +/// impl pair for one wiring entry. pub struct EvaluatedDelegateEntry { pub table_type: Type, pub generics: Generics, @@ -10,15 +12,19 @@ pub struct EvaluatedDelegateEntry { pub value: Type, } +/// Lower a construct into a single evaluated entry. pub trait EvalDelegateEntry { fn eval_entry(&self, table_type: &Type) -> syn::Result; } +/// Lower a construct into a flat list of evaluated entries (a key or statement +/// may expand to several). pub trait EvalDelegateEntries { fn eval_entries(&self, table_type: &Type) -> syn::Result>; } impl EvaluatedDelegateEntry { + /// Emit `impl DelegateComponent for TableType { type Delegate = Value; }`. pub fn build_delegate_component_impl( &self, outer_generics: &Generics, @@ -45,6 +51,9 @@ impl EvaluatedDelegateEntry { Ok(item_impl) } + /// Emit the forwarding `IsProviderFor` impl, + /// bounded on the value being a provider for the same key so a missing + /// transitive dependency stays diagnosable. pub fn build_is_provider_for_impl(&self, outer_generics: &Generics) -> syn::Result { let table_type = &self.table_type; @@ -76,6 +85,8 @@ impl EvaluatedDelegateEntry { Ok(item_impl) } + /// Emit `impl namespace_trait for Key { type Delegate = Value; }`, used by + /// the namespace preset machinery instead of a direct `DelegateComponent` impl. pub fn build_namespace_impl( &self, namespace_trait: &Type, diff --git a/crates/macros/cgp-macro-core/src/types/delegate_component/mapping/mode.rs b/crates/macros/cgp-macro-core/src/types/delegate_component/mapping/mode.rs index eb8e9b8e..1c925d40 100644 --- a/crates/macros/cgp-macro-core/src/types/delegate_component/mapping/mode.rs +++ b/crates/macros/cgp-macro-core/src/types/delegate_component/mapping/mode.rs @@ -1,6 +1,8 @@ use syn::parse::{Parse, ParseStream}; use syn::token::{Colon, FatArrow, RArrow}; +/// The operator between a key and its value, peeked to select the mapping +/// variant: `:` Normal, `->` Direct, `=>` Redirect. pub enum DelegateMode { Normal(Colon), Direct(RArrow), diff --git a/crates/macros/cgp-macro-core/src/types/delegate_component/mapping/normal.rs b/crates/macros/cgp-macro-core/src/types/delegate_component/mapping/normal.rs index 7c036cef..d1cbdf04 100644 --- a/crates/macros/cgp-macro-core/src/types/delegate_component/mapping/normal.rs +++ b/crates/macros/cgp-macro-core/src/types/delegate_component/mapping/normal.rs @@ -7,6 +7,8 @@ use crate::types::delegate_component::{ EvaluatedDelegateEntry, ExtractInnerDelegateTables, InnerDelegateTable, }; +/// A `Key: Value` mapping — the common form, whose evaluated `Delegate` is the +/// value type directly. #[derive(Debug, Clone)] pub struct NormalDelegateMapping { pub key: DelegateKey, diff --git a/crates/macros/cgp-macro-core/src/types/delegate_component/mapping/redirect.rs b/crates/macros/cgp-macro-core/src/types/delegate_component/mapping/redirect.rs index 44015fe7..3586b367 100644 --- a/crates/macros/cgp-macro-core/src/types/delegate_component/mapping/redirect.rs +++ b/crates/macros/cgp-macro-core/src/types/delegate_component/mapping/redirect.rs @@ -8,6 +8,8 @@ use crate::types::delegate_component::{ }; use crate::types::path::UniPath; +/// A `Key => @path` mapping that redirects the lookup along an `@`-path: +/// `Delegate` becomes `RedirectLookup`. #[derive(Debug, Clone)] pub struct RedirectDelegateMapping { pub key: DelegateKey, diff --git a/crates/macros/cgp-macro-core/src/types/delegate_component/statement/combined.rs b/crates/macros/cgp-macro-core/src/types/delegate_component/statement/combined.rs index 53c40386..4d4979ae 100644 --- a/crates/macros/cgp-macro-core/src/types/delegate_component/statement/combined.rs +++ b/crates/macros/cgp-macro-core/src/types/delegate_component/statement/combined.rs @@ -9,6 +9,8 @@ use crate::types::delegate_component::{ }; use crate::types::keywords::{Namespace, Open}; +/// A leading table statement — `namespace`, `open`, or `for` — all of which +/// lower into the same flat entry list as ordinary mappings. #[derive(Debug, Clone)] pub enum DelegateStatement { Namespace(NamespaceDelegateStatement), @@ -17,6 +19,8 @@ pub enum DelegateStatement { } impl DelegateStatement { + /// Peek whether the input begins with a statement keyword, so all leading + /// statements are consumed before the mappings. pub fn peek_statement(input: ParseStream) -> bool { input.peek_keyword::() || input.peek_keyword::() || input.peek(For) } diff --git a/crates/macros/cgp-macro-core/src/types/delegate_component/statement/eval.rs b/crates/macros/cgp-macro-core/src/types/delegate_component/statement/eval.rs index 8a388575..3c55aa56 100644 --- a/crates/macros/cgp-macro-core/src/types/delegate_component/statement/eval.rs +++ b/crates/macros/cgp-macro-core/src/types/delegate_component/statement/eval.rs @@ -4,14 +4,19 @@ use crate::parse_internal; use crate::types::delegate_component::{EvalDelegateEntry, EvaluatedDelegateEntry}; use crate::types::ident::PathWithTypeArgs; +/// Lower a `namespace`/`for` statement into intermediate for-entries. pub trait EvalForEntries { fn eval_for_entries(&self, table_type: &Type) -> syn::Result>; } +/// Lower a construct into a single for-entry. pub trait EvalForEntry { fn eval_for_entry(&self, table_type: &Type) -> syn::Result; } +/// The shared intermediate for the `namespace` and `for` forms, from which the +/// `__Key__: Namespace<.., Delegate = __Value__>` bound and the final +/// `EvaluatedDelegateEntry` are built. pub struct EvaluatedForEntry { pub generics: Generics, pub table_type: Type, @@ -22,6 +27,8 @@ pub struct EvaluatedForEntry { pub mapping_value: Type, } +/// Lower every for-entry a `namespace`/`for` statement produces into evaluated +/// delegate entries. pub fn eval_delegate_entries_via_for( entry: &Entry, table_type: &Type, diff --git a/crates/macros/cgp-macro-core/src/types/delegate_component/statement/for_loop.rs b/crates/macros/cgp-macro-core/src/types/delegate_component/statement/for_loop.rs index c948f0ce..15d66ccc 100644 --- a/crates/macros/cgp-macro-core/src/types/delegate_component/statement/for_loop.rs +++ b/crates/macros/cgp-macro-core/src/types/delegate_component/statement/for_loop.rs @@ -10,6 +10,9 @@ use crate::types::delegate_component::{ }; use crate::types::ident::PathWithTypeArgs; +/// The `for in SomeTable where .. { .. }` loop that pulls mappings +/// out of another lookup table, reconstructing each entry's namespace-trait bound +/// with the table type and a `Delegate =` binding appended. #[derive(Debug, Clone)] pub struct ForDelegateStatement { pub for_token: For, diff --git a/crates/macros/cgp-macro-core/src/types/delegate_component/statement/namespace.rs b/crates/macros/cgp-macro-core/src/types/delegate_component/statement/namespace.rs index 586a85ce..62f5d167 100644 --- a/crates/macros/cgp-macro-core/src/types/delegate_component/statement/namespace.rs +++ b/crates/macros/cgp-macro-core/src/types/delegate_component/statement/namespace.rs @@ -10,6 +10,9 @@ use crate::types::delegate_component::{ use crate::types::keyword::Keyword; use crate::types::keywords::Namespace; +/// The `namespace SomeNamespace;` header. It forwards every lookup through the +/// named namespace trait via a blanket `DelegateComponent<__Key__>` impl bounded +/// on `__Key__: SomeNamespace`. #[derive(Debug, Clone)] pub struct NamespaceDelegateStatement { pub namespace: Keyword, diff --git a/crates/macros/cgp-macro-core/src/types/delegate_component/statement/open.rs b/crates/macros/cgp-macro-core/src/types/delegate_component/statement/open.rs index 1d1dbd97..6a64e5e3 100644 --- a/crates/macros/cgp-macro-core/src/types/delegate_component/statement/open.rs +++ b/crates/macros/cgp-macro-core/src/types/delegate_component/statement/open.rs @@ -1,6 +1,6 @@ use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; -use syn::token::{Comma, Semi}; +use syn::token::{Brace, Comma, Semi}; use syn::{Type, braced}; use crate::exports::{Nil, PathCons, RedirectLookup}; @@ -9,6 +9,10 @@ use crate::types::delegate_component::{EvalDelegateEntries, EvaluatedDelegateEnt use crate::types::keyword::Keyword; use crate::types::keywords::Open; +/// The `open { A, B };` header — braces optional when opening a single +/// component (`open A;`). Each listed component is wired to a +/// `RedirectLookup` rooted at the component name in the context's own table, so +/// the `@Component.Key` mappings that follow dispatch on the redirect path. #[derive(Debug, Clone)] pub struct OpenDelegateStatement { pub open: Keyword, @@ -20,11 +24,16 @@ impl Parse for OpenDelegateStatement { fn parse(input: ParseStream) -> syn::Result { let open = input.parse()?; - let components: Punctuated = { + let components: Punctuated = if input.peek(Brace) { let body; braced!(body in input); Punctuated::parse_terminated(&body)? + } else { + // Braceless single-component form `open A;`. Opening several + // components at once still requires the braced list. + let component: Type = input.parse()?; + Punctuated::from_iter([component]) }; let semi = input.parse()?; diff --git a/crates/macros/cgp-macro-core/src/types/delegate_component/table/inner.rs b/crates/macros/cgp-macro-core/src/types/delegate_component/table/inner.rs index fa9bbf4e..0cbe6b82 100644 --- a/crates/macros/cgp-macro-core/src/types/delegate_component/table/inner.rs +++ b/crates/macros/cgp-macro-core/src/types/delegate_component/table/inner.rs @@ -6,10 +6,14 @@ use crate::types::delegate_component::DelegateEntries; use crate::types::empty_struct::EmptyStruct; use crate::types::generics::TypeGenerics; +/// Collect every nested `UseDelegate` table reachable from a +/// construct, so `DelegateTable::eval` can emit each inner table's own items. pub trait ExtractInnerDelegateTables { fn extract_inner_tables(&self) -> Vec; } +/// A nested table lifted out of a `UseDelegate` value; it is +/// evaluated exactly like a top-level table, keyed on its own identifier. #[derive(Debug, Clone)] pub struct InnerDelegateTable { pub table_ident: Ident, diff --git a/crates/macros/cgp-macro-core/src/types/delegate_component/table/main.rs b/crates/macros/cgp-macro-core/src/types/delegate_component/table/main.rs index 54aa2776..b98d5022 100644 --- a/crates/macros/cgp-macro-core/src/types/delegate_component/table/main.rs +++ b/crates/macros/cgp-macro-core/src/types/delegate_component/table/main.rs @@ -12,6 +12,8 @@ use crate::types::ident::IdentWithTypeGenerics; use crate::types::keyword::Keyword; use crate::types::keywords::New; +/// The whole parsed `delegate_components!` body: an optional generic list and +/// `new` keyword, the target type, and its braced table of entries. pub struct DelegateTable { pub attributes: Vec, pub impl_generics: ImplGenerics, @@ -20,6 +22,8 @@ pub struct DelegateTable { pub entries: DelegateEntries, } +/// The lowered table: the generated impls and any structs (`new` target and +/// lifted inner tables). Its `ToTokens` emits the structs first, then the impls. pub struct EvaluatedDelegateTable { pub item_impls: Vec, pub item_structs: Vec, @@ -53,6 +57,9 @@ impl Parse for DelegateTable { } impl DelegateTable { + /// Lower the table: emit the `new` struct if present, build every entry's + /// impl pair, and lift out each nested `UseDelegate` inner table as its own + /// struct and impls. pub fn eval(&self) -> syn::Result { let mut item_impls = Vec::new(); let mut item_structs = Vec::new(); diff --git a/crates/macros/cgp-macro-core/src/types/delegate_component/validate_attributes.rs b/crates/macros/cgp-macro-core/src/types/delegate_component/validate_attributes.rs index 2cd071ba..565326e6 100644 --- a/crates/macros/cgp-macro-core/src/types/delegate_component/validate_attributes.rs +++ b/crates/macros/cgp-macro-core/src/types/delegate_component/validate_attributes.rs @@ -3,8 +3,9 @@ use syn::spanned::Spanned; use syn::{Attribute, Error}; use crate::types::delegate_component::{ - DelegateEntries, DelegateKey, DelegateMapping, DelegateStatement, DelegateTable, - ForDelegateStatement, MultiDelegateKey, PathDelegateKey, SingleDelegateKey, + DelegateEntries, DelegateKey, DelegateMapping, DelegateStatement, DelegateTable, DelegateValue, + DelegateValueWithInnerTable, ForDelegateStatement, MultiDelegateKey, PathDelegateKey, + SingleDelegateKey, }; /** @@ -16,6 +17,8 @@ pub trait ValidateAttributes { fn validate_attributes(&self) -> syn::Result<()>; } +/// Error with a spanned "unsupported attribute" message if any attribute is +/// present, pointing at the first one. pub fn reject_non_empty_attributes(attributes: &[Attribute]) -> syn::Result<()> { if !attributes.is_empty() { let attribute = &attributes[0]; @@ -67,11 +70,37 @@ impl ValidateAttributes for DelegateKey { } } +impl ValidateAttributes for DelegateValueWithInnerTable { + fn validate_attributes(&self) -> syn::Result<()> { + // The wrapper cannot carry attributes, but the inner table's keys can, so + // recurse into it rather than letting an attribute be silently dropped. + self.inner_table.entries.validate_attributes() + } +} + +impl ValidateAttributes for DelegateValue { + fn validate_attributes(&self) -> syn::Result<()> { + match self { + DelegateValue::Type(_) => Ok(()), + DelegateValue::WithTable(value) => value.validate_attributes(), + } + } +} + impl ValidateAttributes for DelegateMapping { fn validate_attributes(&self) -> syn::Result<()> { match self { - DelegateMapping::Normal(mapping) => mapping.key.validate_attributes(), - DelegateMapping::Direct(mapping) => mapping.key.validate_attributes(), + // A Normal or Direct value may open a nested inner table whose keys + // can hold attributes, so validate the value as well as the key. + DelegateMapping::Normal(mapping) => { + mapping.key.validate_attributes()?; + mapping.value.validate_attributes() + } + DelegateMapping::Direct(mapping) => { + mapping.key.validate_attributes()?; + mapping.value.validate_attributes() + } + // A Redirect value is a bare `@`-path with no inner table. DelegateMapping::Redirect(mapping) => mapping.key.validate_attributes(), } } @@ -81,6 +110,7 @@ impl ValidateAttributes for ForDelegateStatement { fn validate_attributes(&self) -> syn::Result<()> { for mapping in &self.mappings { mapping.key.validate_attributes()?; + mapping.value.validate_attributes()?; } Ok(()) diff --git a/crates/macros/cgp-macro-core/src/types/delegate_component/value/combined.rs b/crates/macros/cgp-macro-core/src/types/delegate_component/value/combined.rs index bce6fb69..66d40939 100644 --- a/crates/macros/cgp-macro-core/src/types/delegate_component/value/combined.rs +++ b/crates/macros/cgp-macro-core/src/types/delegate_component/value/combined.rs @@ -6,6 +6,8 @@ use crate::types::delegate_component::{ DelegateValueWithInnerTable, EvalDelegateValue, ExtractInnerDelegateTables, InnerDelegateTable, }; +/// The right side of a Normal or Direct mapping: either a plain provider type or +/// a legacy `Wrapper` value that opens a nested table. #[derive(Debug, Clone)] pub enum DelegateValue { Type(Type), diff --git a/crates/macros/cgp-macro-core/src/types/delegate_component/value/eval.rs b/crates/macros/cgp-macro-core/src/types/delegate_component/value/eval.rs index 77accf66..5bd73aae 100644 --- a/crates/macros/cgp-macro-core/src/types/delegate_component/value/eval.rs +++ b/crates/macros/cgp-macro-core/src/types/delegate_component/value/eval.rs @@ -1,5 +1,6 @@ use syn::Type; +/// Lower a mapping value into the provider `Type` stored as the entry's `Delegate`. pub trait EvalDelegateValue { fn eval(&self) -> syn::Result; } diff --git a/crates/macros/cgp-macro-core/src/types/delegate_component/value/inner_table.rs b/crates/macros/cgp-macro-core/src/types/delegate_component/value/inner_table.rs index 0be4b7d4..8da510d5 100644 --- a/crates/macros/cgp-macro-core/src/types/delegate_component/value/inner_table.rs +++ b/crates/macros/cgp-macro-core/src/types/delegate_component/value/inner_table.rs @@ -9,6 +9,9 @@ use crate::types::delegate_component::{ use crate::types::keyword::Keyword; use crate::types::keywords::New; +/// The legacy nested-dispatch value `Wrapper`: it evaluates to +/// the type `Wrapper` (dropping `new`), and the inner table is lifted +/// out to become its own struct and impls. #[derive(Debug, Clone)] pub struct DelegateValueWithInnerTable { pub new: Keyword, diff --git a/crates/macros/cgp-macro-core/src/types/product/product_expr.rs b/crates/macros/cgp-macro-core/src/types/product/product_expr.rs index a9a96c42..e7ea5181 100644 --- a/crates/macros/cgp-macro-core/src/types/product/product_expr.rs +++ b/crates/macros/cgp-macro-core/src/types/product/product_expr.rs @@ -1,5 +1,5 @@ use quote::quote; -use syn::Type; +use syn::Expr; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; use syn::token::Comma; @@ -7,17 +7,21 @@ use syn::token::Comma; use crate::exports::{Cons, Nil}; use crate::functions::parse_internal; +/// The value-level `product!` macro: a list of expression items that folds into a +/// `Cons(..)`/`Nil` value expression. The type-level counterpart is `ProductType`. pub struct ProductExpr { - pub types: Punctuated, + pub exprs: Punctuated, } impl ProductExpr { - pub fn eval(&self) -> syn::Result { + /// Fold the items right-to-left onto `Nil` with the `Cons` tuple-struct + /// constructor, yielding a value expression (not a type). + pub fn eval(&self) -> syn::Result { let mut out = quote!(#Nil); - for ty in self.types.iter().rev() { + for expr in self.exprs.iter().rev() { out = quote! { - #Cons(#ty, #out) + #Cons(#expr, #out) }; } @@ -27,8 +31,8 @@ impl ProductExpr { impl Parse for ProductExpr { fn parse(input: ParseStream) -> syn::Result { - let types = Punctuated::parse_terminated(input)?; + let exprs = Punctuated::parse_terminated(input)?; - Ok(Self { types }) + Ok(Self { exprs }) } } diff --git a/crates/macros/cgp-macro-core/src/visitors/replace_self/replace_self_receiver.rs b/crates/macros/cgp-macro-core/src/visitors/replace_self/replace_self_receiver.rs index 7fdf0bce..0110d082 100644 --- a/crates/macros/cgp-macro-core/src/visitors/replace_self/replace_self_receiver.rs +++ b/crates/macros/cgp-macro-core/src/visitors/replace_self/replace_self_receiver.rs @@ -2,6 +2,9 @@ use proc_macro2::Ident; use syn::visit_mut::VisitMut; use syn::{FnArg, Receiver, Signature, Type, parse_quote}; +/// Rewrites a method's `self` receiver into an explicit context parameter, +/// preserving the reference and mutability shape (`&self` → `ctx: &Context`, +/// `&mut self` → `ctx: &mut Context`, `self` → `ctx: Context`). pub struct ReplaceSelfReceiverVisitor<'a> { pub replaced_ident: &'a Ident, pub replaced_type: &'a Type, @@ -39,7 +42,9 @@ pub fn replace_self_receiver( parse_quote!(#replaced_ident : & #life mut #replaced_type) } (None, Some(_mut)) => { - parse_quote!(#replaced_ident : mut #replaced_type) + // Owned mutable receiver `mut self`: `mut` binds the parameter, not + // the type, so it must precede the identifier — `mut ctx: Context`. + parse_quote!(mut #replaced_ident : #replaced_type) } } } diff --git a/crates/macros/cgp-macro-core/src/visitors/replace_self/replace_self_type.rs b/crates/macros/cgp-macro-core/src/visitors/replace_self/replace_self_type.rs index 891514b0..62a09a73 100644 --- a/crates/macros/cgp-macro-core/src/visitors/replace_self/replace_self_type.rs +++ b/crates/macros/cgp-macro-core/src/visitors/replace_self/replace_self_type.rs @@ -4,6 +4,9 @@ use quote::{ToTokens, format_ident}; use syn::visit_mut::{self, VisitMut}; use syn::{Macro, Path, Type}; +/// Rewrites the `Self` type (and `Self::Foo` paths) to the context type, +/// skipping any `Self::Assoc` whose associated type is declared locally in the +/// block so the trait's own associated types are left intact. pub struct ReplaceSelfTypeVisitor<'a> { pub replaced_type: &'a Type, pub skip_assoc_types: &'a Vec, diff --git a/crates/macros/cgp-macro-core/src/visitors/replace_self/replace_self_value.rs b/crates/macros/cgp-macro-core/src/visitors/replace_self/replace_self_value.rs index 9e144a5a..4fc82e1d 100644 --- a/crates/macros/cgp-macro-core/src/visitors/replace_self/replace_self_value.rs +++ b/crates/macros/cgp-macro-core/src/visitors/replace_self/replace_self_value.rs @@ -1,8 +1,12 @@ +use itertools::Itertools; use proc_macro2::{Group, TokenStream, TokenTree}; use quote::format_ident; use syn::visit_mut::{self, VisitMut}; use syn::{Expr, Ident, ItemFn, Macro, Path}; +/// Rewrites every `self` *value* expression to the context identifier, stopping +/// at nested `fn` items (which do not capture the outer `self`) and leaving +/// `self::` module paths inside macro bodies intact. pub struct ReplaceSelfValueVisitor<'a> { pub replaced_ident: &'a Ident, } @@ -37,12 +41,19 @@ pub fn replace_self_value_in_token_stream( let mut result_stream: Vec = Vec::new(); - let token_iter = stream.into_iter(); + let mut token_iter = stream.into_iter().multipeek(); - for tree in token_iter { + while let Some(tree) = token_iter.next() { match tree { TokenTree::Ident(ident) => { - if ident == self_ident { + // Rewrite `self` as a value expression, but leave a `self::` module + // path intact — its leading `self` is the current module, not the + // receiver. A value `self` is never followed by `::`, so a trailing + // `::` unambiguously marks the path form. + let is_module_path = matches!(token_iter.peek(), Some(TokenTree::Punct(p)) if p.as_char() == ':') + && matches!(token_iter.peek(), Some(TokenTree::Punct(p)) if p.as_char() == ':'); + + if ident == self_ident && !is_module_path { result_stream.push(TokenTree::Ident(replaced_ident.clone())); } else { result_stream.push(TokenTree::Ident(ident)); diff --git a/crates/tests/CLAUDE.md b/crates/tests/CLAUDE.md index 2098f79e..73a01a07 100644 --- a/crates/tests/CLAUDE.md +++ b/crates/tests/CLAUDE.md @@ -5,7 +5,7 @@ moving, or refactoring any test here. Invoke the `/cgp` skill first — every te in this tree is CGP code, and the skill is the authoritative source for CGP semantics and vocabulary. -The test suite has three jobs, split across crates: +The test suite has four jobs, split across crates: - **`cgp-tests`** is the main suite: realistic example code that must **compile and run**. A passing test is often just successful compilation, because much of CGP @@ -13,8 +13,11 @@ The test suite has three jobs, split across crates: user-facing macros are exercised end-to-end. - **`cgp-macro-tests`** tests the **internals** of the CGP macros by calling the functions in `cgp-macro-core` directly (parsers, AST types), and is the home for - **failure cases** — inputs CGP should reject, and cases where a macro currently - emits invalid or wrong code. + **inputs a macro rejects** (via `assert_macro_rejects`) and for **pinning the exact + invalid tokens** a macro emits (`invalid_expansion` string snapshots). +- **`cgp-compile-fail-tests`** holds the **`compile_fail` doctests**: input a macro + *accepts* but whose *expansion* then fails to compile. It is a library crate + because doctests run only for a library target; see "Adding a failure case" below. - **`cgp-test-crate-a` / `cgp-test-crate-b`** are auxiliary packages for **cross-crate** behavior: whether a downstream crate can extend a namespace or provide a provider for a component defined elsewhere, under Rust's coherence and @@ -107,18 +110,50 @@ plain macro, not the snapshot form. The expansion is already pinned in the ownin target, and a redundant snapshot only adds golden output that breaks on unrelated macro changes. -## Adding a failure case (in `cgp-macro-tests`) +## Adding a failure case CGP will have corner cases it does not yet handle. Do **not** try to fix them inline -while refactoring; capture them as failing-behavior tests instead, in a dedicated -failure-case target: - -- **Input that should be rejected** — assert the `cgp-macro-core` parser rejects it, - using the `assert_rejects` helper pattern (see `ident_with_type_params`). -- **A macro that emits invalid Rust** — capture the expanded code as an `insta` - inline snapshot (the snapshot is a *string*, so it compiles even though the code - would not), and add a code comment explaining **why** the output is wrong and - **what the correct output should be**. +while refactoring; capture them as failing-behavior tests instead. The mechanism +depends on *where* the failure lands — whether the macro refuses the input, or +accepts it and emits Rust that then fails to compile. + +**Input a macro rejects — test the entrypoint in `cgp-macro-tests`.** When a macro +itself refuses the input by returning `Err` during expansion, assert it with the +`assert_macro_rejects` helper in `cgp-macro-tests` (see `parser_rejections`). This +drives the `cgp-macro-lib` entrypoint directly and checks the internal `Result`, +which is enough to pin a rejection and gives a precise check of the macro's own +diagnostic. This is the right tool for a structural error the macro is expected to +catch, and such a case does **not** also need a compile-fail test. + +**Input a macro accepts whose expansion fails to compile — a `compile_fail` doctest +in `cgp-compile-fail-tests`.** Reserve `compile_fail` tests for input a CGP macro +*accepts* but whose *expansion* then fails to type- or borrow-check — the failure +lands on the emitted Rust, not inside the macro. This is the tool for a documented +bug or known limitation, and for the cases a macro cannot reject because it lacks the +whole-program view the check needs: two separate `delegate_components!` blocks that +delegate the same key, or generic `delegate_components!` entries that expand to +overlapping impls, both of which the macro defers to the Rust compiler. Write each as +a ```` ```rust,compile_fail ```` doctest containing the real macro invocation; +rustdoc compiles it and the test passes only if it fails to compile. Pair it with a +companion ```` ```rust ```` block that compiles once the offending element is +removed, so the probe proves *which* element causes the failure, and comment on why +it must not compile. + +These doctests live in the dedicated **`cgp-compile-fail-tests`** crate, not in +`cgp-tests` — doctests are collected only from a *library* target, never from an +integration (`tests/`) target, so an integration-test file's doctests would silently +never run. Inside that crate, group cases by CGP concept exactly as the main suite +does: one subdirectory per concept under `src/` (`basic_delegation/`, `dispatching/`, +…), one module file per category of case within it, registered with `pub mod`. Run +them with `cargo test --doc -p cgp-compile-fail-tests`; `cargo nextest` does not run +doctests. + +**Pinning the exact invalid output** is a separate, rarer need: only when you must +*inspect* the wrong tokens a macro emits (not merely assert they fail to compile), +capture the expanded code as an `insta` inline string snapshot in the +`invalid_expansion` target of `cgp-macro-tests` (the snapshot is a *string*, so it +compiles even though the code would not), with a comment explaining **why** the +output is wrong and **what the correct output should be**. Every failure case must also be recorded in the construct's **implementation document** under `docs/implementation/`, in its `## Known issues` section and @@ -145,14 +180,20 @@ document's Tests or Snapshots section in the same change. ## Running the suite ``` -cargo nextest run -p cgp-tests # the main suite -cargo nextest run -p cgp-macro-tests # macro internals + failures -cargo nextest run --workspace # everything +cargo nextest run -p cgp-tests # the main suite +cargo nextest run -p cgp-macro-tests # macro internals + rejection/invalid-expansion cases +cargo nextest run --workspace # everything + +cargo test --doc -p cgp-compile-fail-tests # compile_fail doctests — NOT run by nextest -cargo insta test -p cgp-tests --review # review snapshot diffs -cargo insta test -p cgp-tests --accept # accept intended snapshot changes +cargo insta test -p cgp-tests --review # review snapshot diffs +cargo insta test -p cgp-tests --accept # accept intended snapshot changes ``` +`cargo nextest` does not run doctests, so the `compile_fail` cases in +`cgp-compile-fail-tests` must be run with `cargo test --doc`; include it when +verifying a change that touches a macro's accepted input or its expansion. + A snapshot test that fails prints a diff of the generated code; accept it with `cargo insta` only after confirming the change is intended. diff --git a/crates/tests/cgp-compile-fail-tests/Cargo.toml b/crates/tests/cgp-compile-fail-tests/Cargo.toml new file mode 100644 index 00000000..658db067 --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "cgp-compile-fail-tests" +version = "0.7.0" +edition = { workspace = true } +license = { workspace = true } +repository = { workspace = true } +authors = { workspace = true } +rust-version = { workspace = true } +keywords = { workspace = true } + +[dependencies] +cgp = { workspace = true } diff --git a/crates/tests/cgp-compile-fail-tests/src/lib.rs b/crates/tests/cgp-compile-fail-tests/src/lib.rs new file mode 100644 index 00000000..ea77fd0a --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/src/lib.rs @@ -0,0 +1,39 @@ +//! Compile-fail tests for the CGP macros. +//! +//! Every test here is a `compile_fail` doctest: a ```` ```rust,compile_fail ```` +//! block containing a real CGP macro invocation that must **not** compile. +//! rustdoc compiles each block and the test passes only if compilation fails, so +//! this crate exists as a library — doctests are collected only from a library +//! target, never from an integration (`tests/`) target. Run the suite with +//! `cargo test --doc -p cgp-compile-fail-tests` (note that `cargo nextest` does +//! not run doctests). +//! +//! # What belongs here +//! +//! A `compile_fail` doctest is reserved for input that a CGP macro **accepts** but +//! whose **expansion** then fails to compile — the failure lands on the emitted +//! Rust, not inside the macro. This is the right tool for a documented bug or +//! known limitation, and for the cases a macro cannot reject because it lacks the +//! whole-program view the borrow/coherence check needs: two separate +//! `delegate_components!` blocks that delegate the same key, or generic +//! `delegate_components!` entries that expand to overlapping impls, both of which +//! the macro defers to the Rust compiler. Pair each probe with a companion +//! ```` ```rust ```` block that compiles once the offending element is removed, so +//! the test proves *which* element causes the failure, and comment on why it must +//! not compile. +//! +//! Input that a macro **rejects** during expansion (it returns `Err`) does not +//! belong here — test it by driving the entrypoint function directly in +//! `cgp-macro-tests` with the `assert_macro_rejects` helper, which is enough to +//! pin a rejection and gives a precise check of the macro's own diagnostic. +//! +//! # Organization +//! +//! Tests are grouped by CGP concept, mirroring the layout of the main `cgp-tests` +//! suite: one subdirectory per concept under `src/` (`basic_delegation/`, +//! `dispatching/`, …), and within each, one module file per category of +//! compile-fail case. Register each concept as a `pub mod` below and each category +//! as a `pub mod` in the concept's `mod.rs`. +//! +//! No cases are enumerated yet; a future agent adds a concept subdirectory and its +//! category modules alongside the first case it captures. diff --git a/crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_component.rs b/crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_component.rs index d7f8665b..98b1c51c 100644 --- a/crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_component.rs +++ b/crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_component.rs @@ -1,8 +1,8 @@ -//! `#[cgp_component]` must be applied to a trait; applying it to another item -//! is rejected at parse time. +//! `#[cgp_component]` rejects inputs it cannot lower into a component: a +//! non-trait item, and a trait carrying a const generic parameter. //! -//! See docs/implementation/entrypoints/cgp_component.md (Tests) for this failure -//! case, and docs/reference/macros/cgp_component.md for the user-facing semantics. +//! See docs/implementation/entrypoints/cgp_component.md (Tests) for these failure +//! cases, and docs/reference/macros/cgp_component.md for the user-facing semantics. use quote::quote; @@ -20,3 +20,20 @@ fn rejects_non_trait_item() { ) }); } + +#[test] +fn rejects_const_generic_parameter() { + // A const value has no place in the `IsProviderFor` params tuple (a tuple of + // types) and cannot key CGP's type-based wiring, so a const generic parameter + // on the component is rejected rather than lowered into non-compiling code. + assert_macro_rejects("cgp_component with a const generic parameter", || { + cgp_macro_lib::cgp_component( + quote!(Foo), + quote!( + pub trait CanFoo { + fn foo(&self) -> usize; + } + ), + ) + }); +} diff --git a/crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_fn.rs b/crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_fn.rs new file mode 100644 index 00000000..caf38961 --- /dev/null +++ b/crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_fn.rs @@ -0,0 +1,74 @@ +//! `#[cgp_fn]` rejects the implicit-argument shapes it cannot lower: an implicit +//! argument on a function with no `self` receiver, a `mut` binding pattern on an +//! implicit argument, and a `&mut` implicit argument that is not the sole implicit +//! (its exclusive borrow of the context would conflict with reading any other +//! field). Each is a rejection the macro makes during expansion, so it is pinned +//! by driving the entrypoint directly here rather than by a `compile_fail` test. +//! +//! See docs/implementation/entrypoints/cgp_fn.md (Tests) for these failure cases, +//! and docs/reference/attributes/implicit.md for the user-facing rules on where +//! `#[implicit]` may appear. + +use quote::quote; + +use super::assert_macro_rejects; + +#[test] +fn rejects_implicit_arg_without_self() { + // The value of an implicit argument is read from a field of `self`, so a + // function that declares one without a `self` receiver has nowhere to read + // from and is rejected. + assert_macro_rejects("cgp_fn with an implicit argument but no self", || { + cgp_macro_lib::cgp_fn( + quote!(), + quote!( + pub fn rectangle_area(#[implicit] width: f64) -> f64 { + width + } + ), + ) + }); +} + +#[test] +fn rejects_mut_pattern_on_implicit_arg() { + // An implicit argument is bound to the injected field value; a `mut` binding + // pattern is rejected so the read stays immutable (clone the value inside the + // body to get a mutable local). + assert_macro_rejects("cgp_fn with a `mut` implicit argument pattern", || { + cgp_macro_lib::cgp_fn( + quote!(), + quote!( + pub fn rectangle_area(&self, #[implicit] mut width: f64) -> f64 { + width + } + ), + ) + }); +} + +#[test] +fn rejects_mutable_implicit_with_other_implicit() { + // A `&mut` implicit reads through `get_field_mut`, borrowing the whole context + // exclusively for the rest of the body, so it cannot share a function with any + // other implicit read; the macro rejects the combination rather than emit an + // impl that fails to borrow-check. (A lone `&mut` implicit, or any number of + // purely immutable implicits, is accepted.) + assert_macro_rejects( + "cgp_fn with a &mut implicit alongside another implicit", + || { + cgp_macro_lib::cgp_fn( + quote!(), + quote!( + pub fn append_note( + &mut self, + #[implicit] buffer: &mut String, + #[implicit] note: &str, + ) { + buffer.push_str(note); + } + ), + ) + }, + ); +} diff --git a/crates/tests/cgp-macro-tests/tests/parser_rejections/check_components.rs b/crates/tests/cgp-macro-tests/tests/parser_rejections/check_components.rs new file mode 100644 index 00000000..9bf425e0 --- /dev/null +++ b/crates/tests/cgp-macro-tests/tests/parser_rejections/check_components.rs @@ -0,0 +1,63 @@ +//! `check_components!` rejects a malformed table-level attribute set: an empty +//! `#[check_providers()]` (which would otherwise emit a check trait with no impls +//! that verifies nothing), a repeated `#[check_providers]` or `#[check_trait]`, +//! and any unrecognized attribute. +//! +//! See docs/implementation/entrypoints/check_components.md (Tests) for these +//! failure cases, and docs/reference/macros/check_components.md for the +//! user-facing semantics. + +use quote::quote; + +use super::assert_macro_rejects; + +#[test] +fn rejects_empty_check_providers() { + assert_macro_rejects( + "check_components with an empty #[check_providers()]", + || { + cgp_macro_lib::check_components(quote!( + #[check_providers()] + Context { FooComponent } + )) + }, + ); +} + +#[test] +fn rejects_duplicate_check_providers() { + assert_macro_rejects( + "check_components with two #[check_providers] attributes", + || { + cgp_macro_lib::check_components(quote!( + #[check_providers(FooProvider)] + #[check_providers(BarProvider)] + Context { FooComponent } + )) + }, + ); +} + +#[test] +fn rejects_duplicate_check_trait() { + assert_macro_rejects( + "check_components with two #[check_trait] attributes", + || { + cgp_macro_lib::check_components(quote!( + #[check_trait(CheckA)] + #[check_trait(CheckB)] + Context { FooComponent } + )) + }, + ); +} + +#[test] +fn rejects_unknown_attribute() { + assert_macro_rejects("check_components with an unknown table attribute", || { + cgp_macro_lib::check_components(quote!( + #[foo] + Context { FooComponent } + )) + }); +} diff --git a/crates/tests/cgp-macro-tests/tests/parser_rejections/delegate_components.rs b/crates/tests/cgp-macro-tests/tests/parser_rejections/delegate_components.rs new file mode 100644 index 00000000..1c8e0fe4 --- /dev/null +++ b/crates/tests/cgp-macro-tests/tests/parser_rejections/delegate_components.rs @@ -0,0 +1,73 @@ +//! `delegate_components!` supports no attributes, so it rejects any attribute it +//! finds — on the table, on a key, and, crucially, on a key nested inside a +//! `UseDelegate` value, which the validator must recurse into +//! rather than silently drop. It also rejects a braceless `open` header that +//! lists more than one component, since the braceless form opens exactly one. +//! +//! See docs/implementation/entrypoints/delegate_components.md (Tests) for these +//! failure cases, and docs/reference/macros/delegate_components.md for the +//! user-facing semantics. + +use quote::quote; + +use super::assert_macro_rejects; + +#[test] +fn rejects_attribute_on_table() { + assert_macro_rejects("delegate_components with an attribute on the table", || { + cgp_macro_lib::delegate_components(quote!( + #[foo] + Context { FooComponent: Bar } + )) + }); +} + +#[test] +fn rejects_attribute_on_key() { + assert_macro_rejects("delegate_components with an attribute on a key", || { + cgp_macro_lib::delegate_components(quote!(Context { + #[foo] + FooComponent: Bar, + })) + }); +} + +#[test] +fn rejects_braceless_open_with_multiple_components() { + // The braceless `open` form opens a single component; listing several + // without braces is rejected. The parser reads one component type after + // `open`, then fails on the trailing `,` where it expects the `;`. + assert_macro_rejects( + "delegate_components with a braceless open listing multiple components", + || { + cgp_macro_lib::delegate_components(quote!( + Context { + open FooComponent, BarComponent; + + @FooComponent.String: Foo, + @BarComponent.u32: Bar, + } + )) + }, + ); +} + +#[test] +fn rejects_attribute_on_inner_table_key() { + // The attribute is on a key inside the nested `UseDelegate` table. The + // validator recurses into the inner table, so this is rejected rather than + // parsed and discarded. + assert_macro_rejects( + "delegate_components with an attribute on an inner-table key", + || { + cgp_macro_lib::delegate_components(quote!( + Context { + FooComponent: UseDelegate, + } + )) + }, + ); +} diff --git a/crates/tests/cgp-macro-tests/tests/parser_rejections/mod.rs b/crates/tests/cgp-macro-tests/tests/parser_rejections/mod.rs index 56b4a9b1..57213e24 100644 --- a/crates/tests/cgp-macro-tests/tests/parser_rejections/mod.rs +++ b/crates/tests/cgp-macro-tests/tests/parser_rejections/mod.rs @@ -23,3 +23,7 @@ pub fn assert_macro_rejects(label: &str, run: impl FnOnce() -> syn::Result Scalar; + } + ), + ) + }); +} + +#[test] +fn rejects_duplicate_alias_across_specs() { + // Two specs importing under the same bare name would make the substitution + // silently pick one and drop the other, so it is rejected. + assert_macro_rejects("use_type duplicate alias across specs", || { + cgp_macro_lib::cgp_fn( + quote!(), + quote!( + #[use_type(HasFooType.Foo, HasBarType.Foo)] + pub fn do_foo(&self) -> Foo { + todo!() + } + ), + ) + }); +} + +#[test] +fn rejects_duplicate_alias_on_component() { + // The same duplicate check applies to a component trait, not only to impls + // and functions. + assert_macro_rejects("use_type duplicate alias on a component trait", || { + cgp_macro_lib::cgp_component( + quote!(FooProvider), + quote!( + #[use_type(HasErrorType.Error, HasOtherErrorType.Error)] + pub trait CanFoo { + fn foo(&self) -> Error; + } + ), + ) + }); +} + +#[test] +fn rejects_duplicate_alias_within_one_braced_list() { + // Two entries of one braced list aliasing to the same name are also a + // duplicate, even though they belong to the same trait. + assert_macro_rejects("use_type duplicate alias within one braced list", || { + cgp_macro_lib::cgp_fn( + quote!(), + quote!( + #[use_type(HasFooType.{Bar as Dup, Baz as Dup})] + pub fn do_foo(&self) -> Dup { + todo!() + } + ), + ) + }); +} diff --git a/crates/tests/cgp-tests/tests/abstract_types/mod.rs b/crates/tests/cgp-tests/tests/abstract_types/mod.rs index d326ecc0..da8165e5 100644 --- a/crates/tests/cgp-tests/tests/abstract_types/mod.rs +++ b/crates/tests/cgp-tests/tests/abstract_types/mod.rs @@ -17,6 +17,7 @@ pub mod extend_component; pub mod use_type_component; pub mod use_type_foreign; pub mod use_type_generic_param; +pub mod use_type_path_qualified; // The `#[use_type]` attribute rewriting abstract types inside `#[cgp_fn]`: the // bare alias, alias renaming, type-equality bounds, and foreign/nested type diff --git a/crates/tests/cgp-tests/tests/abstract_types/use_type_component.rs b/crates/tests/cgp-tests/tests/abstract_types/use_type_component.rs index 68aaf651..7be07062 100644 --- a/crates/tests/cgp-tests/tests/abstract_types/use_type_component.rs +++ b/crates/tests/cgp-tests/tests/abstract_types/use_type_component.rs @@ -1,7 +1,7 @@ //! Importing abstract types into a `#[cgp_component]` and `#[cgp_impl]` with //! `#[use_type(...)]`, and fixing them on a context with `UseType`. //! -//! `#[use_type(HasScalarType::Scalar, HasErrorType::Error)]` adds the two +//! `#[use_type(HasScalarType.Scalar, HasErrorType.Error)]` adds the two //! abstract-type components as supertraits/dependencies and rewrites the bare //! aliases `Scalar` and `Error` to `::Scalar` / //! `::Error`, so the signatures read without any `Self::` @@ -25,13 +25,13 @@ pub trait HasScalarType { } #[cgp_component(AreaCalculator)] -#[use_type(HasScalarType::Scalar, HasErrorType::Error)] +#[use_type(HasScalarType.Scalar, HasErrorType.Error)] pub trait CanCalculateArea { fn area(&self) -> Result; } #[cgp_impl(new RectangleArea)] -#[use_type(HasScalarType::Scalar, HasErrorType::Error)] +#[use_type(HasScalarType.Scalar, HasErrorType.Error)] impl AreaCalculator where Scalar: Mul + Copy, diff --git a/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_alias.rs b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_alias.rs index 5ca4f3eb..2c9c7bc1 100644 --- a/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_alias.rs +++ b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_alias.rs @@ -1,5 +1,5 @@ //! `#[use_type]` with a *local alias* in a `#[cgp_fn]`: -//! `#[use_type(HasScalarType::{Scalar as S})]`. +//! `#[use_type(HasScalarType.{Scalar as S})]`. //! //! The `{Scalar as S}` form imports the abstract type under the short name `S`, //! which the function body and `where` clause then use freely; the expansion @@ -22,7 +22,7 @@ pub trait HasScalarType { snapshot_cgp_fn! { #[cgp_fn] - #[use_type(HasScalarType::{Scalar as S})] + #[use_type(HasScalarType.{Scalar as S})] pub fn rectangle_area(&self, #[implicit] width: S, #[implicit] height: S) -> S where S: Mul + Copy, diff --git a/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_equality.rs b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_equality.rs index b12e8395..f08f6950 100644 --- a/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_equality.rs +++ b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_equality.rs @@ -1,5 +1,5 @@ //! `#[use_type]` with a *type-equality* bound in a `#[cgp_fn]`: -//! `#[use_type(HasScalarType::{Scalar = f64})]`. +//! `#[use_type(HasScalarType.{Scalar = f64})]`. //! //! The `{Scalar = f64}` form both imports `Scalar` (rewriting the bare alias to //! `::Scalar`) and pins it to a concrete type by adding @@ -19,7 +19,7 @@ pub trait HasScalarType { snapshot_cgp_fn! { #[cgp_fn] - #[use_type(HasScalarType::{Scalar = f64})] + #[use_type(HasScalarType.{Scalar = f64})] pub fn rectangle_area(&self, #[implicit] width: Scalar, #[implicit] height: Scalar) -> Scalar { let res: f64 = width * height; res diff --git a/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_equality_cross_trait.rs b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_equality_cross_trait.rs index ff502459..84ece3a1 100644 --- a/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_equality_cross_trait.rs +++ b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_equality_cross_trait.rs @@ -1,5 +1,5 @@ //! `#[use_type]` type-equality *across two traits* in `#[cgp_fn]`: -//! `#[use_type(HasBarType::{Bar as Baz = Foo}, HasFooType::Foo)]`. +//! `#[use_type(HasBarType.{Bar as Baz = Foo}, HasFooType.Foo)]`. //! //! The `{Bar as Baz = Foo}` form imports `HasBarType::Bar` under the alias `Baz` //! and equates it to the `Foo` alias imported from `HasFooType`, generating a @@ -28,7 +28,7 @@ pub trait HasBarType { snapshot_cgp_fn! { #[cgp_fn] - #[use_type(HasFooType::Foo)] + #[use_type(HasFooType.Foo)] pub fn do_foo(&self) -> Foo { todo!() } @@ -52,7 +52,7 @@ snapshot_cgp_fn! { snapshot_cgp_fn! { #[cgp_fn] - #[use_type(HasBarType::Bar)] + #[use_type(HasBarType.Bar)] pub fn do_bar(&self) -> Bar { todo!() } @@ -76,7 +76,7 @@ snapshot_cgp_fn! { snapshot_cgp_fn! { #[cgp_fn] - #[use_type(HasBarType::{Bar as Baz = Foo}, HasFooType::Foo)] + #[use_type(HasBarType.{Bar as Baz = Foo}, HasFooType.Foo)] #[uses(DoFoo, DoBar)] fn return_foo_or_bar(&self, flag: bool, #[implicit] foo: &Foo, #[implicit] bar: &Baz) -> Foo { if flag { diff --git a/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign.rs b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign.rs index 19362dd1..1dd3a89d 100644 --- a/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign.rs +++ b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign.rs @@ -1,5 +1,5 @@ //! `#[use_type]` importing an abstract type from a *foreign generic parameter* in -//! a `#[cgp_fn]`: `#[use_type(@Types::HasScalarType::Scalar)]`. +//! a `#[cgp_fn]`: `#[use_type(@Types.HasScalarType.Scalar)]`. //! //! The function is generic over `Types: HasScalarType`, and the `@Types::` prefix //! resolves `Scalar` against that parameter, rewriting the bare alias to @@ -22,7 +22,7 @@ pub trait HasScalarType { snapshot_cgp_fn! { #[cgp_fn] - #[use_type(@Types::HasScalarType::Scalar)] + #[use_type(@Types.HasScalarType.Scalar)] pub fn rectangle_area( &self, #[implicit] width: Scalar, diff --git a/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign_equality.rs b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign_equality.rs index 2a4112e8..1e9c86ee 100644 --- a/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign_equality.rs +++ b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign_equality.rs @@ -1,6 +1,6 @@ //! `#[use_type]` chaining an imported associated type into a *nested* foreign type //! source, with type-equality, in a `#[cgp_fn]`: -//! `#[use_type(HasTypes::Types, @Types::HasScalarType::{Scalar = f64})]`. +//! `#[use_type(HasTypes.Types, @Types.HasScalarType.{Scalar = f64})]`. //! //! `HasTypes::Types` imports the abstract `Types` from `Self`, then //! `@Types::HasScalarType::{Scalar = f64}` resolves `Scalar` against *that* @@ -27,8 +27,8 @@ pub trait HasTypes { snapshot_cgp_fn! { #[cgp_fn] #[use_type( - HasTypes::Types, - @Types::HasScalarType::{Scalar = f64}, + HasTypes.Types, + @Types.HasScalarType.{Scalar = f64}, )] pub fn rectangle_area(&self, #[implicit] width: Scalar, #[implicit] height: Scalar) -> Scalar { let res: f64 = width * height; diff --git a/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign_equality_cross_trait.rs b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign_equality_cross_trait.rs index 7df0e3da..e062d958 100644 --- a/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign_equality_cross_trait.rs +++ b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign_equality_cross_trait.rs @@ -1,5 +1,5 @@ //! `#[use_type]` reaching through a *nested foreign* associated type across traits -//! in `#[cgp_fn]`: `#[use_type(HasBarType::Bar, @Bar::HasFooType::Foo)]` and the +//! in `#[cgp_fn]`: `#[use_type(HasBarType.Bar, @Bar.HasFooType.Foo)]` and the //! equality form `@Bar::HasFooType::{Foo as BarFoo = Foo}`. //! //! Here `HasBarType::Bar` itself implements `HasFooType`, so `@Bar::HasFooType::Foo` @@ -25,7 +25,7 @@ pub trait HasBarType { snapshot_cgp_fn! { #[cgp_fn] - #[use_type(HasFooType::Foo)] + #[use_type(HasFooType.Foo)] pub fn do_foo(&self) -> Foo { todo!() } @@ -49,7 +49,7 @@ snapshot_cgp_fn! { snapshot_cgp_fn! { #[cgp_fn] - #[use_type(HasBarType::Bar, @Bar::HasFooType::Foo)] + #[use_type(HasBarType.Bar, @Bar.HasFooType.Foo)] pub fn do_bar(&self) -> Foo { todo!() } @@ -75,9 +75,9 @@ snapshot_cgp_fn! { snapshot_cgp_fn! { #[cgp_fn] #[use_type( - HasFooType::Foo, - HasBarType::Bar, - @Bar::HasFooType::{Foo as BarFoo = Foo}, + HasFooType.Foo, + HasBarType.Bar, + @Bar.HasFooType.{Foo as BarFoo = Foo}, )] #[uses(DoFoo, DoBar)] fn return_foo_or_bar(&self, flag: bool, #[implicit] foo: &Foo, #[implicit] bar: &BarFoo) -> Foo { diff --git a/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_nested_foreign.rs b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_nested_foreign.rs index f2cab1f4..1bb56ec2 100644 --- a/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_nested_foreign.rs +++ b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_nested_foreign.rs @@ -1,5 +1,5 @@ //! `#[use_type]` reaching a nested foreign type in `#[cgp_fn]` combined with -//! `#[extend_where]`: `#[use_type(HasTypes::Types, @Types::HasScalarType::Scalar)]` +//! `#[extend_where]`: `#[use_type(HasTypes.Types, @Types.HasScalarType.Scalar)]` //! plus `#[extend_where(Types: HasScalarType)]`. //! //! `HasTypes::Types` imports the abstract `Types`, then `@Types::HasScalarType::Scalar` @@ -30,7 +30,7 @@ pub trait HasTypes { snapshot_cgp_fn! { #[cgp_fn] - #[use_type(HasTypes::Types, @Types::HasScalarType::Scalar)] + #[use_type(HasTypes.Types, @Types.HasScalarType.Scalar)] #[extend_where(Types: HasScalarType)] pub fn rectangle_area(&self, #[implicit] width: Scalar, #[implicit] height: Scalar) -> Scalar where diff --git a/crates/tests/cgp-tests/tests/abstract_types/use_type_foreign.rs b/crates/tests/cgp-tests/tests/abstract_types/use_type_foreign.rs index d5f97f7e..294a319f 100644 --- a/crates/tests/cgp-tests/tests/abstract_types/use_type_foreign.rs +++ b/crates/tests/cgp-tests/tests/abstract_types/use_type_foreign.rs @@ -1,8 +1,8 @@ //! Importing an abstract type from a *foreign* type parameter with the -//! `#[use_type(@Types::HasScalarType::Scalar)]` form on a generic component. +//! `#[use_type(@Types.HasScalarType.Scalar)]` form on a generic component. //! //! When the abstract type lives on a generic parameter of the component rather -//! than on `Self`, the `@Types::` prefix tells `#[use_type]` to resolve `Scalar` +//! than on `Self`, the `@Types.` prefix tells `#[use_type]` to resolve `Scalar` //! against that parameter, rewriting the bare alias to //! `::Scalar`. The `Error` type is still resolved against //! `Self` via `HasErrorType::Error`. `Types` is a standalone type that supplies @@ -23,13 +23,13 @@ pub trait HasScalarType { } #[cgp_component(AreaCalculator)] -#[use_type(@Types::HasScalarType::Scalar, HasErrorType::Error)] +#[use_type(@Types.HasScalarType.Scalar, HasErrorType.Error)] pub trait CanCalculateArea { fn area(&self) -> Result; } #[cgp_impl(new RectangleArea)] -#[use_type(@Types::HasScalarType::Scalar, HasErrorType::Error)] +#[use_type(@Types.HasScalarType.Scalar, HasErrorType.Error)] impl AreaCalculator where Scalar: Mul + Copy, diff --git a/crates/tests/cgp-tests/tests/abstract_types/use_type_generic_param.rs b/crates/tests/cgp-tests/tests/abstract_types/use_type_generic_param.rs index 3b7f50f7..1aab9574 100644 --- a/crates/tests/cgp-tests/tests/abstract_types/use_type_generic_param.rs +++ b/crates/tests/cgp-tests/tests/abstract_types/use_type_generic_param.rs @@ -3,7 +3,7 @@ //! //! The component `Foo` is implemented for the concrete parameter //! `FooProvider`, where `Error` is the trait's generic parameter — but the -//! same name `Error` is also imported via `#[use_type(HasErrorType::Error)]`. This +//! same name `Error` is also imported via `#[use_type(HasErrorType.Error)]`. This //! test pins that the abstract-type import is desugared correctly into //! `Context::Error` (the imported associated type) rather than shadowing the //! generic parameter — i.e. `Self::Error` resolution stays distinct from the @@ -21,7 +21,7 @@ pub trait Foo { // The `Error` parameter in `FooProvider` must desugar into `Context::Error` // (the abstract error type imported by `#[use_type]`), not `Self::Error`. #[cgp_impl(new FooError)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] impl FooProvider { fn foo(&self, _value: &Error) {} } diff --git a/crates/tests/cgp-tests/tests/abstract_types/use_type_path_qualified.rs b/crates/tests/cgp-tests/tests/abstract_types/use_type_path_qualified.rs new file mode 100644 index 00000000..415ae7c3 --- /dev/null +++ b/crates/tests/cgp-tests/tests/abstract_types/use_type_path_qualified.rs @@ -0,0 +1,68 @@ +//! `#[use_type]` importing an abstract type from a *path-qualified* trait: +//! `#[use_type(scalar::HasScalarType.Scalar)]`. +//! +//! The `.` separator between the trait and its associated type lets the trait be a +//! full path (`scalar::HasScalarType`) whose own `::` segments stay unambiguous, so +//! the trait need not be brought into scope by its bare name. The bare alias +//! `Scalar` still rewrites to `::Scalar` and the +//! path-qualified trait becomes the supertrait/bound. `RectangleArea` provides the +//! area, and `Rectangle` fixes the scalar with `UseType`. +//! +//! See docs/reference/attributes/use_type.md and docs/concepts/abstract-types.md. + +use std::ops::Mul; + +use cgp::prelude::*; + +mod scalar { + use cgp::prelude::*; + + #[cgp_type] + pub trait HasScalarType { + type Scalar; + } +} + +// `scalar::HasScalarType` is referenced only by path; its bare name is never +// imported into this scope. +#[cgp_component(AreaCalculator)] +#[use_type(scalar::HasScalarType.Scalar)] +pub trait CanCalculateArea { + fn area(&self) -> Scalar; +} + +#[cgp_impl(new RectangleArea)] +#[use_type(scalar::HasScalarType.Scalar)] +impl AreaCalculator +where + Scalar: Mul + Copy, +{ + fn area(&self, #[implicit] width: Scalar, #[implicit] height: Scalar) -> Scalar { + width * height + } +} + +#[derive(HasField)] +pub struct Rectangle { + pub width: f64, + pub height: f64, +} + +delegate_and_check_components! { + Rectangle { + scalar::ScalarTypeProviderComponent: + UseType, + AreaCalculatorComponent: + RectangleArea, + } +} + +#[test] +fn test_rectangle_area() { + let rectangle = Rectangle { + width: 3.0, + height: 4.0, + }; + + assert_eq!(rectangle.area(), 12.0); +} diff --git a/crates/tests/cgp-tests/tests/basic_delegation/mod.rs b/crates/tests/cgp-tests/tests/basic_delegation/mod.rs index 85672a66..fa24b192 100644 --- a/crates/tests/cgp-tests/tests/basic_delegation/mod.rs +++ b/crates/tests/cgp-tests/tests/basic_delegation/mod.rs @@ -15,6 +15,8 @@ pub mod consumer_delegate_generic; pub mod consumer_delegate_getter; pub mod default_methods; pub mod impl_self; +pub mod owned_receiver; +pub mod self_in_macro; // `delegate_components!` shape variants (compile-time checks only). pub mod delegate_generic_nested_value; diff --git a/crates/tests/cgp-tests/tests/basic_delegation/owned_receiver.rs b/crates/tests/cgp-tests/tests/basic_delegation/owned_receiver.rs new file mode 100644 index 00000000..840c4fa3 --- /dev/null +++ b/crates/tests/cgp-tests/tests/basic_delegation/owned_receiver.rs @@ -0,0 +1,48 @@ +//! A `#[cgp_impl]` provider may take an owned, mutable receiver `mut self`. +//! +//! The receiver rewrite has to place `mut` on the generated parameter *binding* +//! (`mut __context__: Context`), not on the type: `mut` binds the parameter, +//! while `__context__: mut Context` is not a valid type. This file exercises the +//! owned-mutable receiver so the rewrite of every receiver shape — `&self`, +//! `&mut self`, `self`, and `mut self` — stays covered. +//! +//! See docs/implementation/entrypoints/cgp_impl.md and +//! docs/reference/macros/cgp_impl.md. + +use cgp::prelude::*; + +#[cgp_component(Consumer)] +pub trait CanConsume { + fn consume(self) -> String; +} + +#[cgp_impl(new ResetAndConsume)] +impl Consumer +where + Self: Sized + Default, +{ + fn consume(mut self) -> String { + // `core::mem::take` needs `&mut self`, so the receiver must be `mut`. + drop(core::mem::take(&mut self)); + "consumed".to_owned() + } +} + +#[derive(HasField, Default)] +pub struct Person { + pub name: String, +} + +delegate_components! { + Person { + ConsumerComponent: ResetAndConsume, + } +} + +#[test] +fn test_mut_owned_receiver() { + let person = Person { + name: "World".to_owned(), + }; + assert_eq!(person.consume(), "consumed"); +} diff --git a/crates/tests/cgp-tests/tests/basic_delegation/self_in_macro.rs b/crates/tests/cgp-tests/tests/basic_delegation/self_in_macro.rs new file mode 100644 index 00000000..95a4b76c --- /dev/null +++ b/crates/tests/cgp-tests/tests/basic_delegation/self_in_macro.rs @@ -0,0 +1,60 @@ +//! `self` rewriting inside a `#[cgp_impl]` body distinguishes a value receiver +//! from a `self::` module path. +//! +//! `#[cgp_impl]` rewrites `self` into the context, and inside a macro invocation +//! it must do so at the token level because `VisitMut` cannot see through a +//! `macro!( … )`. The rewrite has to tell the two meanings of `self` apart: a +//! bare `self` value (here `self.name()`) becomes the context, while a `self::` +//! module path (here `self::greeting::SUFFIX`) is the current module and must be +//! left untouched. A value `self` is never followed by `::`, so the trailing +//! `::` is what disambiguates the path form. +//! +//! See docs/implementation/entrypoints/cgp_impl.md and +//! docs/reference/macros/cgp_impl.md. + +use cgp::prelude::*; + +mod greeting { + pub const SUFFIX: &str = "!"; +} + +#[cgp_auto_getter] +pub trait HasName { + fn name(&self) -> &str; +} + +#[cgp_component(Greeter)] +pub trait CanGreet { + fn greet(&self) -> String; +} + +#[cgp_impl(new GreetHello)] +impl Greeter +where + Self: HasName, +{ + fn greet(&self) -> String { + // `self.name()` — value receiver, rewritten to the context. + // `self::greeting::SUFFIX` — module path, left intact. + format!("Hello, {}{}", self.name(), self::greeting::SUFFIX) + } +} + +#[derive(HasField)] +pub struct Person { + pub name: String, +} + +delegate_components! { + Person { + GreeterComponent: GreetHello, + } +} + +#[test] +fn test_self_in_macro() { + let person = Person { + name: "World".to_owned(), + }; + assert_eq!(person.greet(), "Hello, World!"); +} diff --git a/crates/tests/cgp-tests/tests/checking/check_path_context.rs b/crates/tests/cgp-tests/tests/checking/check_path_context.rs new file mode 100644 index 00000000..e1325904 --- /dev/null +++ b/crates/tests/cgp-tests/tests/checking/check_path_context.rs @@ -0,0 +1,66 @@ +//! `check_components!` accepts a path-qualified context type such as +//! `inner::Context`, deriving the check trait name from the final path segment +//! (`__CheckContext`). This mirrors `delegate_components!`, which uses the context +//! type verbatim, so a context defined in another module can be wired and checked +//! by its path without importing it. This concept owns the macro's expansion +//! snapshot. +//! +//! See docs/reference/macros/check_components.md and +//! docs/implementation/entrypoints/check_components.md. + +use cgp_macro_test_util::snapshot_check_components; + +pub mod inner { + use cgp::prelude::*; + + #[cgp_auto_getter] + pub trait HasName { + fn name(&self) -> &str; + } + + #[cgp_component(Greeter)] + pub trait CanGreet { + fn greet(&self); + } + + #[cgp_impl(new GreetHello)] + impl Greeter + where + Self: HasName, + { + fn greet(&self) { + let _ = self.name(); + } + } + + #[derive(HasField)] + pub struct Context { + pub name: String, + } + + // Plain wiring: the `delegate_components!` expansion is snapshotted in + // `basic_delegation`, so we invoke it plainly here. + delegate_components! { + Context { + GreeterComponent: GreetHello, + } + } +} + +snapshot_check_components! { + check_components! { + inner::Context { + inner::GreeterComponent, + } + } + + expand_check_path_context(output) { + insta::assert_snapshot!(output, @" + trait __CheckContext< + __Component__, + __Params__: ?Sized, + >: CanUseComponent<__Component__, __Params__> {} + impl __CheckContext for inner::Context {} + ") + } +} diff --git a/crates/tests/cgp-tests/tests/checking/mod.rs b/crates/tests/cgp-tests/tests/checking/mod.rs index c2dc0762..9d94e0a6 100644 --- a/crates/tests/cgp-tests/tests/checking/mod.rs +++ b/crates/tests/cgp-tests/tests/checking/mod.rs @@ -11,7 +11,9 @@ pub mod delegate_and_check_params; // `check_components!` snapshots (this concept owns the macro's expansion): the // standalone check with `#[check_trait(...)]` overrides and per-entry parameter -// lists, the `#[check_providers(...)]` form, and the generic-context/lifetime form. +// lists, the `#[check_providers(...)]` form, the generic-context/lifetime form, and +// the path-qualified-context form. pub mod check_generic; +pub mod check_path_context; pub mod check_providers; pub mod check_trait; diff --git a/crates/tests/cgp-tests/tests/extensible_records/mod.rs b/crates/tests/cgp-tests/tests/extensible_records/mod.rs index efd4b191..9b81ad5d 100644 --- a/crates/tests/cgp-tests/tests/extensible_records/mod.rs +++ b/crates/tests/cgp-tests/tests/extensible_records/mod.rs @@ -17,6 +17,10 @@ pub mod tuple_record; pub mod record_build_from; pub mod record_build_with_handlers; +// The value-level `product!` macro: building a `Cons`/`Nil` value from expression +// items, whose type is the matching `Product!`. +pub mod product_value; + // `#[derive(HasFields)]` on structs (this concept owns the derive's expansion): // deriving only the field list, across named/tuple/generic/lifetime shapes. pub mod struct_generic; diff --git a/crates/tests/cgp-tests/tests/extensible_records/product_value.rs b/crates/tests/cgp-tests/tests/extensible_records/product_value.rs new file mode 100644 index 00000000..bb67fb3c --- /dev/null +++ b/crates/tests/cgp-tests/tests/extensible_records/product_value.rs @@ -0,0 +1,41 @@ +//! The value-level `product!` macro: building a `Cons`/`Nil` value whose type is +//! the matching `Product!`. +//! +//! `product!` parses its items as expressions, not types, so an element may be any +//! expression — a literal, a method call, an arithmetic expression — and not just a +//! path that happens to also parse as a type. This test pins that the value it +//! builds is exactly the nested `Cons(..)`/`Nil` constructor form, that its type is +//! the corresponding `Product!`, and that the empty and trailing-comma forms work. +//! +//! See docs/implementation/entrypoints/product.md and +//! docs/reference/macros/product.md. + +use cgp::prelude::*; + +#[test] +fn test_product_value() { + // Elements are expressions: a literal, a method call, and an arithmetic + // expression — none of which parse as a type. + let row: Product![u32, String, bool] = product![2 + 3, "hi".to_owned(), true]; + + assert_eq!(row, Cons(5, Cons("hi".to_owned(), Cons(true, Nil)))); + + // The nested spine is addressable field-by-field through the tuple-struct + // fields of `Cons`. + assert_eq!(row.0, 5); + assert_eq!(row.1.0, "hi"); + assert!(row.1.1.0); +} + +#[test] +fn test_empty_product_value() { + // An empty `product![]` is `Nil`, the value of the empty `Product![]`. + let empty: Product![] = product![]; + assert_eq!(empty, Nil); +} + +#[test] +fn test_product_value_trailing_comma() { + let row: Product![u32, u32] = product![1, 2,]; + assert_eq!(row, Cons(1, Cons(2, Nil))); +} diff --git a/crates/tests/cgp-tests/tests/generic_components/component_type_param.rs b/crates/tests/cgp-tests/tests/generic_components/component_type_param.rs new file mode 100644 index 00000000..db9d89d4 --- /dev/null +++ b/crates/tests/cgp-tests/tests/generic_components/component_type_param.rs @@ -0,0 +1,154 @@ +//! `#[cgp_component]` on a trait with a single type parameter and no lifetime. +//! +//! The type parameter is appended after `__Context__` in the provider trait, put +//! into the `IsProviderFor` params tuple as a bare `(Shape)`, and appended to the +//! `RedirectLookup` lookup path via `ConcatPath>`. This is the +//! type-parameter-only variant of the macro expansion, distinct from the combined +//! lifetime-and-type case in `component_lifetime`. The provider, `delegate_components!`, +//! and `check_components!` wiring below is written plainly (owned by the +//! `basic_delegation` and `checking` concepts). +//! +//! See docs/implementation/entrypoints/cgp_component.md (Snapshots) for this +//! type-parameter variant, and docs/reference/macros/cgp_component.md for the +//! user-facing semantics. + +use cgp::prelude::*; +use cgp_macro_test_util::snapshot_cgp_component; + +snapshot_cgp_component! { + #[cgp_component(AreaCalculator)] + pub trait CanCalculateArea { + fn calculate_area(&self, shape: &Shape) -> f64; + } + + expand_area_calculator(output) { + insta::assert_snapshot!(output, @" + pub trait CanCalculateArea { + fn calculate_area(&self, shape: &Shape) -> f64; + } + impl<__Context__, Shape> CanCalculateArea for __Context__ + where + __Context__: AreaCalculator<__Context__, Shape>, + { + fn calculate_area(&self, shape: &Shape) -> f64 { + __Context__::calculate_area(self, shape) + } + } + pub trait AreaCalculator< + __Context__, + Shape, + >: IsProviderFor { + fn calculate_area(__context__: &__Context__, shape: &Shape) -> f64; + } + impl<__Provider__, __Context__, Shape> AreaCalculator<__Context__, Shape> + for __Provider__ + where + __Provider__: DelegateComponent + + IsProviderFor, + <__Provider__ as DelegateComponent< + AreaCalculatorComponent, + >>::Delegate: AreaCalculator<__Context__, Shape>, + { + fn calculate_area(__context__: &__Context__, shape: &Shape) -> f64 { + <__Provider__ as DelegateComponent< + AreaCalculatorComponent, + >>::Delegate::calculate_area(__context__, shape) + } + } + pub struct AreaCalculatorComponent; + impl<__Context__, Shape> AreaCalculator<__Context__, Shape> for UseContext + where + __Context__: CanCalculateArea, + { + fn calculate_area(__context__: &__Context__, shape: &Shape) -> f64 { + __Context__::calculate_area(__context__, shape) + } + } + impl<__Context__, Shape> IsProviderFor + for UseContext + where + __Context__: CanCalculateArea, + {} + impl<__Context__, Shape, __Components__, __Path__> AreaCalculator<__Context__, Shape> + for RedirectLookup<__Components__, __Path__> + where + __Path__: ConcatPath>, + __Components__: DelegateComponent< + <__Path__ as ConcatPath>>::Output, + >, + <__Components__ as DelegateComponent< + <__Path__ as ConcatPath>>::Output, + >>::Delegate: AreaCalculator<__Context__, Shape>, + { + fn calculate_area(__context__: &__Context__, shape: &Shape) -> f64 { + <__Components__ as DelegateComponent< + <__Path__ as ConcatPath>>::Output, + >>::Delegate::calculate_area(__context__, shape) + } + } + impl< + __Context__, + Shape, + __Components__, + __Path__, + > IsProviderFor + for RedirectLookup<__Components__, __Path__> + where + __Path__: ConcatPath>, + __Components__: DelegateComponent< + <__Path__ as ConcatPath>>::Output, + >, + <__Components__ as DelegateComponent< + <__Path__ as ConcatPath>>::Output, + >>::Delegate: IsProviderFor + + AreaCalculator<__Context__, Shape>, + {} + ") + } +} + +pub trait HasArea { + fn area(&self) -> f64; +} + +pub struct Rectangle { + pub width: f64, + pub height: f64, +} + +impl HasArea for Rectangle { + fn area(&self) -> f64 { + self.width * self.height + } +} + +// A single provider handles every shape that knows its own area. +#[cgp_new_provider] +impl AreaCalculator for ComputeArea { + fn calculate_area(_context: &Context, shape: &Shape) -> f64 { + shape.area() + } +} + +pub struct MyContext; + +delegate_components! { + MyContext { + AreaCalculatorComponent: ComputeArea, + } +} + +check_components! { + MyContext { + AreaCalculatorComponent: Rectangle, + } +} + +#[test] +fn test_component_with_type_param() { + let rect = Rectangle { + width: 3.0, + height: 4.0, + }; + assert_eq!(MyContext.calculate_area(&rect), 12.0); +} diff --git a/crates/tests/cgp-tests/tests/generic_components/mod.rs b/crates/tests/cgp-tests/tests/generic_components/mod.rs index 6d5ac472..8dbe0995 100644 --- a/crates/tests/cgp-tests/tests/generic_components/mod.rs +++ b/crates/tests/cgp-tests/tests/generic_components/mod.rs @@ -9,8 +9,10 @@ pub mod fn_generic_param; pub mod fn_impl_generics; // Generic-parameter variants of `#[cgp_component]` (this concept owns their -// snapshots): a component with a lifetime and type parameter, and components -// carrying const generics (a plain const and a const of an abstract type). +// snapshots): a component with a single type parameter, one with a lifetime and +// type parameter, and components carrying a const *item* provided by a +// const-generic provider struct (a plain const and a const of an abstract type). pub mod component_const; pub mod component_generic_const; pub mod component_lifetime; +pub mod component_type_param; diff --git a/crates/tests/cgp-tests/tests/getters/abstract_type_use_type.rs b/crates/tests/cgp-tests/tests/getters/abstract_type_use_type.rs index c62c323d..d7db5baa 100644 --- a/crates/tests/cgp-tests/tests/getters/abstract_type_use_type.rs +++ b/crates/tests/cgp-tests/tests/getters/abstract_type_use_type.rs @@ -1,5 +1,5 @@ //! Getters whose return type is an abstract type imported from another component -//! via `#[use_type(HasScalarType::Scalar)]`, so the signatures name it as the +//! via `#[use_type(HasScalarType.Scalar)]`, so the signatures name it as the //! bare `Scalar` while the expansion rewrites it to //! `::Scalar`. Both the auto getter and the full getter //! variant are pinned. The `#[cgp_type]` scaffolding is written plainly here — @@ -18,7 +18,7 @@ pub trait HasScalarType { snapshot_cgp_auto_getter! { #[cgp_auto_getter] - #[use_type(HasScalarType::Scalar)] + #[use_type(HasScalarType.Scalar)] pub trait AutoRectangleFields { fn width(&self) -> Scalar; @@ -81,7 +81,7 @@ snapshot_cgp_auto_getter! { snapshot_cgp_getter! { #[cgp_getter(RectangleFieldsGetter)] - #[use_type(HasScalarType::Scalar)] + #[use_type(HasScalarType.Scalar)] pub trait HasRectangleFields { fn width(&self) -> Scalar; diff --git a/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_multi_and_use_type.rs b/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_multi_and_use_type.rs index d6296d3c..718d582f 100644 --- a/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_multi_and_use_type.rs +++ b/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_multi_and_use_type.rs @@ -1,9 +1,9 @@ -//! `#[cgp_fn]` combining explicit and `#[implicit]` arguments, generic method -//! parameters, `#[async_trait]`, and `#[use_type]` importing abstract types with -//! renaming. +//! `#[cgp_fn]` combining explicit and `#[implicit]` arguments, generic type +//! parameters lifted onto the trait, `#[async_trait]`, and `#[use_type]` +//! importing abstract types with renaming. //! //! The implicit args here have abstract-type values (`FooX`, `Bar`) imported via -//! `#[use_type(>::{Foo as FooX}, …)]`, showing how implicit reads +//! `#[use_type(HasFooType.{Foo as FooX}, …)]`, showing how implicit reads //! and abstract-type imports combine in one function. //! //! See docs/reference/macros/cgp_fn.md and docs/reference/attributes/use_type.md. @@ -27,7 +27,7 @@ snapshot_cgp_fn! { #[cgp_fn] #[allow(unused)] #[async_trait] - #[use_type(>::{Foo as FooX}, >::{Foo as FooY}, HasBarType::{Bar, Baz})] + #[use_type(HasFooType.{Foo as FooX}, HasFooType.{Foo as FooY}, HasBarType.{Bar, Baz})] pub async fn do_foo_bar( &self, x: X, diff --git a/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_mut_self_immutable.rs b/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_mut_self_immutable.rs new file mode 100644 index 00000000..623995f1 --- /dev/null +++ b/crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_mut_self_immutable.rs @@ -0,0 +1,97 @@ +//! `#[cgp_fn]` with a `&mut self` receiver whose implicit arguments are all +//! immutable. +//! +//! The access mode of an implicit argument follows the argument's own type, not +//! the receiver's, so these `&str` reads go through `HasField`/`get_field` even +//! though the receiver is `&mut self` — contrast `cgp_fn_mutable.rs`, where a +//! `&mut` argument reads through `HasFieldMut`/`get_field_mut`. It also shows that +//! any number of immutable implicits may share a `&mut self` receiver (only a +//! `&mut` implicit is restricted to being the sole implicit argument). +//! +//! See docs/reference/macros/cgp_fn.md and docs/reference/attributes/implicit.md. + +use cgp::prelude::*; +use cgp_macro_test_util::snapshot_cgp_fn; + +snapshot_cgp_fn! { + #[cgp_fn] + pub fn format_label(&mut self, #[implicit] prefix: &str, #[implicit] suffix: &str) -> String { + format!("{prefix}-{suffix}") + } + + expand_format_label(output) { + insta::assert_snapshot!(output, @r#" + pub trait FormatLabel { + fn format_label(&mut self) -> String; + } + impl<__Context__> FormatLabel for __Context__ + where + Self: HasField< + Symbol< + 6, + Chars< + 'p', + Chars<'r', Chars<'e', Chars<'f', Chars<'i', Chars<'x', Nil>>>>>, + >, + >, + Value = String, + > + + HasField< + Symbol< + 6, + Chars< + 's', + Chars<'u', Chars<'f', Chars<'f', Chars<'i', Chars<'x', Nil>>>>>, + >, + >, + Value = String, + >, + { + fn format_label(&mut self) -> String { + let prefix: &str = self + .get_field( + ::core::marker::PhantomData::< + Symbol< + 6, + Chars< + 'p', + Chars< + 'r', + Chars<'e', Chars<'f', Chars<'i', Chars<'x', Nil>>>>, + >, + >, + >, + >, + ) + .as_str(); + let suffix: &str = self + .get_field( + ::core::marker::PhantomData::< + Symbol< + 6, + Chars< + 's', + Chars< + 'u', + Chars<'f', Chars<'f', Chars<'i', Chars<'x', Nil>>>>, + >, + >, + >, + >, + ) + .as_str(); + format!("{prefix}-{suffix}") + } + } + "#) + } +} + +#[derive(HasField)] +pub struct Labels { + pub prefix: String, + pub suffix: String, +} + +pub trait CheckLabels: FormatLabel {} +impl CheckLabels for Labels {} diff --git a/crates/tests/cgp-tests/tests/implicit_arguments/mod.rs b/crates/tests/cgp-tests/tests/implicit_arguments/mod.rs index 9d0c75d3..993efd34 100644 --- a/crates/tests/cgp-tests/tests/implicit_arguments/mod.rs +++ b/crates/tests/cgp-tests/tests/implicit_arguments/mod.rs @@ -10,6 +10,7 @@ pub mod cgp_fn_mutable; // `#[implicit]` arguments inside `#[cgp_impl]` providers, and the implicit // context parameter. +pub mod cgp_fn_mut_self_immutable; pub mod cgp_impl_implicit; pub mod cgp_impl_implicit_generic; pub mod implicit_context; diff --git a/crates/tests/cgp-tests/tests/namespaces/multi_param_open.rs b/crates/tests/cgp-tests/tests/namespaces/multi_param_open.rs index 8f8b6c3a..43566d3b 100644 --- a/crates/tests/cgp-tests/tests/namespaces/multi_param_open.rs +++ b/crates/tests/cgp-tests/tests/namespaces/multi_param_open.rs @@ -29,7 +29,7 @@ pub struct AppA; snapshot_delegate_components! { delegate_components! { AppA { - open {FooProviderComponent}; + open FooProviderComponent; @FooProviderComponent.String.u32: DummyFoo, diff --git a/docs/concepts/abstract-types.md b/docs/concepts/abstract-types.md index 4e96d076..82759749 100644 --- a/docs/concepts/abstract-types.md +++ b/docs/concepts/abstract-types.md @@ -55,7 +55,7 @@ pub trait HasScalarType { } #[cgp_component(AreaOfShapeCalculator)] -#[use_type(HasScalarType::Scalar)] +#[use_type(HasScalarType.Scalar)] pub trait CanCalculateAreaOfShape { fn area(&self, shape: &Shape) -> Scalar; } diff --git a/docs/concepts/coherence.md b/docs/concepts/coherence.md index 360a2be8..ba1fa331 100644 --- a/docs/concepts/coherence.md +++ b/docs/concepts/coherence.md @@ -99,14 +99,14 @@ Two applications can thus serialize `Vec` as hexadecimal and as base64 respe ```rust delegate_components! { AppA { - open {ValueSerializerComponent}; + open ValueSerializerComponent; @ValueSerializerComponent.Vec: SerializeHex, } } delegate_components! { AppB { - open {ValueSerializerComponent}; + open ValueSerializerComponent; @ValueSerializerComponent.Vec: SerializeBase64, } } diff --git a/docs/concepts/modern-idioms.md b/docs/concepts/modern-idioms.md index d15e24ae..37593956 100644 --- a/docs/concepts/modern-idioms.md +++ b/docs/concepts/modern-idioms.md @@ -129,7 +129,7 @@ Avoid [`#[cgp_getter]`](../reference/macros/cgp_getter.md) in ordinary code. It ## Import abstract types with `#[use_type]` -Bring an abstract type into a definition with [`#[use_type]`](../reference/attributes/use_type.md) and write it as a bare alias, rather than declaring the owning trait as a supertrait and qualifying every use as `Self::Type`. The attribute does both jobs at once: `#[use_type(HasScalarType::Scalar)]` adds the trait as a supertrait (on a `#[cgp_component]`) or a `where` bound (on a `#[cgp_impl]`/`#[cgp_fn]`), and rewrites each bare `Scalar` to `::Scalar`. This is the preferred form even for the built-in error type: the legacy component definition +Bring an abstract type into a definition with [`#[use_type]`](../reference/attributes/use_type.md) and write it as a bare alias, rather than declaring the owning trait as a supertrait and qualifying every use as `Self::Type`. The attribute does both jobs at once: `#[use_type(HasScalarType.Scalar)]` adds the trait as a supertrait (on a `#[cgp_component]`) or a `where` bound (on a `#[cgp_impl]`/`#[cgp_fn]`), and rewrites each bare `Scalar` to `::Scalar`. This is the preferred form even for the built-in error type: the legacy component definition ```rust #[cgp_component(Loader)] @@ -142,7 +142,7 @@ becomes ```rust #[cgp_component(Loader)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] pub trait CanLoad { fn load(&self, path: &str) -> Result; } @@ -194,7 +194,7 @@ while the modern form dispatches inline with `open`: ```rust delegate_components! { MyApp { - open { AreaCalculatorComponent }; + open AreaCalculatorComponent; @AreaCalculatorComponent.Rectangle: RectangleArea, @AreaCalculatorComponent.Circle: CircleArea, diff --git a/docs/concepts/modular-error-handling.md b/docs/concepts/modular-error-handling.md index 1070a83a..5cc92dc2 100644 --- a/docs/concepts/modular-error-handling.md +++ b/docs/concepts/modular-error-handling.md @@ -23,19 +23,19 @@ Centralizing the error on one trait is what lets errors compose across component ## Raising and wrapping as capabilities -Two further components give the abstract error its behavior, each parameterized so a context can handle many error shapes. [`CanRaiseError`](../reference/components/can_raise_error.md) converts a concrete source error into the context's abstract error, and its companion `CanWrapError` attaches a piece of detail to an existing one. Both import the abstract error with `#[use_type(HasErrorType::Error)]`, which adds the `HasErrorType` supertrait and lets each signature name the error as the bare `Error`, so the error they produce and enrich is the context's shared error type: +Two further components give the abstract error its behavior, each parameterized so a context can handle many error shapes. [`CanRaiseError`](../reference/components/can_raise_error.md) converts a concrete source error into the context's abstract error, and its companion `CanWrapError` attaches a piece of detail to an existing one. Both import the abstract error with `#[use_type(HasErrorType.Error)]`, which adds the `HasErrorType` supertrait and lets each signature name the error as the bare `Error`, so the error they produce and enrich is the context's shared error type: ```rust #[cgp_component(ErrorRaiser)] #[derive_delegate(UseDelegate)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] pub trait CanRaiseError { fn raise_error(error: SourceError) -> Error; } #[cgp_component(ErrorWrapper)] #[derive_delegate(UseDelegate)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] pub trait CanWrapError { fn wrap_error(error: Error, detail: Detail) -> Error; } @@ -79,7 +79,7 @@ The reason `CanRaiseError` and `CanWrapError` are parameterized rather than mono ```rust delegate_components! { App { - open {ErrorRaiserComponent}; + open ErrorRaiserComponent; @ErrorRaiserComponent.String: RaiseFrom, @ErrorRaiserComponent.ParseIntError: DebugError, @@ -95,7 +95,7 @@ Nothing about modular error handling is confined to the built-in components; an ```rust #[cgp_component(HttpErrorRaiser)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] pub trait CanRaiseHttpError { fn raise_http_error(_code: Code, detail: Detail) -> Error; } diff --git a/docs/concepts/modularity-hierarchy.md b/docs/concepts/modularity-hierarchy.md index 61b3ce08..40f26ba3 100644 --- a/docs/concepts/modularity-hierarchy.md +++ b/docs/concepts/modularity-hierarchy.md @@ -97,14 +97,14 @@ Now two application contexts can serialize the *same* type differently, each coh ```rust delegate_components! { AppA { - open {ValueSerializerComponent}; + open ValueSerializerComponent; @ValueSerializerComponent.Vec: SerializeHex, } } delegate_components! { AppB { - open {ValueSerializerComponent}; + open ValueSerializerComponent; @ValueSerializerComponent.Vec: SerializeBase64, } } @@ -137,7 +137,7 @@ With this in hand a context can fix the element encoding for one collection whil ```rust delegate_components! { AppA { - open {ValueSerializerComponent}; + open ValueSerializerComponent; @ValueSerializerComponent.Vec: SerializeBytes, @ValueSerializerComponent.Vec>: SerializeIteratorWith, @ValueSerializerComponent.Vec: SerializeIteratorWith, diff --git a/docs/concepts/namespaces.md b/docs/concepts/namespaces.md index a39e2ea1..738a4686 100644 --- a/docs/concepts/namespaces.md +++ b/docs/concepts/namespaces.md @@ -27,12 +27,12 @@ This says that when `MyNamespace` is asked for `FooProviderComponent`, it should ## Per-component dispatch with `open` -The most common place a path appears is the `open` statement, which uses path-based redirection to dispatch a single component on its generic parameter — inline, in a context's own table. Writing `open { ValueSerializerComponent };` at the head of a [`delegate_components!`](../reference/macros/delegate_components.md) block redirects that component's lookup along a path rooted at the component name into the context's own table; the per-value entries that follow are then ordinary `@`-path keys pointing into that route: +The most common place a path appears is the `open` statement, which uses path-based redirection to dispatch a single component on its generic parameter — inline, in a context's own table. Writing `open ValueSerializerComponent;` at the head of a [`delegate_components!`](../reference/macros/delegate_components.md) block redirects that component's lookup along a path rooted at the component name into the context's own table; the per-value entries that follow are then ordinary `@`-path keys pointing into that route: ```rust delegate_components! { AppA { - open {ValueSerializerComponent}; + open ValueSerializerComponent; @ValueSerializerComponent.Vec: SerializeHex, diff --git a/docs/concepts/send-bounds.md b/docs/concepts/send-bounds.md index 216d02e6..4a5c8744 100644 --- a/docs/concepts/send-bounds.md +++ b/docs/concepts/send-bounds.md @@ -10,7 +10,7 @@ An async CGP method advertises a future whose auto-traits the caller cannot name #[cgp_component(ApiHandler)] #[async_trait] #[derive_delegate(UseDelegate)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] pub trait CanHandleApi { type Request; type Response; diff --git a/docs/concepts/type-level-dsls.md b/docs/concepts/type-level-dsls.md index d118133d..6f4c3d36 100644 --- a/docs/concepts/type-level-dsls.md +++ b/docs/concepts/type-level-dsls.md @@ -29,7 +29,7 @@ The interpreter is a single CGP component whose `Code` type parameter is the pro #[cgp_component(Handler)] #[derive_delegate(UseDelegate)] #[derive_delegate(UseInputDelegate)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] pub trait CanHandle { type Output; @@ -50,7 +50,7 @@ A provider interprets one fragment by pattern-matching on the `Code` parameter t ```rust #[cgp_impl(new HandleStreamChecksum)] #[uses(CanRaiseError)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] impl Handler, Input> where Input: Unpin + TryStream, diff --git a/docs/examples/application-builder.md b/docs/examples/application-builder.md index f183c452..e1ef07f9 100644 --- a/docs/examples/application-builder.md +++ b/docs/examples/application-builder.md @@ -74,7 +74,7 @@ pub struct SqliteClient { #[cgp_impl(new BuildSqliteClient)] #[uses(HasSqliteOptions, CanRaiseError)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] impl Handler { type Output = SqliteClient; @@ -114,7 +114,7 @@ pub struct HttpClient { #[cgp_impl(new BuildHttpClient)] #[uses(HasHttpClientConfig, CanRaiseError)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] impl Handler { type Output = HttpClient; @@ -146,7 +146,7 @@ pub struct OpenAiClient { #[cgp_impl(new BuildOpenAiClient)] #[uses(HasOpenAiConfig)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] impl Handler { type Output = OpenAiClient; @@ -162,7 +162,7 @@ impl Handler { } ``` -Each provider states exactly what it needs from the context through [`#[uses(...)]`](../reference/attributes/uses.md) as an [impl-side dependency](../concepts/impl-side-dependencies.md), and nothing more. A builder whose construction cannot fail — like `BuildOpenAiClient` — only imports the abstract error type with [`#[use_type(HasErrorType::Error)]`](../reference/attributes/use_type.md) so its output type aligns with the others, while a fallible one also lists the `CanRaiseError` it needs. +Each provider states exactly what it needs from the context through [`#[uses(...)]`](../reference/attributes/uses.md) as an [impl-side dependency](../concepts/impl-side-dependencies.md), and nothing more. A builder whose construction cannot fail — like `BuildOpenAiClient` — only imports the abstract error type with [`#[use_type(HasErrorType.Error)]`](../reference/attributes/use_type.md) so its output type aligns with the others, while a fallible one also lists the `CanRaiseError` it needs. ## Merging the outputs into the application @@ -243,7 +243,7 @@ pub struct PostgresClient { #[cgp_impl(new BuildPostgresClient)] #[uses(HasPostgresUrl, CanRaiseError)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] impl Handler { type Output = PostgresClient; diff --git a/docs/examples/modular-serialization.md b/docs/examples/modular-serialization.md index f0cba9ec..5269e92c 100644 --- a/docs/examples/modular-serialization.md +++ b/docs/examples/modular-serialization.md @@ -204,7 +204,7 @@ pub struct AppA; delegate_components! { AppA { - open {ValueSerializerComponent}; + open ValueSerializerComponent; @ValueSerializerComponent.<'a, T> &'a T: SerializeDeref, @@ -239,7 +239,7 @@ pub struct AppB; delegate_components! { AppB { - open {ValueSerializerComponent}; + open ValueSerializerComponent; @ValueSerializerComponent.<'a, T> &'a T: SerializeDeref, @@ -359,7 +359,7 @@ use cgp_error_anyhow::{RaiseAnyhowError, UseAnyhowError}; delegate_components! { <'s> App<'s> { - open {ValueDeserializerComponent}; + open ValueDeserializerComponent; @ValueDeserializerComponent.u64: UseSerde, diff --git a/docs/examples/money-transfer-api.md b/docs/examples/money-transfer-api.md index b5830b6b..5b86bf39 100644 --- a/docs/examples/money-transfer-api.md +++ b/docs/examples/money-transfer-api.md @@ -45,7 +45,7 @@ Every endpoint is one case of a single component that dispatches on a marker typ #[cgp_component(ApiHandler)] #[async_trait] #[derive_delegate(UseDelegate)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] pub trait CanHandleApi { type Request; type Response; @@ -70,7 +70,7 @@ Each endpoint is a provider for `ApiHandler` that depends on business capabiliti ```rust #[cgp_component(MoneyTransferrer)] #[async_trait] -#[use_type(HasUserIdType::UserId, HasCurrencyType::Currency, HasQuantityType::Quantity, HasErrorType::Error)] +#[use_type(HasUserIdType.UserId, HasCurrencyType.Currency, HasQuantityType.Quantity, HasErrorType.Error)] pub trait CanTransferMoney { async fn transfer_money( &self, @@ -83,7 +83,7 @@ pub trait CanTransferMoney { #[cgp_impl(new HandleTransfer)] #[uses(CanTransferMoney, CanRaiseHttpError)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] impl ApiHandler where Request: HasLoggedInUser + HasTransferMoneyFields, @@ -123,7 +123,7 @@ Cross-cutting concerns are handlers that wrap another handler, which makes them ```rust #[cgp_impl(new HandleFromRequest)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] impl ApiHandler where InHandler: ApiHandler, @@ -147,7 +147,7 @@ where ```rust #[cgp_impl(new UseBasicAuth)] #[uses(CanQueryUserHashedPassword, CanCheckPassword)] -#[use_type(HasUserIdType::UserId, HasErrorType::Error)] +#[use_type(HasUserIdType.UserId, HasErrorType.Error)] impl ApiHandler where InHandler: ApiHandler, @@ -181,7 +181,7 @@ where ```rust #[cgp_impl(ResponseToJson)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] impl ApiHandler where InHandler: ApiHandler, @@ -208,7 +208,7 @@ The business capabilities are satisfied by a provider that reads its data from c ```rust #[cgp_auto_getter] -#[use_type(HasUserIdType::UserId, HasCurrencyType::Currency, HasQuantityType::Quantity)] +#[use_type(HasUserIdType.UserId, HasCurrencyType.Currency, HasQuantityType.Quantity)] pub trait HasMockedUserBalances { fn user_balances( &self, @@ -221,7 +221,7 @@ pub trait HasMockedUserBalances { CanRaiseHttpError, CanRaiseHttpError, )] -#[use_type(HasUserIdType::UserId, HasCurrencyType::Currency, HasQuantityType::Quantity, HasErrorType::Error)] +#[use_type(HasUserIdType.UserId, HasCurrencyType.Currency, HasQuantityType.Quantity, HasErrorType.Error)] impl MoneyTransferrer where Quantity: CheckedAdd + CheckedSub, diff --git a/docs/examples/shell-scripting-dsl.md b/docs/examples/shell-scripting-dsl.md index 992f5da6..e13cfb29 100644 --- a/docs/examples/shell-scripting-dsl.md +++ b/docs/examples/shell-scripting-dsl.md @@ -66,7 +66,7 @@ Every step of a program is interpreted by one component: the [`Handler`](../refe #[cgp_component(Handler)] #[derive_delegate(UseDelegate)] #[derive_delegate(UseInputDelegate)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] pub trait CanHandle { type Output; @@ -99,7 +99,7 @@ pub struct Checksum(pub PhantomData); #[cgp_impl(new HandleStreamChecksum)] #[uses(CanRaiseError)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] impl Handler, Input> where Input: Unpin + TryStream, @@ -205,7 +205,7 @@ A language extension adds new syntax and its interpreters without forking the co pub struct BytesToHex; #[cgp_impl(new HandleBytesToHex)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] impl Handler where Input: AsRef<[u8]>, diff --git a/docs/implementation/asts/attributes.md b/docs/implementation/asts/attributes.md index 94c9cc33..11e720ca 100644 --- a/docs/implementation/asts/attributes.md +++ b/docs/implementation/asts/attributes.md @@ -10,14 +10,14 @@ The modifiers do not parse themselves out of the token stream on their own; a ho ## `#[use_type]` -`#[use_type(HasErrorType::Error)]` imports an abstract type: it rewrites the bare alias everywhere and adds the owning trait as a bound. It parses into a `UseTypeAttribute` per spec, collected into a `UseTypeAttributes`. Each spec captures a context type (defaulting to `Self`, or an explicit `@Context::` foreign context), the owning trait path, and one or more type idents (with optional `as` alias and `=` equality). Application is a two-phase transform. First, the `SubstituteAbstractType` visitor rewrites every bare, single-segment, argument-free use of the alias into the fully-qualified associated type: +`#[use_type(HasErrorType.Error)]` imports an abstract type: it rewrites the bare alias everywhere and adds the owning trait as a bound. It parses into a `UseTypeAttribute` per spec, collected into a `UseTypeAttributes`. Each spec captures a context type (defaulting to `Self`, or an explicit `@Context.` foreign context), the owning trait path, and one or more type idents (with optional `as` alias and `=` equality). Both the context and the trait parse as a `PathWithTypeArgs`, and a `.` (not `::`) separates the context, trait, and associated type — so the trait may be a full path or carry generic arguments (`HasFooType.Foo`) with its own `::` staying inside the path, while the `.` unambiguously marks where the associated type begins. Application is a two-phase transform. First, the `SubstituteAbstractType` visitor rewrites every bare, single-segment, argument-free use of the alias into the fully-qualified associated type: ```rust -// #[use_type(HasErrorType::Error)] turns a bare `Error` into: +// #[use_type(HasErrorType.Error)] turns a bare `Error` into: ::Error ``` -Then the host adds the trait: on a `#[cgp_component]` trait, `transform_item_trait` pushes the trait path onto the consumer trait's supertraits (only for `Self`-context specs); on an impl, `transform_item_impl` derives the `where` predicates — `context_type: trait_path` — and extends the impl's `where` clause. The predicate derivation also resolves `as` aliases, `=` equalities, and cross-spec equalities, and rejects two specs sharing one alias. The visitor is applied in reverse spec order so earlier specs can shadow later ones. A `=` equality is rejected outright on a `#[cgp_component]` trait, since a component definition cannot pin an abstract type to a concrete one. The single-identifier-head parse of an `@Context::` prefix is deliberate: it keeps `IdentWithTypeArgs` rather than a greedy path parser, which would otherwise swallow the trailing `::Trait::Type`. +Then the host adds the trait: on a `#[cgp_component]` trait, `transform_item_trait` pushes the trait path onto the consumer trait's supertraits (only for `Self`-context specs); on an impl, `transform_item_impl` derives the `where` predicates — `context_type: trait_path` — and extends the impl's `where` clause. Both transforms first call `forbid_duplicate_aliases`, which rejects any two imports resolving to the same identifier or alias, comparing every pair across all specs and within a single braced list, so the check applies uniformly to components, impls, and functions. The predicate derivation additionally resolves `as` aliases, `=` equalities, and cross-spec equalities. The visitor is applied in reverse spec order so an earlier spec's substitution can rewrite an identifier a later spec's substitution introduced — this is what makes a nested foreign import such as `HasTypes.Types, @Types.HasScalarType.Scalar` compose into `<::Types as HasScalarType>::Scalar`. A `=` equality is rejected outright on a `#[cgp_component]` trait, since a component definition cannot pin an abstract type to a concrete one. ## `#[use_provider]` @@ -73,7 +73,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. -- **`#[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; [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) 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 equality; [use_type_generic_param.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_generic_param.rs) covers a generic-parameter abstract type. +- **`#[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. - **`#[extend_where]`** — [abstract_types/use_type_fn_nested_foreign.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_nested_foreign.rs) exercises it alongside `#[use_type]` on a `#[cgp_fn]`. diff --git a/docs/implementation/asts/cgp_fn.md b/docs/implementation/asts/cgp_fn.md index c9078dd5..88434790 100644 --- a/docs/implementation/asts/cgp_fn.md +++ b/docs/implementation/asts/cgp_fn.md @@ -18,9 +18,9 @@ The ordering here is the contract the snapshots pin: the implicit-argument bound ## Implicit arguments: `ImplicitArgFields` and `ImplicitArgField` -The implicit-argument types are shared building blocks that `#[cgp_fn]` and `#[cgp_impl]` both use, so they live under `types/implicits/` rather than in the `cgp_fn` module. An `ImplicitArgField` records one extracted argument — its field name, the field type to require, the receiver mutability, the field mode (the conversion to apply), and the original argument type — and `ImplicitArgFields` is the collected list. +The implicit-argument types are shared building blocks that `#[cgp_fn]` and `#[cgp_impl]` both use, so they live under `types/implicits/` rather than in the `cgp_fn` module. An `ImplicitArgField` records one extracted argument — its field name, the field type to require, the field mutability (set from the argument's own type: `Some` only for a `&mut T` argument), the field mode (the conversion to apply), and the original argument type — and `ImplicitArgFields` is the collected list. -The extraction is driven by `extract_and_parse_implicit_args`, which pulls every `#[implicit]`-marked argument out of a signature's inputs and parses each into an `ImplicitArgField`. `ImplicitArgField` then contributes in two directions: `to_has_field_bound` produces the `HasField`/`HasFieldMut` bound the impl requires, and `to_statement` produces the `let #name: #arg_type = self.get_field(...) ;` binding that `prepend_to_block` splices onto the front of the body. The conversion is chosen by `parse_field_type`, the same field-mode logic the getter macros use. +The extraction is driven by `extract_and_parse_implicit_args`, which pulls every `#[implicit]`-marked argument out of a signature's inputs, parses each into an `ImplicitArgField`, and rejects the combinations extraction cannot lower (a missing `self` receiver, a `mut` pattern, and a `&mut` implicit sharing the function with any other implicit). `ImplicitArgField` then contributes in two directions: `to_has_field_bound` produces the `HasField`/`HasFieldMut` bound the impl requires, and `to_statement` produces the `let #name: #arg_type = self.get_field(...) ;` binding that `prepend_to_block` splices onto the front of the body. The conversion is chosen by `parse_field_type`, the same field-mode logic the getter macros use; the receiver's mutability enters only to validate that a `&mut T` argument is paired with a `&mut self` receiver. ```rust // for `#[implicit] name: &str` on `&self`, to_statement produces: @@ -31,8 +31,8 @@ let name: &str = self.get_field(PhantomData::).as_str(); ## Tests -- The stage transforms are exercised end-to-end by the expansion snapshots indexed in the [entrypoint document's Snapshots section](../entrypoints/cgp_fn.md); there is no separate parser-rejection test file for `#[cgp_fn]` in `cgp-macro-tests`. -- The `&mut self`-with-multiple-implicits rejection and the mutable-pattern rejection enforced during extraction are currently unpinned by any test. +- The stage transforms are exercised end-to-end by the expansion snapshots indexed in the [entrypoint document's Snapshots section](../entrypoints/cgp_fn.md). +- The rejections enforced during implicit-argument extraction — an implicit argument without a `self` receiver, a `mut` binding pattern on an implicit argument, and a `&mut` implicit combined with any other implicit — are pinned by [parser_rejections/cgp_fn.rs](../../../crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_fn.rs). ## Source diff --git a/docs/implementation/asts/cgp_impl.md b/docs/implementation/asts/cgp_impl.md index d0829ef1..af13f52a 100644 --- a/docs/implementation/asts/cgp_impl.md +++ b/docs/implementation/asts/cgp_impl.md @@ -47,8 +47,8 @@ impl AreaCalculator { provider_trait_path = AreaCalculator Three `VisitMut` passes in [cgp-macro-core/src/visitors/replace_self/](../../../crates/macros/cgp-macro-core/src/visitors/replace_self/) perform the rewrite that turns consumer-style syntax into provider-style, and they run in this order: - **`ReplaceSelfTypeVisitor`** rewrites the `Self` type to the context type and `Self::Foo` paths to `Context::Foo`, but skips any path whose associated type is in the block's local associated-type list, so a trait's own `Self::Output` is left intact. It handles macro bodies at the token level too, since `VisitMut` does not see inside a `macro!( … )`. -- **`ReplaceSelfReceiverVisitor`** rewrites the method receiver into an explicit context parameter, preserving the reference and mutability shape: `&self` becomes `ctx: &Context`, `&mut self` becomes `ctx: &mut Context`, `self` becomes `ctx: Context`, and a lifetime on the receiver is carried onto the parameter type. -- **`ReplaceSelfValueVisitor`** rewrites every `self` *value* expression to the context identifier, again descending into macro bodies at the token level, and stops at nested `fn` items since they do not capture the outer `self`. +- **`ReplaceSelfReceiverVisitor`** rewrites the method receiver into an explicit context parameter, preserving the reference and mutability shape: `&self` becomes `ctx: &Context`, `&mut self` becomes `ctx: &mut Context`, `self` becomes `ctx: Context`, and `mut self` becomes `mut ctx: Context` — the `mut` binds the parameter, so it precedes the identifier rather than the type. A lifetime on the receiver is carried onto the parameter type. +- **`ReplaceSelfValueVisitor`** rewrites every `self` *value* expression to the context identifier, again descending into macro bodies at the token level, and stops at nested `fn` items since they do not capture the outer `self`. Inside a macro body it rewrites only the value form: a `self::` module path is left intact, since a `self` immediately followed by `::` is the current module rather than the receiver. The receiver identifier and the context type these visitors substitute are the ones `to_raw_item_impl` computed. The same visitors are not used by the [`cgp_provider` stack](cgp_provider.md), which already receives provider-form input. diff --git a/docs/implementation/asts/check_components.md b/docs/implementation/asts/check_components.md index d87dcbcc..6ef66339 100644 --- a/docs/implementation/asts/check_components.md +++ b/docs/implementation/asts/check_components.md @@ -6,7 +6,7 @@ These two stacks generate the compile-time wiring checks. The `check_components` `CheckComponentsTables` is the whole `check_components!` body — a `Vec`, parsed by looping until the input is empty, so one invocation can carry several context blocks. `to_items` concatenates each table's items. -`CheckComponentsTable` is one context block: an optional `check_providers` list, an optional leading generic list, the derived-or-overridden check-trait name, the context type, an optional `where` clause, and the `CheckEntries`. Its `Parse` reads the `#[check_trait]` and `#[check_providers]` attributes (rejecting any other), derives the trait name as `__Check{Context}` from the context type's leading identifier when not overridden, and parses the braced entries. Its `eval` builds the check trait once — supertraiting `CanUseComponent` normally, or `IsProviderFor<…, Context, …>` under `#[check_providers]` — then emits one impl per evaluated entry. For the context-checking form it overrides the impl's `Self`-type span with the component's span so an error points at the component; the `#[check_providers]` form instead emits one impl per listed provider. +`CheckComponentsTable` is one context block: an optional `check_providers` list, an optional leading generic list, the derived-or-overridden check-trait name, the context type, an optional `where` clause, and the `CheckEntries`. Its `Parse` reads the `#[check_trait]` and `#[check_providers]` attributes (rejecting any other, a repeat of either, and an empty `#[check_providers()]`), derives the trait name as `__Check{Context}` from the final segment of the context type's path when not overridden — so a path-qualified context is accepted — and parses the braced entries. Its `eval` builds the check trait once — supertraiting `CanUseComponent` normally, or `IsProviderFor<…, Context, …>` under `#[check_providers]` — then emits one impl per evaluated entry. For the context-checking form it overrides the impl's `Self`-type span with the component's span so an error points at the component; the `#[check_providers]` form instead emits one impl per listed provider. ## `CheckEntries` and `CheckEntry` diff --git a/docs/implementation/asts/delegate_component.md b/docs/implementation/asts/delegate_component.md index fca676e1..aaf51d65 100644 --- a/docs/implementation/asts/delegate_component.md +++ b/docs/implementation/asts/delegate_component.md @@ -20,7 +20,7 @@ The `delegate_component` stack is the set of AST types that `delegate_components `DelegateStatement` is the leading statement form, an enum over three variants that all lower through `eval_entries` into the same flat entry list: -- **`OpenDelegateStatement`** (`open { A, B };`) — opens each listed component for per-value wiring. For each component it emits an entry whose key is the component and whose value is `RedirectLookup>`, rooting a redirect at the component name in the context's own table. The `@Component.Key` mappings that follow store providers under the extended path. +- **`OpenDelegateStatement`** (`open { A, B };`, or `open A;` for a single component) — opens each listed component for per-value wiring. Its parser peeks for a brace: a `{ … }` list parses one or more comma-separated components, while the braceless form parses exactly one component type, so opening several at once still requires the braces. For each component it emits an entry whose key is the component and whose value is `RedirectLookup>`, rooting a redirect at the component name in the context's own table. The `@Component.Key` mappings that follow store providers under the extended path. - **`NamespaceDelegateStatement`** (`namespace SomeNamespace;`) — forwards every lookup through a namespace trait. It lowers via the shared "for-entry" path to a blanket `DelegateComponent<__Key__>` impl bounded on `__Key__: SomeNamespace`, so any key the namespace defines is inherited. - **`ForDelegateStatement`** (`for in SomeTable where … { … }`) — a loop that pulls mappings out of another lookup table. Each inner mapping produces a for-entry whose namespace-trait bound is reconstructed with the table type and a `Delegate = value` binding appended to the namespace path's arguments. @@ -56,7 +56,7 @@ Only Normal and Direct mappings can carry a nested inner table (their values are ## Tests -- The stack is exercised end-to-end by the expansion snapshots and behavioral tests indexed in the [entrypoint document](../entrypoints/delegate_components.md); there are no `cgp-macro-tests` failure cases for the delegate family. +- The stack is exercised end-to-end by the expansion snapshots and behavioral tests indexed in the [entrypoint document](../entrypoints/delegate_components.md), and its attribute rejection — including the recursion through mapping values into inner tables — is pinned by the failure cases in [parser_rejections/delegate_components.rs](../../../crates/tests/cgp-macro-tests/tests/parser_rejections/delegate_components.rs). ## Source diff --git a/docs/implementation/asts/product.md b/docs/implementation/asts/product.md index 4614446b..def54c3e 100644 --- a/docs/implementation/asts/product.md +++ b/docs/implementation/asts/product.md @@ -1,6 +1,6 @@ # The `product` AST stack -The `product` stack is the pair of AST types behind `Product!` and `product!`: `ProductType` for the type-level macro and `ProductExpr` for the value-level one. Both parse a comma-separated list into a `Punctuated` and fold it into a `Cons`/`Nil` chain, differing only in whether they emit the type form `Cons<…>` or the constructor form `Cons(…)`. The [entrypoint document](../entrypoints/product.md) covers what the macros produce; this document covers the types. +The `product` stack is the pair of AST types behind `Product!` and `product!`: `ProductType` for the type-level macro and `ProductExpr` for the value-level one. Both parse a comma-separated list and fold it into a `Cons`/`Nil` chain, but they parse and emit at different levels: `ProductType` reads a list of types and folds it into the type `Cons<…>`, while `ProductExpr` reads a list of expressions and folds it into the value `Cons(…)`. The [entrypoint document](../entrypoints/product.md) covers what the macros produce; this document covers the types. ## `ProductType` @@ -17,19 +17,20 @@ Returning a validated `syn::Type` rather than a raw token stream is what lets th ## `ProductExpr` -`ProductExpr` is the value-level form and is structurally identical: it too holds a `Punctuated` and parses with `parse_terminated`. The one difference is in `eval`, which wraps each element in the tuple-struct constructor form `Cons(ty, acc)` rather than the type form, folding onto `Nil` the same way: +`ProductExpr` is the value-level form. It holds the parsed element list as `exprs: Punctuated` and parses with `parse_terminated`, so each element is a full Rust expression — a literal, a method call, an arithmetic expression — not merely a path that also happens to parse as a type. Its `eval` folds the elements right-to-left onto `Nil` with the tuple-struct constructor form `Cons(expr, acc)`, then re-parses the accumulated tokens into a `syn::Expr` through [`parse_internal!`](../macros/parse_internal.md): ```rust // product![a, b, c] evals to Cons(a, Cons(b, Cons(c, Nil))) ``` -Note that its field is still named and typed as a list of `Type`, not `Expr` — the value macro reuses the type parser and only changes the emission shape, so the two forms cannot diverge in how they read their input. +Parsing the items as `Expr` and re-parsing the fold as `Expr` is what keeps `product!` in expression position: the macro emits a value, so its output is dropped into an expression context, exactly as `ProductType`'s `syn::Type` output is dropped into a type context. ## Tests - The `Cons`/`Nil` field spine is pinned as embedded output by the record derive snapshots ([extensible_records/person_record.rs](../../../crates/tests/cgp-tests/tests/extensible_records/person_record.rs), [extensible_records/record_derive.rs](../../../crates/tests/cgp-tests/tests/extensible_records/record_derive.rs)), which emit a `Product!` of `Field` entries. - [handlers/pipe_handlers.rs](../../../crates/tests/cgp-tests/tests/handlers/pipe_handlers.rs) exercises the type-level `Product![…]` as a list of provider types in a handler pipeline. +- [extensible_records/product_value.rs](../../../crates/tests/cgp-tests/tests/extensible_records/product_value.rs) exercises the value-level `product!`: that expression items (a method call, an arithmetic expression) build the nested `Cons(..)`/`Nil` value, that the value's type is the matching `Product!`, and that the empty and trailing-comma forms work. ## Source diff --git a/docs/implementation/entrypoints/cgp_component.md b/docs/implementation/entrypoints/cgp_component.md index a4dfc8be..7009aaf3 100644 --- a/docs/implementation/entrypoints/cgp_component.md +++ b/docs/implementation/entrypoints/cgp_component.md @@ -58,11 +58,9 @@ A **default method body** is preserved into the provider trait, because the prov **Generic parameters** on the component are appended after the context in the provider trait and collected into the `IsProviderFor` params tuple. A lifetime stays ahead of the context (lifetimes must precede type parameters) and is lifted into `Life<'a>` in that tuple, so `HasReference<'a, T>` yields `IsProviderFor<…, (Life<'a>, T)>`. Only *type* parameters, not lifetimes or const parameters, extend the `RedirectLookup` lookup path. -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. - -## Known issues +A **const generic parameter** on the component is rejected with a spanned `syn::Error` while building the params tuple, since a const value has no representation in a tuple of types and cannot key CGP's type-based wiring (see [parse_is_provider_params](../functions/parse/is_provider_params.md)). This is the idiomatic way to reject it rather than emitting a provider trait that uses the const parameter in type position and fails to compile. A const *item* on the trait (`const CONSTANT: u64;`) is unaffected — that is an associated const, not a generic parameter, and is provided by a const-generic provider struct. -A const generic parameter on the component causes a **panic** rather than a clean error: building the params tuple hits an `unimplemented!` for const parameters (see [parse_is_provider_params](../functions/parse/is_provider_params.md)). Applying `#[cgp_component]` to a trait with a const generic therefore aborts expansion with a panic instead of a spanned `syn::Error`; the correct behavior would be a clean rejection or genuine const support. Because the macro produces no output, this case has no expansion snapshot and is a candidate failure case for `cgp-macro-tests`. +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. ## Snapshots @@ -70,18 +68,20 @@ Every `snapshot_cgp_component!` invocation across the suite is indexed here, sin - [basic_delegation/component_macro.rs](../../../crates/tests/cgp-tests/tests/basic_delegation/component_macro.rs) — the canonical plain expansion (one method, no parameters). - [basic_delegation/default_methods.rs](../../../crates/tests/cgp-tests/tests/basic_delegation/default_methods.rs) — a supertrait lowered to a context `where`-bound plus a default method body copied into the provider trait. +- [generic_components/component_type_param.rs](../../../crates/tests/cgp-tests/tests/generic_components/component_type_param.rs) — a single type parameter, appended after `__Context__`, placed in the params tuple as `(Shape)`, and extending the `RedirectLookup` path via `ConcatPath>`, with no lifetime present. - [generic_components/component_lifetime.rs](../../../crates/tests/cgp-tests/tests/generic_components/component_lifetime.rs) — a lifetime kept ahead of `__Context__`, lifted to `Life<'a>`, with a type parameter extending the `RedirectLookup` path via `ConcatPath`. - [namespaces/namespace_basic.rs](../../../crates/tests/cgp-tests/tests/namespaces/namespace_basic.rs), [namespaces/namespace_symbol_path.rs](../../../crates/tests/cgp-tests/tests/namespaces/namespace_symbol_path.rs), [namespaces/namespace_type_path.rs](../../../crates/tests/cgp-tests/tests/namespaces/namespace_type_path.rs), [namespaces/namespace_multi.rs](../../../crates/tests/cgp-tests/tests/namespaces/namespace_multi.rs), [namespaces/redirect_lookup.rs](../../../crates/tests/cgp-tests/tests/namespaces/redirect_lookup.rs), [namespaces/default_impls.rs](../../../crates/tests/cgp-tests/tests/namespaces/default_impls.rs), [namespaces/prefix_default_namespace.rs](../../../crates/tests/cgp-tests/tests/namespaces/prefix_default_namespace.rs) — the namespace and prefix-impl variants. -Two variants have no snapshot yet: the `UseDelegate` impl a `#[derive_delegate]` attribute adds to a bare component (exercised through the error and handler families instead), and a component carrying a type parameter but no lifetime, distinct from the combined lifetime-and-type case above. +One variant has no snapshot yet: the `UseDelegate` impl a `#[derive_delegate]` attribute adds to a bare component, exercised through the error and handler families instead. ## Tests The behavioral tests confirm the generated wiring works: - [basic_delegation/default_methods.rs](../../../crates/tests/cgp-tests/tests/basic_delegation/default_methods.rs) checks at run time that an empty provider impl inherits the default bodies and `App.greet()` returns the expected string. +- [generic_components/component_type_param.rs](../../../crates/tests/cgp-tests/tests/generic_components/component_type_param.rs) wires a single-type-parameter component to one provider, passes `check_components!` for a concrete type argument, and computes an area at run time. - [generic_components/component_lifetime.rs](../../../crates/tests/cgp-tests/tests/generic_components/component_lifetime.rs) wires the lifetime-carrying component and passes `check_components!`. -- [cgp-macro-tests/tests/parser_rejections/cgp_component.rs](../../../crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_component.rs) asserts the macro rejects a non-trait item at parse time. +- [cgp-macro-tests/tests/parser_rejections/cgp_component.rs](../../../crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_component.rs) asserts the macro rejects a non-trait item and a trait carrying a const generic parameter. ## Source diff --git a/docs/implementation/entrypoints/cgp_fn.md b/docs/implementation/entrypoints/cgp_fn.md index 870a9b33..dd29e223 100644 --- a/docs/implementation/entrypoints/cgp_fn.md +++ b/docs/implementation/entrypoints/cgp_fn.md @@ -50,7 +50,7 @@ where } ``` -The conversion applied to each binding is chosen by the argument's type, following the same field-mode rules the getter macros use: a `&str` argument reads a `String` field and appends `.as_str()`, an owned value appends `.clone()`, an `Option<&T>` reads an `Option` field and appends `.as_ref()`, an `&[T]` reads an `AsRef<[T]>` field and appends `.as_ref()`, and a plain `&T` is taken by reference with no conversion. A `&mut self` receiver switches the reads to `HasFieldMut`/`get_field_mut`. These modes are shared with `#[cgp_auto_getter]` and `#[cgp_getter]` through the [field-parsing helpers](../asts/cgp_getter.md); the difference is only where the read lands — a prepended `let` in the body here, a getter-method body there. +The conversion applied to each binding is chosen by the argument's type, following the same field-mode rules the getter macros use: a `&str` argument reads a `String` field and appends `.as_str()`, an owned value appends `.clone()`, an `Option<&T>` reads an `Option` field and appends `.as_ref()`, an `&[T]` reads an `AsRef<[T]>` field and appends `.as_ref()`, and a plain `&T` is taken by reference with no conversion. A `&mut T` argument reads through `HasFieldMut`/`get_field_mut` and requires a `&mut self` receiver; every immutable argument reads through `HasField`/`get_field` regardless of the receiver. These modes are shared with `#[cgp_auto_getter]` and `#[cgp_getter]` through the [field-parsing helpers](../asts/cgp_getter.md); the difference is only where the read lands — a prepended `let` in the body here, a getter-method body there. ## Behavior and corner cases @@ -60,7 +60,7 @@ The conversion applied to each binding is chosen by the argument's type, followi **The visibility is moved, not copied.** `preprocess` takes the function's visibility off the inner `ItemFn` and re-applies it to the generated trait, so the emitted method inside the trait is always inherited-visibility while the trait itself carries the `pub` the user wrote. -**A `&mut self` receiver constrains the implicit arguments.** At most one mutable implicit argument is allowed when the receiver is `&mut self`, and a mutable field reference requires the `&mut self` receiver; a mutable *pattern* on an implicit argument is rejected outright. These checks are enforced during implicit-argument extraction. +**A `&mut` implicit argument must be the only implicit argument.** The `field_mut` of each argument follows the argument's own type — set from the `&mut` in `&mut T`, not from the receiver — so a `&mut T` argument reads through `get_field_mut` while every immutable argument reads through `get_field`, even under a `&mut self` receiver. Because a `get_field_mut` read borrows the whole context exclusively for the rest of the body, a `&mut` implicit cannot coexist with any other implicit read; extraction rejects the combination (`has_mutable && count > 1`) rather than emit a blanket impl that fails to borrow-check. Any number of purely immutable implicits, by contrast, are shared borrows and combine freely. A `&mut T` argument additionally requires the `&mut self` receiver, and a `mut` *pattern* on any implicit argument is rejected outright. These checks are enforced during implicit-argument extraction. ## Known issues @@ -73,8 +73,9 @@ Every `snapshot_cgp_fn!` invocation across the suite is indexed here, since thes - [implicit_arguments/cgp_fn_greet.rs](../../../crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_greet.rs) — the canonical plain case: one `#[implicit]` `&str` argument dropped from the signature and read via `HasField` with `.as_str()` applied. - [implicit_arguments/cgp_fn_custom_trait_name.rs](../../../crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_custom_trait_name.rs) — `#[cgp_fn(CanCalculateRectangleArea)]` overrides the generated trait name; two owned `f64` implicits each `.clone()`d. - [implicit_arguments/cgp_fn_mutable.rs](../../../crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_mutable.rs) — `&mut self` with a mutable implicit argument, reading through `HasFieldMut`/`get_field_mut`. +- [implicit_arguments/cgp_fn_mut_self_immutable.rs](../../../crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_mut_self_immutable.rs) — a `&mut self` receiver with two *immutable* implicit arguments, showing they read through `HasField`/`get_field` (the access mode follows the argument, not the receiver) and that several immutable implicits may share a `&mut self` receiver. - [implicit_arguments/cgp_fn_calling_fn.rs](../../../crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_calling_fn.rs) — one `#[cgp_fn]` capability depending on another through an explicit `where Self:` bound. -- [implicit_arguments/cgp_fn_multi_and_use_type.rs](../../../crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_multi_and_use_type.rs) — explicit and implicit arguments mixed, generic method parameters, `#[async_trait]` preserved as a raw attribute, and `#[use_type]` importing and renaming abstract types. +- [implicit_arguments/cgp_fn_multi_and_use_type.rs](../../../crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_multi_and_use_type.rs) — explicit and implicit arguments mixed, generic type parameters lifted onto the trait, `#[async_trait]` preserved as a raw attribute, and `#[use_type]` importing and renaming abstract types. - [async_and_send/cgp_fn_async.rs](../../../crates/tests/cgp-tests/tests/async_and_send/cgp_fn_async.rs) — the canonical async expansion, an `async fn` combined with `#[async_trait]`. - [generic_components/fn_generic_param.rs](../../../crates/tests/cgp-tests/tests/generic_components/fn_generic_param.rs) — a function generic over a type parameter, showing the parameter moved onto both trait and impl. - [generic_components/fn_impl_generics.rs](../../../crates/tests/cgp-tests/tests/generic_components/fn_impl_generics.rs) — `#[impl_generics(...)]` adding a generic parameter to the impl only, not the trait. @@ -93,6 +94,10 @@ Because `#[cgp_fn]` emits a blanket impl, its snapshot tests double as behaviora - [implicit_arguments/cgp_fn_calling_fn.rs](../../../crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_calling_fn.rs) confirms one capability calling another resolves through both blanket impls at run time. - [impl_side_dependencies/fn_uses.rs](../../../crates/tests/cgp-tests/tests/impl_side_dependencies/fn_uses.rs) and [impl_side_dependencies/fn_extend.rs](../../../crates/tests/cgp-tests/tests/impl_side_dependencies/fn_extend.rs) exercise the two ways of stating a dependency and confirm the resulting bounds are satisfiable. +The failure cases pin the inputs `#[cgp_fn]` refuses during expansion, each asserting the entrypoint returns `Err`: + +- [parser_rejections/cgp_fn.rs](../../../crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_fn.rs) covers an implicit argument on a function with no `self` receiver, a `mut` binding pattern on an implicit argument, and a `&mut` implicit argument that is not the sole implicit (which would borrow the context mutably and again at once). + ## Source - Entry point: `cgp_fn` in [cgp-macro-lib/src/cgp_fn.rs](../../../crates/macros/cgp-macro-lib/src/cgp_fn.rs). diff --git a/docs/implementation/entrypoints/cgp_impl.md b/docs/implementation/entrypoints/cgp_impl.md index 943feb07..178d975e 100644 --- a/docs/implementation/entrypoints/cgp_impl.md +++ b/docs/implementation/entrypoints/cgp_impl.md @@ -46,6 +46,8 @@ When the `for Context` clause is omitted, the inserted parameter is `__Context__ The **receiver identifier** is computed from the context type: if the context type is a bare identifier it is snake-cased and the result used as the parameter name, and any context type that is not a plain identifier falls back to the literal `__context__`. `Context` and `__Context__` both yield `__context__`. Every `self` in a body is rewritten to that identifier, every `Self` type to the context type, via the three `replace_self` visitors run in sequence. +Inside a **macro body** the rewrite is token-level, because `VisitMut` cannot see through a `macro!( … )`. The type visitor still skips a local associated type (`Self::Output` where `Output` is declared in the block), and the value visitor still distinguishes the two meanings of `self`: a bare `self` value becomes the context, but a `self::` module path is left intact, since a value `self` is never followed by `::`. The token-level pass cannot reason about scope, so a nested `fn` written *inside* a macro invocation does not stop the `self` rewrite the way a nested `fn` at the AST level does. + A **`for Context` clause is optional**, and omitting it is the idiomatic form. When present, the `Self` type of the block *is* the context and the trait path is the provider trait; when absent, `ItemCgpImpl::lower` treats the block's `Self` type as the provider trait path and inserts `__Context__` at the front of the impl generics. **Local associated types are preserved by name.** A `type Output = …` the block declares itself is collected before the rewrite so the `replace_self` type visitor leaves `Self::Output` alone rather than rewriting it to the context — only imported abstract types (`Self::Error` from `#[use_type]`) and receiver `self`/`Self` are rewritten. @@ -56,7 +58,7 @@ The **`#[cgp_impl(Self)]` passthrough** bypasses the whole rewrite: when the pro ## Known issues -The `#[cgp_impl(Self)]` form requires an explicit `for Context` clause and errors cleanly when it is missing; there are no other known limitations specific to this macro beyond those inherited from [`#[cgp_provider]`](cgp_provider.md) (see its Known issues). +The `#[cgp_impl(Self)]` form requires an explicit `for Context` clause and errors cleanly when it is missing. In this form the `new` keyword and the `: ComponentType` override are silently ignored rather than rejected: the `Bare` branch returns the `impl` block untouched and never consults `args.new` or `args.component_type`, so `#[cgp_impl(new Self)]` and `#[cgp_impl(Self: SomeComponent)]` parse and compile but have the same effect as a plain `#[cgp_impl(Self)]`. This is harmless — neither option is meaningful for a direct consumer-trait impl — but a stricter parser would reject them. Beyond this, the macro has no known limitations specific to it apart from those inherited from [`#[cgp_provider]`](cgp_provider.md) (see its Known issues). ## Snapshots @@ -76,6 +78,8 @@ The behavioral tests confirm the lowered wiring works: - [basic_delegation/provider_macro.rs](../../../crates/tests/cgp-tests/tests/basic_delegation/provider_macro.rs) wires `FooProviderComponent` to the generated `ValueToString` and checks the call resolves at run time. - [implicit_arguments/cgp_impl_implicit.rs](../../../crates/tests/cgp-tests/tests/implicit_arguments/cgp_impl_implicit.rs) wires the implicit-argument provider through `delegate_and_check_components!` and confirms the field reads compute the area. - [higher_order_providers/use_provider_impl.rs](../../../crates/tests/cgp-tests/tests/higher_order_providers/use_provider_impl.rs) wires the scaled higher-order provider onto a context and runs it. +- [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). ## Source diff --git a/docs/implementation/entrypoints/check_components.md b/docs/implementation/entrypoints/check_components.md index a947cdc0..fbb5b985 100644 --- a/docs/implementation/entrypoints/check_components.md +++ b/docs/implementation/entrypoints/check_components.md @@ -12,7 +12,7 @@ let items = tables.to_items()?; Ok(quote! { #( #items )* }) ``` -All real logic lives in `cgp-macro-core`. A malformed table fails while parsing, and an unknown table-level attribute (anything other than `#[check_trait]` or `#[check_providers]`) fails with a spanned error during parsing. +All real logic lives in `cgp-macro-core`. A malformed table fails while parsing. Attribute validation also happens during parsing: an unknown table-level attribute (anything other than `#[check_trait]` or `#[check_providers]`) fails with a spanned error, as do a repeated `#[check_trait]`, a repeated `#[check_providers]`, and an empty `#[check_providers()]` (which would otherwise emit a check trait with no impls that verifies nothing). ## Pipeline @@ -44,6 +44,8 @@ A component with parameters places them in the `__Params__` slot — a single pa **A component with no value** emits a single unit-params entry; a bracketed value that is empty is treated the same way. +**The check trait name is derived from the context type's final path segment.** `derive_check_trait_ident` parses the context type through `PathWithTypeArgs` and prepends `__Check` to the last segment's identifier, so a path-qualified context such as `some_mod::Context` yields `__CheckContext` and is accepted rather than rejected — matching `delegate_components!`, which uses the context type verbatim. + ## Snapshots Every `snapshot_check_components!` invocation across the suite is indexed here, since these snapshots belong to this entrypoint: @@ -51,6 +53,7 @@ Every `snapshot_check_components!` invocation across the suite is indexed here, - [checking/check_trait.rs](../../../crates/tests/cgp-tests/tests/checking/check_trait.rs) — the standalone check form: multiple check blocks in one invocation, each renamed with `#[check_trait(...)]`, per-entry parameter lists for generic-parameter components, and an array key checked against a parameter list. - [checking/check_generic.rs](../../../crates/tests/cgp-tests/tests/checking/check_generic.rs) — a generic context (`<'a, I>` plus `where I: Clone`) whose generics and clause are carried onto each impl, a check parameter that uses a generic (`Component: &'a I`), and a component that is itself generic (`BarGetterAtComponent`). - [checking/check_providers.rs](../../../crates/tests/cgp-tests/tests/checking/check_providers.rs) — the `#[check_providers(...)]` form: the trait supertraits `IsProviderFor` and is implemented for each listed provider rather than for the context. +- [checking/check_path_context.rs](../../../crates/tests/cgp-tests/tests/checking/check_path_context.rs) — a path-qualified context (`inner::Context`): the derived trait name (`__CheckContext`) comes from the final path segment, and the impl targets the context by its full path. No snapshot pins the plainest single-block, single-bare-component case on its own; it is covered implicitly by the richer `check_trait` block above. @@ -59,7 +62,7 @@ No snapshot pins the plainest single-block, single-bare-component case on its ow The behavioral coverage for `check_components!` is the compile-time assertion itself: - The files listed under Snapshots are compile-only tests, so a successful build is the passing check. Each pins both the expansion (via the snapshot) and the fact that the asserted wiring resolves. -- There are no `cgp-macro-tests` failure cases for the check family. +- [parser_rejections/check_components.rs](../../../crates/tests/cgp-macro-tests/tests/parser_rejections/check_components.rs) — the table-level attribute rejections: an empty `#[check_providers()]`, a repeated `#[check_providers]`, a repeated `#[check_trait]`, and an unknown attribute each fail to parse. ## Source diff --git a/docs/implementation/entrypoints/delegate_components.md b/docs/implementation/entrypoints/delegate_components.md index 87127666..cf6a7528 100644 --- a/docs/implementation/entrypoints/delegate_components.md +++ b/docs/implementation/entrypoints/delegate_components.md @@ -13,7 +13,7 @@ let evaluated_table = table.eval()?; Ok(evaluated_table.to_token_stream()) ``` -All real logic lives in `cgp-macro-core`. A malformed body fails while parsing `DelegateTable`, and an attribute on the table or any key fails in `validate_attributes` with a spanned "unsupported attribute" error rather than being silently dropped. +All real logic lives in `cgp-macro-core`. A malformed body fails while parsing `DelegateTable`, and an attribute on the table or any key fails in `validate_attributes` with a spanned "unsupported attribute" error rather than being silently dropped. The check recurses through mapping values, so an attribute on a key nested inside a `UseDelegate` table is rejected too, not just one on a top-level key. ## Pipeline @@ -40,7 +40,7 @@ Both `__Context__` and `__Params__` are the reserved identifiers that appear lit The `open` header and `@Component.Key` entries lower through the [`RedirectLookup`](cgp_component.md) impl that every `#[cgp_component]` already generates. The header wires each opened component to a redirect rooted at the component name in the context's own table, and each `@`-path entry stores its provider under the extended path key: ```rust -// open { AreaCalculatorComponent }; → the redirect entry +// open AreaCalculatorComponent; → the redirect entry impl DelegateComponent for MyApp { type Delegate = RedirectLookup>; } @@ -60,7 +60,9 @@ An **`@`-path key** carries a leading `__Wildcard__` generic and lowers the path ## Known issues -The macro's parser is permissive about the body shape and surfaces most mistakes as generic `syn` parse errors rather than tailored diagnostics — for example, an `open` header written after a plain mapping fails to parse because statements must lead the block, but the error points at the unexpected token rather than explaining the ordering rule. There is no failure-case coverage for the delegate family in `cgp-macro-tests`. +The macro's parser is permissive about the body shape and surfaces most mistakes as generic `syn` parse errors rather than tailored diagnostics — for example, an `open` header written after a plain mapping fails to parse because statements must lead the block, but the error (`expected `:``) points at the unexpected token rather than explaining the ordering rule. + +A duplicate key — the same component mapped twice, whether by two plain entries or by an `open` header colliding with an explicit mapping — is not caught by the macro; it emits two conflicting `DelegateComponent` impls and surfaces as a coherence error (`E0119`) at compile time, the same as two hand-written impls would. ## Snapshots @@ -72,8 +74,8 @@ Every `snapshot_delegate_components!` invocation across the suite is indexed her The namespace snapshots pin the statement and `@`-path forms: -- [namespaces/open_dispatch.rs](../../../crates/tests/cgp-tests/tests/namespaces/open_dispatch.rs) — the `open { … }` header plus `@Component.Key` per-value entries, including a brace group sharing one provider across several keys. -- [namespaces/multi_param_open.rs](../../../crates/tests/cgp-tests/tests/namespaces/multi_param_open.rs) — an `open` dispatch on a multi-segment `@Component.A.B` path, one segment carrying an entry generic. +- [namespaces/open_dispatch.rs](../../../crates/tests/cgp-tests/tests/namespaces/open_dispatch.rs) — the braced `open { A, B }` header opening two components at once, plus `@Component.Key` per-value entries, including a brace group sharing one provider across several keys. +- [namespaces/multi_param_open.rs](../../../crates/tests/cgp-tests/tests/namespaces/multi_param_open.rs) — the braceless single-component `open Component;` form, dispatched on a multi-segment `@Component.A.B` path with one segment carrying an entry generic. - [namespaces/namespace_basic.rs](../../../crates/tests/cgp-tests/tests/namespaces/namespace_basic.rs), [namespaces/namespace_symbol_path.rs](../../../crates/tests/cgp-tests/tests/namespaces/namespace_symbol_path.rs), [namespaces/namespace_type_path.rs](../../../crates/tests/cgp-tests/tests/namespaces/namespace_type_path.rs) — the `namespace …;` header forwarding every lookup through a namespace trait, with bare, symbol-path, and type-path `@`-keys. - [namespaces/namespace_multi.rs](../../../crates/tests/cgp-tests/tests/namespaces/namespace_multi.rs), [namespaces/namespace_group.rs](../../../crates/tests/cgp-tests/tests/namespaces/namespace_group.rs) — brace-group and array-group `@`-keys expanding to the cartesian product of segments. - [namespaces/multi_param_namespace.rs](../../../crates/tests/cgp-tests/tests/namespaces/multi_param_namespace.rs) — multi-segment namespace paths with a per-segment generic. @@ -96,6 +98,10 @@ The behavioral tests confirm the generated wiring resolves and compiles: - [basic_delegation/delegate_generic_nested_value.rs](../../../crates/tests/cgp-tests/tests/basic_delegation/delegate_generic_nested_value.rs) checks a per-entry `` list threads through both the outer key and the inner generated table struct. - [basic_delegation/consumer_delegate_getter.rs](../../../crates/tests/cgp-tests/tests/basic_delegation/consumer_delegate_getter.rs) and [basic_delegation/consumer_delegate_generic.rs](../../../crates/tests/cgp-tests/tests/basic_delegation/consumer_delegate_generic.rs) check that a context may satisfy some components by wiring and others by a direct trait impl, and that a generic component resolves independently per type argument. +The failure cases in `cgp-macro-tests` pin the attribute rejection: + +- [parser_rejections/delegate_components.rs](../../../crates/tests/cgp-macro-tests/tests/parser_rejections/delegate_components.rs) asserts the macro rejects an attribute on the table, on a key, and on a key nested inside a `UseDelegate` value (the last confirms the validator recurses through mapping values rather than dropping the attribute), and that a braceless `open` header listing more than one component is rejected (the braceless form opens exactly one). + ## Source - Entry point: `delegate_components` in [cgp-macro-lib/src/delegate_components.rs](../../../crates/macros/cgp-macro-lib/src/delegate_components.rs). diff --git a/docs/implementation/entrypoints/product.md b/docs/implementation/entrypoints/product.md index fe961ed6..5c57b9e5 100644 --- a/docs/implementation/entrypoints/product.md +++ b/docs/implementation/entrypoints/product.md @@ -20,7 +20,7 @@ Both parse the body as a comma-separated list, so a malformed element fails at ` ## Pipeline -Each macro is a parse-then-`eval` step with no further stages. The `Parse` impl reads a `Punctuated`, and `eval` folds it into the chain and re-parses the result through [`parse_internal!`](../macros/parse_internal.md) so the emitted tokens are validated as a `syn::Type`. The [`product` AST document](../asts/product.md) covers both types. +Each macro is a parse-then-`eval` step with no further stages. `Product!`'s `Parse` impl reads a `Punctuated` and `product!`'s reads a `Punctuated`; each `eval` folds its list into the chain and re-parses the result through [`parse_internal!`](../macros/parse_internal.md), validating `Product!`'s output as a `syn::Type` and `product!`'s as a `syn::Expr`. The [`product` AST document](../asts/product.md) covers both types. ## Generated items @@ -31,7 +31,7 @@ Each macro is a parse-then-`eval` step with no further stages. The `Parse` impl Cons>> ``` -`product!` emits a value of that type using the `Cons` tuple-struct constructor rather than the type form, so the value's type is exactly what `Product!` builds over the same elements: +`product!` emits a value of that type using the `Cons` tuple-struct constructor rather than the type form, so the value's type is exactly what `Product!` builds over the corresponding element types: ```rust // product![a, b, c] @@ -42,14 +42,15 @@ The chain is built by folding right-to-left onto `Nil`, so an empty `Product![]` ## Behavior and corner cases -The parser is deliberately permissive: `product!` parses its elements as `Type`s exactly as `Product!` does, not as `Expr`s, and only the emission differs (`Cons<…>` versus `Cons(…)`). A trailing comma is accepted on both because the list is parsed with `parse_terminated`, and an empty body is valid and yields `Nil`. +The two macros parse at the level they emit at: `Product!` reads its elements as types and `product!` reads them as expressions, so a `product!` element may be any expression — a literal, a method call, an arithmetic expression — and not just a path that also parses as a type. Parsing and re-parsing at the right level is what keeps each macro in its position: `product!`'s `syn::Expr` output is valid in expression context and `Product!`'s `syn::Type` output in type context. A trailing comma is accepted on both because the list is parsed with `parse_terminated`, and an empty body is valid and yields `Nil` (a value for `product!`, a type for `Product!`). -Because `eval` re-parses its output through `parse_internal!`, a fold that produced malformed tokens would surface as a spanned `syn::Error` rather than raw token garbage — though with `Cons`/`Nil` and well-formed element types this path does not normally fail. +Because `eval` re-parses its output through `parse_internal!`, a fold that produced malformed tokens would surface as a spanned `syn::Error` rather than raw token garbage — though with `Cons`/`Nil` and well-formed elements this path does not normally fail. ## Tests -`Product!`/`product!` have no snapshot macro of their own; the chain they build is exercised wherever a struct's field list is derived, since `#[derive(HasFields)]` emits a `Product!` of `Field` entries. +`Product!`/`product!` have no snapshot macro of their own; the type-level chain they build is exercised wherever a struct's field list is derived, since `#[derive(HasFields)]` emits a `Product!` of `Field` entries, and the value-level form has a dedicated behavioral test. +- [extensible_records/product_value.rs](../../../crates/tests/cgp-tests/tests/extensible_records/product_value.rs) exercises the value-level `product!` directly: that expression items build the nested `Cons(..)`/`Nil` value, that its type is the matching `Product!`, and that the empty and trailing-comma forms work. - [extensible_records/person_record.rs](../../../crates/tests/cgp-tests/tests/extensible_records/person_record.rs) and [extensible_records/record_derive.rs](../../../crates/tests/cgp-tests/tests/extensible_records/record_derive.rs) pin, through `snapshot_derive_cgp_data!` goldens, the `Product!` field spine a record derives, so the `Cons`/`Nil` shape is checked as embedded output. - [handlers/pipe_handlers.rs](../../../crates/tests/cgp-tests/tests/handlers/pipe_handlers.rs) uses `Product![…]` to write a handler pipeline, exercising the type-level form as a list of provider types rather than fields. diff --git a/docs/implementation/functions/parse/is_provider_params.md b/docs/implementation/functions/parse/is_provider_params.md index a71e1a56..1909d072 100644 --- a/docs/implementation/functions/parse/is_provider_params.md +++ b/docs/implementation/functions/parse/is_provider_params.md @@ -2,23 +2,21 @@ `parse_is_provider_params` converts a trait's generic parameters into the tuple of types that fills the `Params` position of an [`IsProviderFor`](../../../reference/traits/is_provider_for.md) bound. Every provider trait carries an `IsProviderFor` supertrait, and this function computes the `(Params)` part from the consumer trait's generics, so the marker records exactly the extra parameters a component takes beyond its context. -The transformation is a straightforward per-parameter mapping, but it normalizes the parameters into type form because the params tuple is a tuple of *types*. A type parameter passes through as itself: `T` becomes `T`. A lifetime parameter is lifted into a type through the `Life` wrapper, because a bare lifetime cannot appear as a tuple element: `'a` becomes `Life<'a>`. The parameters are first passed through `TypeGenerics` so that bounds and defaults are stripped and only the parameter names remain, then each is rendered with `parse_internal!`. The result is a `Punctuated` that the caller wraps in parentheses. +The transformation is a straightforward per-parameter mapping over the trait's generics, emitting one type per parameter because the params tuple is a tuple of *types*. A type parameter passes through by name: `T` becomes `T`. A lifetime parameter is lifted into a type through the `Life` wrapper, because a bare lifetime cannot appear as a tuple element: `'a` becomes `Life<'a>`. Only the parameter's name matters, so any bounds or defaults on it are dropped. Each element is rendered with `parse_internal!`, and the result is a `Punctuated` that the caller wraps in parentheses. ## Behavior and corner cases Lifetimes are preserved in the params tuple even though they are dropped from the redirected lookup path. `parse_is_provider_params` emits `Life<'a>` for a lifetime, so `HasReference<'a, T>` yields the tuple `(Life<'a>, T)`; the separate `generic_params_to_path` helper used by the `RedirectLookup` impl keeps only type parameters, which is why a lifetime appears in `IsProviderFor` but not in the `ConcatPath` path. Holding both facts together is necessary to read the lifetime-component snapshot correctly. -## Known issues - -A const generic parameter triggers a panic. The `GenericParam::Const` arm is `unimplemented!("const generic parameters are not yet supported in CGP traits")`, so any component-defining macro that reaches this function with a const parameter aborts expansion with a panic rather than returning a spanned `syn::Error`. The correct behavior would be a clean rejection or genuine const-parameter support. This is the root cause of the const-generic limitation recorded in [entrypoints/cgp_component.md](../../entrypoints/cgp_component.md); the same panic affects every macro that builds a provider trait through this helper. +A const generic parameter is rejected with a spanned `syn::Error`. A const value cannot appear in the params tuple (which is a tuple of types) and CGP's type-based wiring cannot key on it, so the `GenericParam::Const` arm returns an error rather than emitting a tuple element. Because every macro that builds a provider trait routes through this helper, the rejection applies uniformly to `#[cgp_component]`, `#[cgp_type]`, and `#[cgp_getter]`; the user-facing consequence is recorded in [entrypoints/cgp_component.md](../../entrypoints/cgp_component.md). ## Tests -The function is covered indirectly through the expansion snapshots that pin the `IsProviderFor` params tuple. +The params tuple is pinned by the expansion snapshots, and the const-parameter rejection has its own failure case. - The empty `()` case in [basic_delegation/component_macro.rs](../../../../crates/tests/cgp-tests/tests/basic_delegation/component_macro.rs). - The `(Life<'a>, T)` case in [generic_components/component_lifetime.rs](../../../../crates/tests/cgp-tests/tests/generic_components/component_lifetime.rs). -- The const-generic panic has no test yet and is a candidate failure case for `cgp-macro-tests`. +- The const-parameter rejection in [cgp-macro-tests/tests/parser_rejections/cgp_component.rs](../../../../crates/tests/cgp-macro-tests/tests/parser_rejections/cgp_component.rs). ## Source diff --git a/docs/reference/attributes/implicit.md b/docs/reference/attributes/implicit.md index 071437c4..6e7a660b 100644 --- a/docs/reference/attributes/implicit.md +++ b/docs/reference/attributes/implicit.md @@ -22,7 +22,7 @@ fn area(&self, #[implicit] width: f64, #[implicit] height: f64) -> f64 { The argument name doubles as the field name. Here `width` and `height` name both the local variables used in the body and the context fields the values are read from, via `Symbol!("width")` and `Symbol!("height")`. The argument type is the type the body sees, and it determines how the field is accessed (described under Expansion). -Three rules constrain where `#[implicit]` may appear. The function must take `self` as its first argument, because the field is read from `self`; a function with implicit arguments but no receiver is rejected. The argument pattern must be a bare identifier, not a destructuring or `mut` pattern — to get a mutable local, clone the injected value explicitly inside the body. And when the receiver is `&mut self`, at most one implicit argument is allowed, since each one borrows from the same context. +Three rules constrain where `#[implicit]` may appear. The function must take `self` as its first argument, because the field is read from `self`; a function with implicit arguments but no receiver is rejected. The argument pattern must be a bare identifier, not a destructuring or `mut` pattern — to get a mutable local, clone the injected value explicitly inside the body. And a `&mut`-reference implicit argument (`&mut T`) must be the *only* implicit argument on its function, and requires a `&mut self` receiver: it is read through `get_field_mut`, which borrows the whole context exclusively, so it cannot coexist with any other field read. Immutable implicit arguments carry no such restriction — they are shared borrows and combine freely, in any number, on either a `&self` or a `&mut self` receiver. `#[implicit]` is usable wherever CGP rewrites function bodies into providers: inside [`#[cgp_fn]`](../macros/cgp_fn.md) and inside the methods of a [`#[cgp_impl]`](../macros/cgp_impl.md) block. It is not a standalone macro — it is only meaningful as an argument attribute consumed by those macros. @@ -60,7 +60,7 @@ where The two `let` bindings are inserted at the top of the body in argument order, before any of the original statements, so the names are in scope for the rest of the function. The generated context type parameter is literally named `__Context__` in the emitted code; the examples here use `Context` for readability. -The access expression depends on the argument type, following the same rules as [`#[cgp_auto_getter]`](../macros/cgp_auto_getter.md). For an owned type such as `f64` or `String`, the macro reads the field by reference and appends `.clone()`, so the body receives an owned value. The one special case worth knowing is `&str`: an argument typed `&str` is backed by a `String` field, and the access uses `.as_str()` rather than `.clone()`. Concretely: +The access expression depends on the argument type, following the same rules as [`#[cgp_auto_getter]`](../macros/cgp_auto_getter.md). For an owned type such as `f64` or `String`, the macro reads the field by reference and appends `.clone()`, so the body receives an owned value. The one special case worth knowing is `&str`: an argument typed `&str` is backed by a `String` field, and the access uses `.as_str()` rather than `.clone()`. The mutability of the access follows the *argument's* own type, not the receiver's: only a `&mut T` argument reads through `HasFieldMut`/`get_field_mut`, while every immutable argument reads through `HasField`/`get_field` even on a `&mut self` receiver. Concretely: ```rust #[cgp_fn] diff --git a/docs/reference/attributes/use_type.md b/docs/reference/attributes/use_type.md index 3ae1a115..4859395f 100644 --- a/docs/reference/attributes/use_type.md +++ b/docs/reference/attributes/use_type.md @@ -6,25 +6,47 @@ `#[use_type]` removes the boilerplate of referring to an abstract type that lives on another CGP trait. A CGP trait often needs a type that is defined elsewhere — a `Scalar` from `HasScalarType`, an `Error` from `HasErrorType` — and Rust requires every reference to that type to be written in fully-qualified form, `::Scalar`, because a bare `Scalar` is not a type the compiler knows about. Writing that prefix on every occurrence, in the return type, in each implicit argument, and in the body, is verbose and easy to get wrong. -The attribute lets you write the bare identifier `Scalar` everywhere and have the macro expand it for you. You declare the type once in the attribute — `#[use_type(HasScalarType::Scalar)]` — and the macro replaces each standalone `Scalar` type with `::Scalar`, while also adding `HasScalarType` as a supertrait of the generated trait (for `#[cgp_component]`) or as a `where`-clause bound on the impl (for `#[cgp_impl]` and `#[cgp_fn]`). The bare identifier reads like a normal generic, but resolves to the qualified associated type. +The attribute lets you write the bare identifier `Scalar` everywhere and have the macro expand it for you. You declare the type once in the attribute — `#[use_type(HasScalarType.Scalar)]` — and the macro replaces each standalone `Scalar` type with `::Scalar`, while also adding `HasScalarType` as a supertrait of the generated trait (for `#[cgp_component]`) or as a `where`-clause bound on the impl (for `#[cgp_impl]` and `#[cgp_fn]`). The bare identifier reads like a normal generic, but resolves to the qualified associated type. Beyond saving keystrokes, the fully-qualified rewrite removes ambiguity that the bare form cannot express. Because the macro always emits the `::Type` path, nested associated types compose without the author ever spelling out the path, foreign abstract types can be pulled from a type parameter rather than `Self`, and type-equality constraints between two imported types can be stated declaratively. These capabilities are why the `/cgp` skill recommends `#[use_type]` as the default way to import abstract types in all three macros. ## Syntax -`#[use_type]` is applied as an outer attribute alongside the `#[cgp_fn]`, `#[cgp_impl]`, or `#[cgp_component]` attribute, and its argument names a trait and one or more of its associated types. The simplest form imports a single type from a trait by path: +`#[use_type]` is applied as an outer attribute alongside the `#[cgp_fn]`, `#[cgp_impl]`, or `#[cgp_component]` attribute, and its argument names a trait and one or more of its associated types. A `.` separates the trait from the associated type — not `::` — which is what lets the trait itself be a full path or carry generic arguments without the parser confusing a path segment for the associated type. The simplest form imports a single type from a trait: ```rust -#[use_type(HasScalarType::Scalar)] +#[use_type(HasScalarType.Scalar)] ``` -The path before the final segment is the trait, and the final segment is the associated type to import. The rewrite target — the type the bare identifier expands into — defaults to `Self`, so the example above rewrites `Scalar` to `::Scalar`. +The part before the `.` is the trait and the identifier after it is the associated type to import. The rewrite target — the type the bare identifier expands into — defaults to `Self`, so the example above rewrites `Scalar` to `::Scalar`. -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. +Because the `.` is the only separator the macro looks for, the trait may be written as a full path or with generic arguments, both using ordinary `::`. `#[use_type(errors::HasErrorType.Error)]` imports from a trait named by path without bringing it into scope, and `#[use_type(HasFooType.Foo)]` imports the associated type of a specific generic instantiation, rewriting `Foo` to `>::Foo`. This is what makes it possible to import the same associated type from two instantiations under different aliases, as in `#[use_type(HasFooType.{Foo as FooX}, HasFooType.{Foo as FooY})]`. -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)]`. +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. -One restriction applies to `#[cgp_component]` specifically: the `= ...` type-equality form is rejected there, because a trait definition cannot carry the impl-side equality constraint that the equality form produces. Equality constraints belong on `#[cgp_fn]` and `#[cgp_impl]`, where they become `where` bounds. +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)]`. + +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. + +## Syntax Grammar + +The grammar below covers the tokens inside `#[use_type(...)]` — the comma-separated list of import specs, not the surrounding attribute delimiters. + +```ebnf +UseTypeArgs -> UseTypeSpec (`,` UseTypeSpec)* `,`? + +UseTypeSpec -> (`@` ContextPath `.`)? TraitPath `.` TypeItems + +ContextPath -> TypePath +TraitPath -> TypePath + +TypeItems -> UseTypeIdent + | `{` UseTypeIdent (`,` UseTypeIdent)* `,`? `}` + +UseTypeIdent -> IDENTIFIER (`as` IDENTIFIER)? (`=` Type)? +``` + +`ContextPath` and `TraitPath` are ordinary Rust `TypePath`s (a path whose final segment may carry angle-bracketed generic arguments); their `::` segments belong to the path, while the `.` after them starts the associated-type list. An omitted `@ContextPath .` prefix defaults the rewrite target to `Self`. In each `UseTypeIdent`, the leading `IDENTIFIER` is the associated type's own name, an `as` clause gives it a local alias to write in the signature, and an `= Type` clause pins it with an equality bound (accepted on `#[cgp_fn]` and `#[cgp_impl]`, rejected on `#[cgp_component]`). ## Expansion @@ -36,7 +58,7 @@ pub trait HasScalarType { } #[cgp_fn] -#[use_type(HasScalarType::Scalar)] +#[use_type(HasScalarType.Scalar)] fn rectangle_area( &self, #[implicit] width: Scalar, @@ -71,13 +93,13 @@ where The substitution is purely textual at the type level: it matches single-segment type paths with no arguments whose identifier equals the imported name (or its alias), and replaces them with `::Scalar`. A bare `Scalar` anywhere — return type, implicit-argument annotation, or a `let` binding inside the body — is rewritten the same way, which is what makes nested uses work without the author writing any path. -Because the rewrite fires only on the bare identifier of an *imported* type, a construct's own **local associated types must always stay qualified as `Self::Assoc`** and are left untouched. A `#[cgp_component]` trait or a `#[cgp_impl]` provider that declares its own `type Output` refers to it as `Self::Output`, never as a bare `Output`, precisely because `Output` is the construct's own type rather than one imported from another trait — `#[use_type]` neither imports it nor rewrites it, and it should not be listed in a `#[use_type]` attribute. This is why a mixed signature such as `Result` is correct and idiomatic: the local `Self::Output` stays qualified while the imported foreign type `Error` (from `#[use_type(HasErrorType::Error)]`) is written bare. Attempting to write the local type bare would leave a `Output` identifier that resolves to nothing, since the substitution pass has no entry for it. +Because the rewrite fires only on the bare identifier of an *imported* type, a construct's own **local associated types must always stay qualified as `Self::Assoc`** and are left untouched. A `#[cgp_component]` trait or a `#[cgp_impl]` provider that declares its own `type Output` refers to it as `Self::Output`, never as a bare `Output`, precisely because `Output` is the construct's own type rather than one imported from another trait — `#[use_type]` neither imports it nor rewrites it, and it should not be listed in a `#[use_type]` attribute. This is why a mixed signature such as `Result` is correct and idiomatic: the local `Self::Output` stays qualified while the imported foreign type `Error` (from `#[use_type(HasErrorType.Error)]`) is written bare. Attempting to write the local type bare would leave a `Output` identifier that resolves to nothing, since the substitution pass has no entry for it. For `#[cgp_component]`, the trait is added as a supertrait rather than a `where` bound, and the rewrite touches the trait's own signatures. Starting from: ```rust #[cgp_component(AreaCalculator)] -#[use_type(HasScalarType::Scalar)] +#[use_type(HasScalarType.Scalar)] pub trait CanCalculateArea { fn area(&self) -> Scalar; } @@ -92,11 +114,11 @@ pub trait CanCalculateArea: HasScalarType { } ``` -The supertrait is added only when the rewrite target is `Self`. With the foreign-type `@` form the target is a named type, so no supertrait is added; instead the bound lands in the impl's `where` clause. This `#[cgp_fn]` imports `Scalar` from a generic parameter `Types`: +The supertrait is added only when the rewrite target is `Self`. With the foreign-type `@` form the target is a named type, so on `#[cgp_fn]` and `#[cgp_impl]` the bound lands in the impl's `where` clause instead of as a supertrait; on `#[cgp_component]` no bound is added at all, so a component using the `@` form must declare the parameter's bound itself (for example `pub trait CanCalculateArea`). This `#[cgp_fn]` imports `Scalar` from a generic parameter `Types`: ```rust #[cgp_fn] -#[use_type(@Types::HasScalarType::Scalar)] +#[use_type(@Types.HasScalarType.Scalar)] pub fn rectangle_area( &self, #[implicit] width: Scalar, @@ -136,7 +158,7 @@ where } ``` -The type-equality form adds a constrained bound on top of the substitution. Writing `#[use_type(HasScalarType::{Scalar = f64})]` substitutes `Scalar` to `::Scalar` exactly as before, but emits `Self: HasScalarType` in the `where` clause in place of the plain `Self: HasScalarType`, pinning the abstract type to `f64`. When one import's equality target names another import's alias — as in `#[use_type(HasBarType::{Bar as Baz = Foo}, HasFooType::Foo)]` — the macro resolves the target across specs and emits `Self: HasBarType::Foo>`, tying the two abstract types together. Two imports may not share the same identifier or alias; doing so is a compile error. +The type-equality form adds a constrained bound on top of the substitution. Writing `#[use_type(HasScalarType.{Scalar = f64})]` substitutes `Scalar` to `::Scalar` exactly as before, but emits `Self: HasScalarType` in the `where` clause in place of the plain `Self: HasScalarType`, pinning the abstract type to `f64`. When one import's equality target names another import's alias — as in `#[use_type(HasBarType.{Bar as Baz = Foo}, HasFooType.Foo)]` — the macro resolves the target across specs and emits `Self: HasBarType::Foo>`, tying the two abstract types together. This cross-spec resolution relies on aliases being unique, which the duplicate check described in the Syntax section guarantees. ## Examples @@ -152,7 +174,7 @@ pub trait HasScalarType { } #[cgp_component(AreaCalculator)] -#[use_type(HasScalarType::Scalar)] +#[use_type(HasScalarType.Scalar)] pub trait CanCalculateArea { fn area(&self) -> Scalar; } @@ -162,7 +184,7 @@ pub trait CanCalculateArea { ```rust #[cgp_impl(new RectangleArea)] -#[use_type(HasScalarType::Scalar)] +#[use_type(HasScalarType.Scalar)] impl AreaCalculator { fn area(&self, #[implicit] width: Scalar, #[implicit] height: Scalar) -> Scalar { width * height @@ -178,8 +200,8 @@ The provider's `#[use_type]` adds `Self: HasScalarType` to its `where` clause an ## Source -- Parsing: the attribute is parsed by `UseTypeAttribute` in [crates/macros/cgp-macro-core/src/types/attributes/use_type/attribute.rs](../../../crates/macros/cgp-macro-core/src/types/attributes/use_type/attribute.rs), with per-type entries (`as` alias and `=` equality) in `ident.rs`. -- Two-phase transform (substitute then add bounds): lives in `attributes.rs` as `transform_item_trait` (supertrait for `#[cgp_component]`) and `transform_item_impl` (`where` bound for impls); the type-equality and foreign-context resolution are in `type_predicates.rs`. +- Parsing: the attribute is parsed by `UseTypeAttribute` in [crates/macros/cgp-macro-core/src/types/attributes/use_type/attribute.rs](../../../crates/macros/cgp-macro-core/src/types/attributes/use_type/attribute.rs), which reads the context and trait as `PathWithTypeArgs` separated by `.`; per-type entries (`as` alias and `=` equality) are in `ident.rs`. +- Two-phase transform (substitute then add bounds): lives in `attributes.rs` as `transform_item_trait` (supertrait for `#[cgp_component]`) and `transform_item_impl` (`where` bound for impls); the type-equality and foreign-context resolution are in `type_predicates.rs`, whose `forbid_duplicate_aliases` both transforms call to reject a shared identifier or alias. - Identifier substitution: the `SubstituteAbstractType` `VisitMut` pass in [crates/macros/cgp-macro-core/src/visitors/substitute_abstract_type.rs](../../../crates/macros/cgp-macro-core/src/visitors/substitute_abstract_type.rs), which matches single-segment, argument-free type paths. - `= ...` rejection for component traits: enforced in `types/attributes/cgp_component_attributes.rs`. - Implementation document (the internal AST types, the two-phase transform, and the index of tests and snapshots): [implementation/asts/attributes.md](../../implementation/asts/attributes.md). diff --git a/docs/reference/components/can_raise_error.md b/docs/reference/components/can_raise_error.md index 5cb1330a..6f713338 100644 --- a/docs/reference/components/can_raise_error.md +++ b/docs/reference/components/can_raise_error.md @@ -10,19 +10,19 @@ ## Definition -Both traits import the context's shared abstract error type with [`#[use_type(HasErrorType::Error)]`](../attributes/use_type.md), so a bare `Error` in either signature stands for the context's error rather than being written as `Self::Error`. Each is a `#[cgp_component]`, making it a full component with a generated provider trait. `CanRaiseError` converts a source error into the abstract error: +Both traits import the context's shared abstract error type with [`#[use_type(HasErrorType.Error)]`](../attributes/use_type.md), so a bare `Error` in either signature stands for the context's error rather than being written as `Self::Error`. Each is a `#[cgp_component]`, making it a full component with a generated provider trait. `CanRaiseError` converts a source error into the abstract error: ```rust #[cgp_component(ErrorRaiser)] #[prefix(@cgp.core.error in DefaultNamespace)] #[derive_delegate(UseDelegate)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] pub trait CanRaiseError { fn raise_error(error: SourceError) -> Error; } ``` -The `SourceError` parameter is the concrete error being raised, and `raise_error` is an associated function — it takes the source error by value and returns the abstract `Error`, without needing a `self` receiver, because raising an error is a property of the context type rather than of any particular value. `#[use_type(HasErrorType::Error)]` adds `HasErrorType` as a supertrait and rewrites the bare `Error` to `::Error`. The `#[cgp_component(ErrorRaiser)]` attribute names the provider trait `ErrorRaiser`, and [`#[derive_delegate(UseDelegate)]`](../attributes/derive_delegate.md) wires `UseDelegate` so the raise behavior can be dispatched per source-error type through a delegation table — a context can handle each `SourceError` with a different provider. +The `SourceError` parameter is the concrete error being raised, and `raise_error` is an associated function — it takes the source error by value and returns the abstract `Error`, without needing a `self` receiver, because raising an error is a property of the context type rather than of any particular value. `#[use_type(HasErrorType.Error)]` adds `HasErrorType` as a supertrait and rewrites the bare `Error` to `::Error`. The `#[cgp_component(ErrorRaiser)]` attribute names the provider trait `ErrorRaiser`, and [`#[derive_delegate(UseDelegate)]`](../attributes/derive_delegate.md) wires `UseDelegate` so the raise behavior can be dispatched per source-error type through a delegation table — a context can handle each `SourceError` with a different provider. `CanWrapError` has the same shape but takes an existing error plus a detail: @@ -30,7 +30,7 @@ The `SourceError` parameter is the concrete error being raised, and `raise_error #[cgp_component(ErrorWrapper)] #[prefix(@cgp.core.error in DefaultNamespace)] #[derive_delegate(UseDelegate)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] pub trait CanWrapError { fn wrap_error(error: Error, detail: Detail) -> Error; } @@ -52,14 +52,14 @@ A provider raises a concrete error into the abstract one and wraps a message ont use cgp::prelude::*; #[cgp_component(Loader)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] pub trait CanLoad { fn load(&self, path: &str) -> Result; } #[cgp_impl(new LoadOrFail)] #[uses(CanRaiseError, CanWrapError)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] impl Loader { fn load(&self, path: &str) -> Result { if path.is_empty() { diff --git a/docs/reference/components/handler.md b/docs/reference/components/handler.md index c983187b..03bb0f4d 100644 --- a/docs/reference/components/handler.md +++ b/docs/reference/components/handler.md @@ -10,14 +10,14 @@ This generality is why the family promotes toward `Handler` rather than away fro ## Definition -`Handler` is a CGP component defined with `#[cgp_component]` under `#[async_trait]`, and it imports the context's abstract error type through [`#[use_type(HasErrorType::Error)]`](../attributes/use_type.md) so its method can return that error by the bare name `Error`: +`Handler` is a CGP component defined with `#[cgp_component]` under `#[async_trait]`, and it imports the context's abstract error type through [`#[use_type(HasErrorType.Error)]`](../attributes/use_type.md) so its method can return that error by the bare name `Error`: ```rust #[async_trait] #[cgp_component(Handler)] #[derive_delegate(UseDelegate)] #[derive_delegate(UseInputDelegate)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] pub trait CanHandle { type Output; @@ -29,9 +29,9 @@ pub trait CanHandle { } ``` -The consumer trait `CanHandle` combines the async and fallible refinements of the base signature. Its `handle` method is declared `async` and returns `Result`, so it is the async counterpart of `CanTryCompute` and the fallible counterpart of `CanComputeAsync`. `#[use_type(HasErrorType::Error)]` adds `HasErrorType` as a supertrait and rewrites the bare `Error` to `::Error`, which is why the definition never spells `HasErrorType` or `Self::Error` by hand; the local associated type `Output` stays qualified as `Self::Output`, because it is the trait's own type rather than an imported one. The `#[async_trait]` attribute then rewrites the `async fn` into a method returning `impl Future>`, avoiding any boxed future. The component is wired through the generated `HandlerComponent` marker, its provider trait is `Handler` with the context moved into an explicit first parameter, and the two `#[derive_delegate(...)]` attributes generate dispatching providers keyed on `Code` and on `Input`. +The consumer trait `CanHandle` combines the async and fallible refinements of the base signature. Its `handle` method is declared `async` and returns `Result`, so it is the async counterpart of `CanTryCompute` and the fallible counterpart of `CanComputeAsync`. `#[use_type(HasErrorType.Error)]` adds `HasErrorType` as a supertrait and rewrites the bare `Error` to `::Error`, which is why the definition never spells `HasErrorType` or `Self::Error` by hand; the local associated type `Output` stays qualified as `Self::Output`, because it is the trait's own type rather than an imported one. The `#[async_trait]` attribute then rewrites the `async fn` into a method returning `impl Future>`, avoiding any boxed future. The component is wired through the generated `HandlerComponent` marker, its provider trait is `Handler` with the context moved into an explicit first parameter, and the two `#[derive_delegate(...)]` attributes generate dispatching providers keyed on `Code` and on `Input`. -The by-reference sibling `HandlerRef` is identical except that it borrows its input. Its consumer trait `CanHandleRef` also imports the error type with `#[use_type(HasErrorType::Error)]` and declares `async fn handle_ref(&self, _tag: PhantomData, input: &Input) -> Result`, taking `&Input` where `CanHandle` takes `Input`. +The by-reference sibling `HandlerRef` is identical except that it borrows its input. Its consumer trait `CanHandleRef` also imports the error type with `#[use_type(HasErrorType.Error)]` and declares `async fn handle_ref(&self, _tag: PhantomData, input: &Input) -> Result`, taking `&Input` where `CanHandle` takes `Input`. ## Implementations diff --git a/docs/reference/components/has_error_type.md b/docs/reference/components/has_error_type.md index e67b185a..1b96fa5a 100644 --- a/docs/reference/components/has_error_type.md +++ b/docs/reference/components/has_error_type.md @@ -40,7 +40,7 @@ A context declares its abstract error and generic code returns it without naming use cgp::prelude::*; #[cgp_component(Validator)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] pub trait CanValidate { fn validate(&self) -> Result<(), Error>; } @@ -54,7 +54,7 @@ delegate_components! { } ``` -Here [`#[use_type(HasErrorType::Error)]`](../attributes/use_type.md) adds `HasErrorType` as a supertrait of `CanValidate` and rewrites the bare `Error` to `::Error`, so `validate` returns the context's shared abstract error without writing `Self::Error`. `App` wires its error-type component to `UseType`, fixing that error to `String` (which satisfies the `Debug` bound). The same abstract error can equally be implemented directly: +Here [`#[use_type(HasErrorType.Error)]`](../attributes/use_type.md) adds `HasErrorType` as a supertrait of `CanValidate` and rewrites the bare `Error` to `::Error`, so `validate` returns the context's shared abstract error without writing `Self::Error`. `App` wires its error-type component to `UseType`, fixing that error to `String` (which satisfies the `Debug` bound). The same abstract error can equally be implemented directly: ```rust impl HasErrorType for App { diff --git a/docs/reference/components/has_runtime.md b/docs/reference/components/has_runtime.md index 3ebf216b..612162d6 100644 --- a/docs/reference/components/has_runtime.md +++ b/docs/reference/components/has_runtime.md @@ -25,17 +25,17 @@ pub type RuntimeOf = ::Runtime; Because the trait carries no provider-name argument, `#[cgp_type]` derives the provider name from the associated type: `Runtime` yields the provider trait `RuntimeTypeProvider` and the component marker `RuntimeTypeProviderComponent`. The `Runtime` associated type carries no bound, so any concrete type may be plugged in. The `RuntimeOf` alias is the convenient spelling of the resolved runtime type, used wherever writing `::Runtime` in full would be noise. -`HasRuntime` is a getter component defined with [`#[cgp_getter]`](../macros/cgp_getter.md), and it imports the runtime type with [`#[use_type(HasRuntimeType::Runtime)]`](../attributes/use_type.md) so the runtime type is available as the getter's return type under the bare name `Runtime`: +`HasRuntime` is a getter component defined with [`#[cgp_getter]`](../macros/cgp_getter.md), and it imports the runtime type with [`#[use_type(HasRuntimeType.Runtime)]`](../attributes/use_type.md) so the runtime type is available as the getter's return type under the bare name `Runtime`: ```rust #[cgp_getter] -#[use_type(HasRuntimeType::Runtime)] +#[use_type(HasRuntimeType.Runtime)] pub trait HasRuntime { fn runtime(&self) -> &Runtime; } ``` -`#[cgp_getter]` derives the provider name from the trait name by stripping the `Has` prefix and appending `Getter`, so `HasRuntime` yields the provider trait `RuntimeGetter` and the component marker `RuntimeGetterComponent`. `#[use_type(HasRuntimeType::Runtime)]` adds `HasRuntimeType` as a supertrait and rewrites the bare `Runtime` to `::Runtime`, so the `runtime` method borrows the runtime value out of a borrow of the context, returning `&Runtime` — the abstract type supplied by `HasRuntimeType` — without writing `Self::Runtime` by hand. +`#[cgp_getter]` derives the provider name from the trait name by stripping the `Has` prefix and appending `Getter`, so `HasRuntime` yields the provider trait `RuntimeGetter` and the component marker `RuntimeGetterComponent`. `#[use_type(HasRuntimeType.Runtime)]` adds `HasRuntimeType` as a supertrait and rewrites the bare `Runtime` to `::Runtime`, so the `runtime` method borrows the runtime value out of a borrow of the context, returning `&Runtime` — the abstract type supplied by `HasRuntimeType` — without writing `Self::Runtime` by hand. ## Behavior diff --git a/docs/reference/components/runner.md b/docs/reference/components/runner.md index ac9a62f6..3d84f31a 100644 --- a/docs/reference/components/runner.md +++ b/docs/reference/components/runner.md @@ -12,13 +12,13 @@ The family is the execution layer that ties together the rest of a CGP applicati ## Definition -Both components are declared in `cgp-run`, each with [`#[cgp_component]`](../macros/cgp_component.md), `#[async_trait]`, a [`#[derive_delegate(UseDelegate)]`](../attributes/derive_delegate.md) attribute, and [`#[use_type(HasErrorType::Error)]`](../attributes/use_type.md) so the task can fail with the context's abstract error, written as the bare `Error`: +Both components are declared in `cgp-run`, each with [`#[cgp_component]`](../macros/cgp_component.md), `#[async_trait]`, a [`#[derive_delegate(UseDelegate)]`](../attributes/derive_delegate.md) attribute, and [`#[use_type(HasErrorType.Error)]`](../attributes/use_type.md) so the task can fail with the context's abstract error, written as the bare `Error`: ```rust #[cgp_component(Runner)] #[async_trait] #[derive_delegate(UseDelegate)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] pub trait CanRun { async fn run(&self, _code: PhantomData) -> Result<(), Error>; } @@ -26,7 +26,7 @@ pub trait CanRun { #[cgp_component(SendRunner)] #[async_trait] #[derive_delegate(UseDelegate)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] pub trait CanSendRun { fn send_run( &self, @@ -35,7 +35,7 @@ pub trait CanSendRun { } ``` -The `Code` parameter is the type-level name of the task to run; it is passed only as `PhantomData`, so it carries no data and exists purely to select an implementation. `#[use_type(HasErrorType::Error)]` adds `HasErrorType` as a supertrait and rewrites the bare `Error` to `::Error`, so neither trait spells `HasErrorType` or `Self::Error` by hand. `#[cgp_component(Runner)]` names the provider trait `Runner` and the component marker `RunnerComponent`; `#[cgp_component(SendRunner)]` names them `SendRunner` and `SendRunnerComponent`. The [`#[derive_delegate(UseDelegate)]`](../attributes/derive_delegate.md) attribute generates a `UseDelegate` provider that dispatches on `Code`, so a context can route different `Code` tags to different runner providers through an inner delegation table. +The `Code` parameter is the type-level name of the task to run; it is passed only as `PhantomData`, so it carries no data and exists purely to select an implementation. `#[use_type(HasErrorType.Error)]` adds `HasErrorType` as a supertrait and rewrites the bare `Error` to `::Error`, so neither trait spells `HasErrorType` or `Self::Error` by hand. `#[cgp_component(Runner)]` names the provider trait `Runner` and the component marker `RunnerComponent`; `#[cgp_component(SendRunner)]` names them `SendRunner` and `SendRunnerComponent`. The [`#[derive_delegate(UseDelegate)]`](../attributes/derive_delegate.md) attribute generates a `UseDelegate` provider that dispatches on `Code`, so a context can route different `Code` tags to different runner providers through an inner delegation table. The two traits differ only in how they shape the asynchronous return. `CanRun::run` is an ordinary `async fn` returning `Result<(), Error>`, with no `Send` requirement on the future. `CanSendRun::send_run` instead returns an explicit `impl Future> + Send`, so callers may move the future across threads. Both produce `()` on success — the task's effect is observable elsewhere, not returned. @@ -78,7 +78,7 @@ A complete flow defines tasks, wires runners, proxies the `Send` variant, and sp ```rust #[cgp_impl(new SpawnAndRun: RunnerComponent)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] impl Runner where Self: 'static + Send + Clone + CanSendRun, diff --git a/docs/reference/components/try_computer.md b/docs/reference/components/try_computer.md index 2aff9064..e6ce1577 100644 --- a/docs/reference/components/try_computer.md +++ b/docs/reference/components/try_computer.md @@ -10,13 +10,13 @@ Returning the *context's* abstract error rather than a concrete one is what keep ## Definition -`TryComputer` is a CGP component defined with `#[cgp_component]`, and it imports the context's abstract error type through [`#[use_type(HasErrorType::Error)]`](../attributes/use_type.md) so that its method can return that error by the bare name `Error`: +`TryComputer` is a CGP component defined with `#[cgp_component]`, and it imports the context's abstract error type through [`#[use_type(HasErrorType.Error)]`](../attributes/use_type.md) so that its method can return that error by the bare name `Error`: ```rust #[cgp_component(TryComputer)] #[derive_delegate(UseDelegate)] #[derive_delegate(UseInputDelegate)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] pub trait CanTryCompute { type Output; @@ -28,9 +28,9 @@ pub trait CanTryCompute { } ``` -The consumer trait `CanTryCompute` mirrors `CanCompute` but for the fallible case. Its `try_compute` method takes `&self`, a `PhantomData` naming the computation, and the `Input` by value, returning `Result` — the associated `Output` on success and the context's abstract error on failure. `#[use_type(HasErrorType::Error)]` adds `HasErrorType` as a supertrait and rewrites the bare `Error` to `::Error`, so the definition writes neither `HasErrorType` nor `Self::Error` by hand; the local associated type `Output` stays qualified as `Self::Output`, since it is the trait's own type. The component is wired through the generated `TryComputerComponent` marker, and the macro generates the provider trait `TryComputer` with the context moved into an explicit first parameter. The two `#[derive_delegate(...)]` attributes generate dispatching providers keyed on `Code` and on `Input`. +The consumer trait `CanTryCompute` mirrors `CanCompute` but for the fallible case. Its `try_compute` method takes `&self`, a `PhantomData` naming the computation, and the `Input` by value, returning `Result` — the associated `Output` on success and the context's abstract error on failure. `#[use_type(HasErrorType.Error)]` adds `HasErrorType` as a supertrait and rewrites the bare `Error` to `::Error`, so the definition writes neither `HasErrorType` nor `Self::Error` by hand; the local associated type `Output` stays qualified as `Self::Output`, since it is the trait's own type. The component is wired through the generated `TryComputerComponent` marker, and the macro generates the provider trait `TryComputer` with the context moved into an explicit first parameter. The two `#[derive_delegate(...)]` attributes generate dispatching providers keyed on `Code` and on `Input`. -The by-reference sibling `TryComputerRef` is identical except that it borrows its input. Its consumer trait `CanTryComputeRef` also imports the error type with `#[use_type(HasErrorType::Error)]` and declares `fn try_compute_ref(&self, _code: PhantomData, input: &Input) -> Result`, taking `&Input` where `CanTryCompute` takes `Input`. Both components are synchronous; their async-and-fallible counterpart is `Handler`. +The by-reference sibling `TryComputerRef` is identical except that it borrows its input. Its consumer trait `CanTryComputeRef` also imports the error type with `#[use_type(HasErrorType.Error)]` and declares `fn try_compute_ref(&self, _code: PhantomData, input: &Input) -> Result`, taking `&Input` where `CanTryCompute` takes `Input`. Both components are synchronous; their async-and-fallible counterpart is `Handler`. ## Implementations @@ -66,7 +66,7 @@ use cgp::extra::handler::{CanTryCompute, TryComputer, TryComputerComponent}; #[cgp_impl(new ParseU64)] #[uses(CanRaiseError)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] impl TryComputer { type Output = u64; diff --git a/docs/reference/macros/cgp_component.md b/docs/reference/macros/cgp_component.md index 57a86c8a..d25e06db 100644 --- a/docs/reference/macros/cgp_component.md +++ b/docs/reference/macros/cgp_component.md @@ -36,7 +36,7 @@ The three keys correspond to the three names the macro needs, and each has a def Two companion attributes extend the macro for special cases and are documented separately. Adding [`#[derive_delegate(...)]`](../attributes/derive_delegate.md) generates `UseDelegate` providers that dispatch on a generic parameter, and adding [`#[extend(...)]`](../attributes/extend.md) adds supertrait bounds to the generated consumer trait. The related macros [`#[cgp_type]`](cgp_type.md) and [`#[cgp_getter]`](cgp_getter.md) build on `#[cgp_component]` to derive additional constructs for abstract-type and getter components respectively. -When a component's supertrait exists only to supply an abstract type the trait's own signatures name — most commonly [`HasErrorType`](../components/has_error_type.md), whose `Error` a fallible method returns — prefer importing that type with [`#[use_type]`](../attributes/use_type.md) over writing the supertrait and the qualified `Self::` path by hand. Annotating the trait with `#[use_type(HasErrorType::Error)]` adds `HasErrorType` as a supertrait *and* rewrites a bare `Error` in the signatures to `::Error`, so the definition reads `pub trait CanLoad { fn load(&self, path: &str) -> Result; }` rather than spelling `: HasErrorType` and `Self::Error`. For a supertrait `#[use_type]` cannot express — a capability supertrait with no associated type to import, or a bound the trait does not name in its signatures — declare it with `#[extend(...)]` in preference to native `: Supertrait` syntax, which reads as OOP-style inheritance rather than the capability import a CGP supertrait actually is. A local associated type the trait declares itself, such as a handler's `type Output`, always stays written as `Self::Output`; it is not imported, so `#[use_type]` neither lists nor rewrites it. +When a component's supertrait exists only to supply an abstract type the trait's own signatures name — most commonly [`HasErrorType`](../components/has_error_type.md), whose `Error` a fallible method returns — prefer importing that type with [`#[use_type]`](../attributes/use_type.md) over writing the supertrait and the qualified `Self::` path by hand. Annotating the trait with `#[use_type(HasErrorType.Error)]` adds `HasErrorType` as a supertrait *and* rewrites a bare `Error` in the signatures to `::Error`, so the definition reads `pub trait CanLoad { fn load(&self, path: &str) -> Result; }` rather than spelling `: HasErrorType` and `Self::Error`. For a supertrait `#[use_type]` cannot express — a capability supertrait with no associated type to import, or a bound the trait does not name in its signatures — declare it with `#[extend(...)]` in preference to native `: Supertrait` syntax, which reads as OOP-style inheritance rather than the capability import a CGP supertrait actually is. A local associated type the trait declares itself, such as a handler's `type Output`, always stays written as `Self::Output`; it is not imported, so `#[use_type]` neither lists nor rewrites it. ## Syntax Grammar @@ -181,6 +181,10 @@ The call `rect.area()` resolves through the consumer blanket impl to `Rectangle: `#[cgp_component]` is the root that most other constructs attach to. [`#[cgp_impl]`](cgp_impl.md) and [`#[cgp_provider]`](cgp_provider.md) are the idiomatic ways to write providers for a component; [`#[cgp_fn]`](cgp_fn.md) is the lighter-weight alternative when only one implementation is ever needed. [`delegate_components!`](delegate_components.md) wires a component to a provider on a concrete context, and [`check_components!`](check_components.md) verifies at compile time that the wiring is complete. The specialized forms [`#[cgp_type]`](cgp_type.md) and [`#[cgp_getter]`](cgp_getter.md) extend `#[cgp_component]` for abstract types and getters. The attributes [`#[derive_delegate]`](../attributes/derive_delegate.md), [`#[extend]`](../attributes/extend.md), and [`#[use_type]`](../attributes/use_type.md) modify what the macro generates. +## Known issues + +A const generic parameter on the trait is not supported and is rejected with a compile error. Because the provider trait records a component's extra parameters as a tuple of *types* in its `IsProviderFor` supertrait, and CGP's wiring dispatches on types rather than values, a const value has nowhere to live in that machinery. A const *item* on the trait is unaffected — writing `const CONSTANT: u64;` as a trait member is an associated const, not a generic parameter, and is supplied by a const-generic provider struct (for example `UseConstant`) in the usual way. + ## Source - Entry point: `cgp_component` in [crates/macros/cgp-macro-lib/src/cgp_component.rs](../../../crates/macros/cgp-macro-lib/src/cgp_component.rs), which drives the `preprocess → eval → to_items` pipeline. diff --git a/docs/reference/macros/cgp_impl.md b/docs/reference/macros/cgp_impl.md index 20efc854..17c41761 100644 --- a/docs/reference/macros/cgp_impl.md +++ b/docs/reference/macros/cgp_impl.md @@ -43,7 +43,7 @@ impl AreaCalculator { } ``` -Several companion attributes are supported on a `#[cgp_impl]` block and are processed before the provider-trait rewrite. Method parameters marked [`#[implicit]`](../attributes/implicit.md) are extracted from the signature and turned into `HasField` reads on the context. [`#[uses(...)]`](../attributes/uses.md) adds simple trait bounds on `Self`, [`#[use_type(Trait::Type)]`](../attributes/use_type.md) imports an abstract type and rewrites its occurrences to fully qualified form, and [`#[use_provider(...)]`](../attributes/use_provider.md) supports higher-order providers by adding the `Self` parameter to an inner provider bound. +Several companion attributes are supported on a `#[cgp_impl]` block and are processed before the provider-trait rewrite. Method parameters marked [`#[implicit]`](../attributes/implicit.md) are extracted from the signature and turned into `HasField` reads on the context. [`#[uses(...)]`](../attributes/uses.md) adds simple trait bounds on `Self`, [`#[use_type(Trait.Type)]`](../attributes/use_type.md) imports an abstract type and rewrites its occurrences to fully qualified form, and [`#[use_provider(...)]`](../attributes/use_provider.md) supports higher-order providers by adding the `Self` parameter to an inner provider bound. Finally, [`#[default_impl(...)]`](../traits/default_namespace.md) registers the provider as a namespace's per-type default, emitting an extra delegation impl alongside the provider without changing the provider impl itself. ## Syntax Grammar @@ -117,7 +117,7 @@ where The generated `IsProviderFor` impl is a copy of the provider impl's signature with its body removed and the same `where` clause retained, so the provider's dependencies are captured for error reporting. The first argument is the component name, the second is the context type, and the third is a tuple of any remaining provider-trait type parameters. For a provider trait with multiple parameters, such as `ComputerRef`, the trailing parameters are grouped into that tuple — the `IsProviderFor` impl reads `IsProviderFor`. -One special case bypasses the provider rewrite entirely. Writing `#[cgp_impl(Self)]` — naming `Self` as the provider — emits the `impl` block unchanged as an ordinary consumer-trait implementation on the concrete context. This requires the `for Context` clause to be present, and it is useful when you want to implement a consumer trait directly while still applying companion attributes such as [`#[use_provider]`](../attributes/use_provider.md): +One special case bypasses the provider rewrite entirely. Writing `#[cgp_impl(Self)]` — naming `Self` as the provider — emits the `impl` block unchanged as an ordinary consumer-trait implementation on the concrete context. This requires the `for Context` clause to be present, and it is useful when you want to implement a consumer trait directly while still applying companion attributes such as [`#[use_provider]`](../attributes/use_provider.md). Because no provider struct is generated in this form, the `new` keyword and the `: ComponentType` override have no effect when the provider is `Self`: ```rust #[cgp_impl(Self)] @@ -173,7 +173,7 @@ The call `rect.area()` resolves through the consumer blanket impl to `Rectangle` ## Related constructs -`#[cgp_impl]` is the recommended way to implement a component defined by [`#[cgp_component]`](cgp_component.md), and it is one layer of sugar above [`#[cgp_provider]`](cgp_provider.md), which it desugars to; [`#[cgp_new_provider]`](cgp_new_provider.md) is the `new`-keyword equivalent at that lower layer. For the common case where only a single implementation is ever needed and no wiring is desired, [`#[cgp_fn]`](cgp_fn.md) is the lighter alternative. The companion attributes [`#[implicit]`](../attributes/implicit.md), [`#[uses]`](../attributes/uses.md), [`#[use_type]`](../attributes/use_type.md), and [`#[use_provider]`](../attributes/use_provider.md) all apply inside a `#[cgp_impl]` block. Once a provider is written, [`delegate_components!`](delegate_components.md) wires it onto a context and [`check_components!`](check_components.md) verifies the wiring. +`#[cgp_impl]` is the recommended way to implement a component defined by [`#[cgp_component]`](cgp_component.md), and it is one layer of sugar above [`#[cgp_provider]`](cgp_provider.md), which it desugars to; [`#[cgp_new_provider]`](cgp_new_provider.md) is the `new`-keyword equivalent at that lower layer. For the common case where only a single implementation is ever needed and no wiring is desired, [`#[cgp_fn]`](cgp_fn.md) is the lighter alternative. The companion attributes [`#[implicit]`](../attributes/implicit.md), [`#[uses]`](../attributes/uses.md), [`#[use_type]`](../attributes/use_type.md), [`#[use_provider]`](../attributes/use_provider.md), and [`#[default_impl]`](../traits/default_namespace.md) all apply inside a `#[cgp_impl]` block. Once a provider is written, [`delegate_components!`](delegate_components.md) wires it onto a context and [`check_components!`](check_components.md) verifies the wiring. ## Source diff --git a/docs/reference/macros/check_components.md b/docs/reference/macros/check_components.md index e8a785f5..06458543 100644 --- a/docs/reference/macros/check_components.md +++ b/docs/reference/macros/check_components.md @@ -45,9 +45,9 @@ check_components! { } ``` -The check trait's name can be set with `#[check_trait(Name)]` on the table. The macro otherwise derives a name of the form `__Check{Context}` (for example `__CheckPerson`), so the override is needed when two `check_components!` tables in the same module would otherwise collide. A leading `<...>` generic list and a trailing `where` clause may also be attached to a table to introduce and constrain generics used by the checked parameters. +The check trait's name can be set with `#[check_trait(Name)]` on the table. The macro otherwise derives a name of the form `__Check{Context}` (for example `__CheckPerson`) from the final segment of the context type's path, so a path-qualified context such as `some_mod::Person` also yields `__CheckPerson`; the override is needed when two `check_components!` tables in the same module would otherwise collide. A leading `<...>` generic list and a trailing `where` clause may also be attached to a table to introduce and constrain generics used by the checked parameters. -A `#[check_providers(...)]` attribute changes what is checked: instead of verifying the context, it verifies that each listed provider is a provider for the context. This is the form to reach for when a higher-order provider needs each layer checked separately, since each provider in the list is asserted independently. +A `#[check_providers(...)]` attribute changes what is checked: instead of verifying the context, it verifies that each listed provider is a provider for the context. This is the form to reach for when a higher-order provider needs each layer checked separately, since each provider in the list is asserted independently. It must list at least one provider and may appear at most once on a table; an empty list or a repeated attribute is a compile error. ## Syntax Grammar diff --git a/docs/reference/macros/delegate_components.md b/docs/reference/macros/delegate_components.md index 883ddd6a..64c2edce 100644 --- a/docs/reference/macros/delegate_components.md +++ b/docs/reference/macros/delegate_components.md @@ -40,12 +40,12 @@ delegate_components! { A leading `<...>` generic list on the target makes the whole table generic, so the same wiring can apply across a family of context types — for example `delegate_components! { MyContext { ... } }` wires every `MyContext` at once. -The recommended way to dispatch a component on its generic parameter is the `open` statement, which folds the per-value entries directly into the context's own table. A leading `open { AreaCalculatorComponent };` header opens one or more components for per-key wiring, after which a `@`-path entry such as `@AreaCalculatorComponent.Rectangle: RectangleArea` assigns a provider to a single value of that component's dispatch parameter: +The recommended way to dispatch a component on its generic parameter is the `open` statement, which folds the per-value entries directly into the context's own table. A leading `open AreaCalculatorComponent;` header opens one or more components for per-key wiring, after which a `@`-path entry such as `@AreaCalculatorComponent.Rectangle: RectangleArea` assigns a provider to a single value of that component's dispatch parameter. The braces are optional when opening a single component, so `open AreaCalculatorComponent;` is equivalent to `open { AreaCalculatorComponent };`; the braced list is only required to open several components at once: ```rust delegate_components! { MyApp { - open {AreaCalculatorComponent}; + open AreaCalculatorComponent; @AreaCalculatorComponent.Rectangle: RectangleArea, @AreaCalculatorComponent.Circle: CircleArea, @@ -73,7 +73,7 @@ This wires `MyApp` to calculate the area of a `Rectangle` through `RectangleArea Beyond plain `Key: Value` entries and `open`, the table body also accepts the other namespace-oriented statement forms used to opt a context into a [`#[cgp_namespace]`](cgp_namespace.md): a leading `namespace SomeNamespace;` header that forwards every lookup through that namespace, `@`-path keys such as `@app.ErrorRaiserComponent` that target a route rather than a bare component name, and `for in SomeTable { ... }` loops that pull entries out of another lookup table. These forms are described under [`#[cgp_namespace]`](cgp_namespace.md), where they are most often written. -The macro accepts no attributes on the table or its keys and rejects any it finds. Attribute-driven variants such as `#[check_params(...)]` and `#[skip_check]` belong to [`delegate_and_check_components!`](delegate_and_check_components.md), not here. +The macro accepts no attributes on the table or on any of its keys — including keys nested inside a `UseDelegate` value — and rejects any it finds with a spanned "unsupported attribute" error rather than discarding it. Attribute-driven variants such as `#[check_params(...)]` and `#[skip_check]` belong to [`delegate_and_check_components!`](delegate_and_check_components.md), not here. ## Syntax Grammar @@ -88,7 +88,7 @@ TableBody -> Statement* ( Mapping ( `,` Mapping )* `,`? )? Statement -> OpenStmt | NamespaceStmt | ForStmt -OpenStmt -> `open` `{` Type ( `,` Type )* `,`? `}` `;` // NamespaceStmt, ForStmt — see #[cgp_namespace] +OpenStmt -> `open` ( `{` Type ( `,` Type )* `,`? `}` | Type ) `;` // NamespaceStmt, ForStmt — see #[cgp_namespace] Mapping -> Key `:` ProviderValue | Key `->` ProviderValue @@ -105,7 +105,7 @@ ProviderValue -> Type Path -> `@` PathSegment ( `.` PathSegment )* // see Path! ``` -A leading `Generics` list (a Rust `< … >`) makes the whole table generic over the target; the `new` keyword additionally emits the target struct. Each `Mapping` chooses one of three operators: `` `:` `` maps a key directly to the named provider — the common form — while `` `->` `` delegates to the value's own entry for that key and `` `=>` `` redirects the lookup along an `@`-`Path`; the operators other than `:` are used mainly by the namespace machinery and detailed under [`#[cgp_namespace]`](cgp_namespace.md). A `Key` may be a single type, a bracketed list expanding to one entry per name, or an `@`-`PathKey`. The nested-table `ProviderValue` form wires the key to a `UseDelegate`-style wrapper while defining the inner table in place. `Mapping`, `Key`, and `ProviderValue` are the shared productions reused by [`delegate_and_check_components!`](delegate_and_check_components.md) and [`#[cgp_namespace]`](cgp_namespace.md). An `OpenStmt` opens each listed component for per-value wiring directly in the context's table, after which `@Component.Key` path keys populate it; the `NamespaceStmt` and `ForStmt` statement forms and the `Path` segment rules are defined under [`#[cgp_namespace]`](cgp_namespace.md) and [`Path!`](path.md). The macro accepts no attributes on the table or its entries and rejects any it finds. +A leading `Generics` list (a Rust `< … >`) makes the whole table generic over the target; the `new` keyword additionally emits the target struct. Each `Mapping` chooses one of three operators: `` `:` `` maps a key directly to the named provider — the common form — while `` `->` `` delegates to the value's own entry for that key and `` `=>` `` redirects the lookup along an `@`-`Path`; the operators other than `:` are used mainly by the namespace machinery and detailed under [`#[cgp_namespace]`](cgp_namespace.md). A `Key` may be a single type, a bracketed list expanding to one entry per name, or an `@`-`PathKey`. The nested-table `ProviderValue` form wires the key to a `UseDelegate`-style wrapper while defining the inner table in place. `Mapping`, `Key`, and `ProviderValue` are the shared productions reused by [`delegate_and_check_components!`](delegate_and_check_components.md) and [`#[cgp_namespace]`](cgp_namespace.md). An `OpenStmt` opens each listed component for per-value wiring directly in the context's table, after which `@Component.Key` path keys populate it; its brace-delimited list may be written without braces when it opens exactly one component (`open Component;`), while opening several at once requires the braces. The `NamespaceStmt` and `ForStmt` statement forms and the `Path` segment rules are defined under [`#[cgp_namespace]`](cgp_namespace.md) and [`Path!`](path.md). The macro accepts no attributes on the table or its entries and rejects any it finds. ## Expansion @@ -178,7 +178,7 @@ The `open` statement expands each listed component to a redirect entry, with the ```rust delegate_components! { MyApp { - open {AreaCalculatorComponent}; + open AreaCalculatorComponent; @AreaCalculatorComponent.Rectangle: RectangleArea, @AreaCalculatorComponent.Circle: CircleArea, @@ -186,7 +186,7 @@ delegate_components! { } ``` -the `open { AreaCalculatorComponent };` header wires `AreaCalculatorComponent` to a `RedirectLookup` rooted at the component name inside `MyApp`'s own table: +the `open AreaCalculatorComponent;` header wires `AreaCalculatorComponent` to a `RedirectLookup` rooted at the component name inside `MyApp`'s own table: ```rust impl DelegateComponent for MyApp { diff --git a/docs/skills/cgp/SKILL.md b/docs/skills/cgp/SKILL.md index 8a184063..187aa166 100644 --- a/docs/skills/cgp/SKILL.md +++ b/docs/skills/cgp/SKILL.md @@ -245,7 +245,7 @@ OOP-style inheritance rather than a capability import; and import abstract types supertraits) with [`#[use_type]`](abstract-types.md), writing the bare alias (`Scalar`, `Error`) rather than declaring the trait as a hand-written supertrait or `where Self: HasScalarType` bound and then qualifying every use as `Self::Scalar`. This applies even to `#[cgp_component]` trait definitions: prefer -`#[use_type(HasErrorType::Error)]` over `: HasErrorType` + `Self::Error`, since the attribute adds the +`#[use_type(HasErrorType.Error)]` over `: HasErrorType` + `Self::Error`, since the attribute adds the supertrait for you and lets the signatures read in the bare form. When defining a new dispatchable component, skip `#[derive_delegate]`/`UseDelegate` and dispatch through the `open` statement or a namespace instead. The explicit forms remain correct @@ -316,7 +316,7 @@ shape like this: ```rust delegate_components! { MyApp { - open { AreaCalculatorComponent }; + open AreaCalculatorComponent; @AreaCalculatorComponent.Rectangle: RectangleArea, @AreaCalculatorComponent.Circle: CircleArea, @@ -324,8 +324,10 @@ delegate_components! { } ``` -The `open { … };` header opens one or more components for per-value wiring and **must lead** the -block (it comes before any plain `Component: Provider` mappings, or the macro fails to parse). Each +The `open … ;` header opens one or more components for per-value wiring and **must lead** the +block (it comes before any plain `Component: Provider` mappings, or the macro fails to parse). The +braces are optional when opening a single component (`open AreaCalculatorComponent;`); use the +braced list `open { A, B };` to open several at once. Each `@Component.Key: Provider` entry then assigns a provider for one value of the dispatch parameter; a brace group on the final segment shares one provider across several values (`@AreaCalculatorComponent.{u32, u64, bool}: SomeProvider`), and a key may carry generics @@ -519,7 +521,7 @@ delegate_components! { The direct impl is just as valid and shows that abstract types are ordinary associated-type traits. -The `#[use_type(HasScalarType::Scalar)]` attribute is the recommended way to *use* an abstract type +The `#[use_type(HasScalarType.Scalar)]` attribute is the recommended way to *use* an abstract type inside `#[cgp_fn]`/`#[cgp_impl]`/`#[cgp_component]`: it rewrites bare `Scalar` to the fully-qualified `::Scalar` everywhere and adds the supertrait/where bound, removing `Self::` boilerplate and ambiguity. CGP's built-in abstract-type component is `HasType` (provider @@ -574,19 +576,19 @@ CGP makes the error type abstract so generic code can fail without naming a conc error type; `CanRaiseError` constructs it from a concrete source error (`Context::raise_error(source)`); `CanWrapError` attaches detail. Both build on `HasErrorType` and are associated-function (no `self`) components that dispatch per source/detail -type. An error-aware trait imports that error type with `#[use_type(HasErrorType::Error)]`, so it +type. An error-aware trait imports that error type with `#[use_type(HasErrorType.Error)]`, so it names the error as the bare `Error` instead of writing `: HasErrorType` and `Self::Error` by hand: ```rust #[cgp_component(Loader)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] pub trait CanLoad { fn load(&self, path: &str) -> Result; } #[cgp_impl(new LoadOrFail)] #[uses(CanRaiseError)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] impl Loader { fn load(&self, path: &str) -> Result { if path.is_empty() { @@ -605,7 +607,7 @@ A context wires its error type and the raise/wrap behavior. The backend provider ```rust delegate_components! { App { - open { ErrorRaiserComponent }; + open ErrorRaiserComponent; ErrorTypeProviderComponent: UseType, @ErrorRaiserComponent.String: RaiseFrom, diff --git a/docs/skills/cgp/references/abstract-types.md b/docs/skills/cgp/references/abstract-types.md index 7fdba373..542b5d56 100644 --- a/docs/skills/cgp/references/abstract-types.md +++ b/docs/skills/cgp/references/abstract-types.md @@ -100,7 +100,7 @@ A context implementing this through its field wiring supplies both the concrete The strongly recommended way to *refer to* an abstract type from another definition is the `#[use_type]` attribute. A provider or component often needs a type that lives on a different trait — a `Scalar` from `HasScalarType`, an `Error` from `HasErrorType` — and Rust requires every mention to be written in fully-qualified form, `::Scalar`, because a bare `Scalar` is not a type the compiler knows. Writing that prefix on every occurrence is verbose and easy to get wrong. -`#[use_type]` lets you write the bare identifier everywhere and have the macro expand it. You declare the import once alongside `#[cgp_fn]`, `#[cgp_impl]`, or `#[cgp_component]`, and the macro rewrites each standalone `Scalar` into `::Scalar` while also adding `HasScalarType` as a supertrait (for `#[cgp_component]`) or a `where`-clause bound (for `#[cgp_impl]` and `#[cgp_fn]`). Consider a `rectangle_area` function that multiplies two implicit fields: +`#[use_type]` lets you write the bare identifier everywhere and have the macro expand it. You declare the import once alongside `#[cgp_fn]`, `#[cgp_impl]`, or `#[cgp_component]` as `#[use_type(Trait.AssocType)]` — a `.` (not `::`) separates the trait from the associated type — and the macro rewrites each standalone `Scalar` into `::Scalar` while also adding `HasScalarType` as a supertrait (for `#[cgp_component]`) or a `where`-clause bound (for `#[cgp_impl]` and `#[cgp_fn]`). The `.` separator keeps the trait unambiguous even when it is a full path or carries generic arguments (`errors::HasErrorType.Error`, `HasFooType.Foo`). Consider a `rectangle_area` function that multiplies two implicit fields: ```rust pub trait HasScalarType { @@ -108,7 +108,7 @@ pub trait HasScalarType { } #[cgp_fn] -#[use_type(HasScalarType::Scalar)] +#[use_type(HasScalarType.Scalar)] fn rectangle_area( &self, #[implicit] width: Scalar, @@ -143,7 +143,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]`. +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. ## Sharing one type across contexts diff --git a/docs/skills/cgp/references/error-handling.md b/docs/skills/cgp/references/error-handling.md index ea6fe7f9..993b84d7 100644 --- a/docs/skills/cgp/references/error-handling.md +++ b/docs/skills/cgp/references/error-handling.md @@ -21,13 +21,13 @@ pub type ErrorOf = ::Error; The `Debug` bound lets `Self::Error` flow into `.unwrap()` and straightforward logging without an extra constraint, and it is enforced on whatever concrete type a context chooses. `ErrorOf` is the convenient spelling of the associated-type path. Generic code that may fail returns `Result` (or `Result>`) and never names a concrete error. -Centralizing the error type on one trait is what lets errors compose. A context trait that may fail depends on `HasErrorType`, so every such trait refers to the *same* error; if each declared its own associated `Error`, a context bounded by several of them would face several incompatible error types with no way to unify them. `HasErrorType` carries no methods — it only declares the type. The behavior of producing errors lives in the traits that build on it. The preferred way to author such a trait is [`#[use_type(HasErrorType::Error)]`](abstract-types.md), which adds `HasErrorType` as a supertrait *and* rewrites a bare `Error` in the signatures to `::Error` — so you write neither `: HasErrorType` nor `Self::Error` by hand. +Centralizing the error type on one trait is what lets errors compose. A context trait that may fail depends on `HasErrorType`, so every such trait refers to the *same* error; if each declared its own associated `Error`, a context bounded by several of them would face several incompatible error types with no way to unify them. `HasErrorType` carries no methods — it only declares the type. The behavior of producing errors lives in the traits that build on it. The preferred way to author such a trait is [`#[use_type(HasErrorType.Error)]`](abstract-types.md), which adds `HasErrorType` as a supertrait *and* rewrites a bare `Error` in the signatures to `::Error` — so you write neither `: HasErrorType` nor `Self::Error` by hand. Because `#[cgp_type]` generates a `UseType` blanket impl, a context fixes its error type by wiring the error-type component to `UseType`, exactly as for any abstract type: ```rust #[cgp_component(Validator)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] pub trait CanValidate { fn validate(&self) -> Result<(), Error>; } @@ -41,23 +41,23 @@ delegate_components! { } ``` -Here `#[use_type(HasErrorType::Error)]` makes `CanValidate` depend on the shared abstract error and lets `validate` name it as the bare `Error`, and `App` fixes that error to `String`. The standalone backends (`cgp-error-anyhow`, `cgp-error-eyre`, `cgp-error-std`) supply ready-made providers that set `Error` to their respective library types instead. A context can equally implement the trait directly — `impl HasErrorType for App { type Error = String; }` — which makes plain that it is an ordinary trait with a `Debug`-bounded associated type. +Here `#[use_type(HasErrorType.Error)]` makes `CanValidate` depend on the shared abstract error and lets `validate` name it as the bare `Error`, and `App` fixes that error to `String`. The standalone backends (`cgp-error-anyhow`, `cgp-error-eyre`, `cgp-error-std`) supply ready-made providers that set `Error` to their respective library types instead. A context can equally implement the trait directly — `impl HasErrorType for App { type Error = String; }` — which makes plain that it is an ordinary trait with a `Debug`-bounded associated type. ## `CanRaiseError` and `CanWrapError`: producing and enriching the error -`CanRaiseError` is the consumer trait for turning a concrete source error into the context's abstract error, and `CanWrapError` is the companion that attaches detail to an existing one. Both import the error type with `#[use_type(HasErrorType::Error)]` — so they name it as the bare `Error` and gain `HasErrorType` as a supertrait — and both are `#[cgp_component]`s that delegate per type so a context can handle each source error or detail with a different provider: +`CanRaiseError` is the consumer trait for turning a concrete source error into the context's abstract error, and `CanWrapError` is the companion that attaches detail to an existing one. Both import the error type with `#[use_type(HasErrorType.Error)]` — so they name it as the bare `Error` and gain `HasErrorType` as a supertrait — and both are `#[cgp_component]`s that delegate per type so a context can handle each source error or detail with a different provider: ```rust #[cgp_component(ErrorRaiser)] #[derive_delegate(UseDelegate)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] pub trait CanRaiseError { fn raise_error(error: SourceError) -> Error; } #[cgp_component(ErrorWrapper)] #[derive_delegate(UseDelegate)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] pub trait CanWrapError { fn wrap_error(error: Error, detail: Detail) -> Error; } @@ -69,14 +69,14 @@ A provider written against these bounds names neither the context nor its concre ```rust #[cgp_component(Loader)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] pub trait CanLoad { fn load(&self, path: &str) -> Result; } #[cgp_impl(new LoadOrFail)] #[uses(CanRaiseError, CanWrapError)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] impl Loader { fn load(&self, path: &str) -> Result { if path.is_empty() { @@ -116,7 +116,7 @@ The string-formatting providers are designed to *compose* with the others rather ```rust delegate_components! { App { - open { ErrorRaiserComponent }; + open ErrorRaiserComponent; @ErrorRaiserComponent.String: RaiseFrom, @ErrorRaiserComponent.ParseError: DebugError, @@ -124,7 +124,7 @@ delegate_components! { } ``` -The `open { ErrorRaiserComponent };` header opens the component for per-type wiring, and each `@ErrorRaiserComponent.: Provider` entry assigns the provider for one source-error type, folded directly into `App`'s own table — `open` needs no `#[derive_delegate]` of its own because every `#[cgp_component]` already generates the `RedirectLookup` impl it dispatches through. The legacy equivalent writes the same per-type entries into a separate `UseDelegate` nested table; that form is still common in existing code but is slated for deprecation, so prefer `open` for new wiring. See [wiring](wiring.md) for both forms. +The `open ErrorRaiserComponent;` header opens the component for per-type wiring, and each `@ErrorRaiserComponent.: Provider` entry assigns the provider for one source-error type, folded directly into `App`'s own table — `open` needs no `#[derive_delegate]` of its own because every `#[cgp_component]` already generates the `RedirectLookup` impl it dispatches through. The legacy equivalent writes the same per-type entries into a separate `UseDelegate` nested table; that form is still common in existing code but is slated for deprecation, so prefer `open` for new wiring. See [wiring](wiring.md) for both forms. A raised `String` is converted straight into the abstract error by `RaiseFrom`, while a raised `ParseError` is formatted with `Debug` by `DebugError` and then routed back through the `String` entry — which `RaiseFrom` handles — yielding one coherent error type from two unrelated sources. The choice of provider is therefore also a statement about which source errors the context accepts and how, and each wiring is verified with `check_components!` like any other. diff --git a/docs/skills/cgp/references/functions-and-getters.md b/docs/skills/cgp/references/functions-and-getters.md index e23a9057..cb073be2 100644 --- a/docs/skills/cgp/references/functions-and-getters.md +++ b/docs/skills/cgp/references/functions-and-getters.md @@ -124,7 +124,7 @@ pub fn greet(&self, #[implicit] name: &str) { // binding: let name: &str = self.get_field(PhantomData::).as_str(); ``` -Three rules constrain where `#[implicit]` may appear. The function must take `self` as its first argument, since the field is read from it; the argument pattern must be a bare identifier, not a destructuring or `mut` pattern (clone inside the body for a mutable local); and a `&mut self` receiver allows at most one implicit argument, since each borrows from the same context. `#[implicit]` is usable in both `#[cgp_fn]` and the methods of a `#[cgp_impl]` provider, with the same desugaring in each — inside `#[cgp_impl]` the `HasField` bounds simply join the provider impl's `where` clause. +Three rules constrain where `#[implicit]` may appear. The function must take `self` as its first argument, since the field is read from it; the argument pattern must be a bare identifier, not a destructuring or `mut` pattern (clone inside the body for a mutable local); and a `&mut T` implicit argument must be the *only* implicit argument (and needs a `&mut self` receiver), since it reads through `get_field_mut` and borrows the whole context exclusively — immutable implicits are shared borrows and combine freely in any number. The access mode follows the argument's own type, not the receiver's: only a `&mut T` argument reads through `get_field_mut`. `#[implicit]` is usable in both `#[cgp_fn]` and the methods of a `#[cgp_impl]` provider, with the same desugaring in each — inside `#[cgp_impl]` the `HasField` bounds simply join the provider impl's `where` clause. ## Importing capabilities: `#[uses]` diff --git a/docs/skills/cgp/references/handlers.md b/docs/skills/cgp/references/handlers.md index 5c574baf..e528d603 100644 --- a/docs/skills/cgp/references/handlers.md +++ b/docs/skills/cgp/references/handlers.md @@ -34,7 +34,7 @@ The provider trait `Computer` moves the context into an ex #[cgp_component(TryComputer)] #[derive_delegate(UseDelegate)] #[derive_delegate(UseInputDelegate)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] pub trait CanTryCompute { type Output; fn try_compute(&self, _code: PhantomData, input: Input) @@ -51,7 +51,7 @@ A `TryComputer` provider carries a `Context: HasErrorType` bound and typically c #[cgp_component(Handler)] #[derive_delegate(UseDelegate)] #[derive_delegate(UseInputDelegate)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] pub trait CanHandle { type Output; async fn handle(&self, _tag: PhantomData, input: Input) @@ -84,7 +84,7 @@ The runner pair executes a unit of work selected by a `Code` tag rather than tra #[cgp_component(Runner)] #[async_trait] #[derive_delegate(UseDelegate)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] pub trait CanRun { async fn run(&self, _code: PhantomData) -> Result<(), Error>; } @@ -92,7 +92,7 @@ pub trait CanRun { #[cgp_component(SendRunner)] #[async_trait] #[derive_delegate(UseDelegate)] -#[use_type(HasErrorType::Error)] +#[use_type(HasErrorType.Error)] pub trait CanSendRun { fn send_run(&self, _code: PhantomData) -> impl Future> + Send; diff --git a/docs/skills/cgp/references/higher-order-providers.md b/docs/skills/cgp/references/higher-order-providers.md index ab11d50e..eb96698d 100644 --- a/docs/skills/cgp/references/higher-order-providers.md +++ b/docs/skills/cgp/references/higher-order-providers.md @@ -89,7 +89,7 @@ When the right provider depends on *which* concrete type a generic parameter is ```rust delegate_components! { MyApp { - open { AreaCalculatorComponent }; + open AreaCalculatorComponent; @AreaCalculatorComponent.Rectangle: RectangleArea, @AreaCalculatorComponent.Circle: CircleArea, @@ -97,7 +97,7 @@ delegate_components! { } ``` -The leading `open { AreaCalculatorComponent };` header opens the component for per-value wiring, and each `@AreaCalculatorComponent.Key: Provider` entry assigns a provider for one value of the dispatch parameter: when `Shape` is `Rectangle`, `MyApp` calculates area through `RectangleArea`, and `Circle` resolves to `CircleArea`. After this wiring, `MyApp` implements `CanCalculateArea` through `RectangleArea` and `CanCalculateArea` through `CircleArea`, with the dispatch parameter selecting between them. Adding a shape is one more entry; the providers stay untouched. The `open` form needs no extra macro on the component — it rides the `RedirectLookup` impl that every `#[cgp_component]` already generates, so dispatching a component per type requires no `#[derive_delegate]` on the trait. Two shorthands keep the entries compact: an array on the final path segment shares one provider across several values (`@AreaCalculatorComponent.[Rectangle, Circle]: SomeProvider`), and generic parameters precede the dispatch value when it needs them (`@AreaCalculatorComponent.<'a, T> &'a T: SomeProvider`). The [wiring](wiring.md) reference is the canonical home of the `open` statement and its grammar. +The leading `open AreaCalculatorComponent;` header opens the component for per-value wiring, and each `@AreaCalculatorComponent.Key: Provider` entry assigns a provider for one value of the dispatch parameter: when `Shape` is `Rectangle`, `MyApp` calculates area through `RectangleArea`, and `Circle` resolves to `CircleArea`. After this wiring, `MyApp` implements `CanCalculateArea` through `RectangleArea` and `CanCalculateArea` through `CircleArea`, with the dispatch parameter selecting between them. Adding a shape is one more entry; the providers stay untouched. The `open` form needs no extra macro on the component — it rides the `RedirectLookup` impl that every `#[cgp_component]` already generates, so dispatching a component per type requires no `#[derive_delegate]` on the trait. Two shorthands keep the entries compact: an array on the final path segment shares one provider across several values (`@AreaCalculatorComponent.[Rectangle, Circle]: SomeProvider`), and generic parameters precede the dispatch value when it needs them (`@AreaCalculatorComponent.<'a, T> &'a T: SomeProvider`). The [wiring](wiring.md) reference is the canonical home of the `open` statement and its grammar. ### Legacy: `derive_delegate` and `UseDelegate` nested tables @@ -137,7 +137,7 @@ The real leverage of generic-parameter dispatch appears when the main target of ```rust #[cgp_component(AreaCalculator)] -#[use_type(HasScalarType::Scalar)] +#[use_type(HasScalarType.Scalar)] pub trait CanCalculateAreaOfShape { fn area_of_shape(&self, shape: &Shape) -> Scalar; } diff --git a/docs/skills/cgp/references/modularity-hierarchy.md b/docs/skills/cgp/references/modularity-hierarchy.md index 855a9bbc..f3a665fa 100644 --- a/docs/skills/cgp/references/modularity-hierarchy.md +++ b/docs/skills/cgp/references/modularity-hierarchy.md @@ -101,7 +101,7 @@ pub trait CanSerializeValue { delegate_components! { new MyAppA { - open { ValueSerializerComponent }; + open ValueSerializerComponent; @ValueSerializerComponent.Vec: SerializeBytes, @ValueSerializerComponent.Vec: SerializeIterator, @@ -110,7 +110,7 @@ delegate_components! { delegate_components! { new MyAppB { - open { ValueSerializerComponent }; + open ValueSerializerComponent; @ValueSerializerComponent.Vec: SerializeHex, @ValueSerializerComponent.Vec: SerializeIterator, @@ -118,7 +118,7 @@ delegate_components! { } ``` -The `open { ValueSerializerComponent };` header opens the component for per-value wiring, and each `@ValueSerializerComponent.Value: Provider` entry assigns a provider for one concrete value type. The gain is that `MyAppA` and `MyAppB` resolve `Vec` to different providers — bytes versus hex — with no conflict, because each choice is coherent only within its own context. The orphan rule no longer applies: a context can wire `Vec` even when its crate owns neither `CanSerializeValue` nor `Vec`, as long as it owns the context type, so you never commit to a global serialization for `Vec` up front. The costs are that the trait must be modified to add the context parameter, and that every value type a context touches must be wired explicitly, which grows tedious for a large type set. +The `open ValueSerializerComponent;` header opens the component for per-value wiring, and each `@ValueSerializerComponent.Value: Provider` entry assigns a provider for one concrete value type. The gain is that `MyAppA` and `MyAppB` resolve `Vec` to different providers — bytes versus hex — with no conflict, because each choice is coherent only within its own context. The orphan rule no longer applies: a context can wire `Vec` even when its crate owns neither `CanSerializeValue` nor `Vec`, as long as it owns the context type, so you never commit to a global serialization for `Vec` up front. The costs are that the trait must be modified to add the context parameter, and that every value type a context touches must be wired explicitly, which grows tedious for a large type set. The `open` form rides the dispatch machinery that every `#[cgp_component]` already generates, so the trait needs no extra option. A legacy alternative writes the same dispatch with `derive_delegate: UseDelegate` on the trait and a `UseDelegate: SerializeBytes, ... }>` nested table in each context's wiring; it is retained for compatibility but `open` is preferred for new code, and the two forms appear side by side in [wiring](wiring.md). @@ -143,7 +143,7 @@ where delegate_components! { new MyAppA { - open { ValueSerializerComponent }; + open ValueSerializerComponent; @ValueSerializerComponent.Vec: SerializeBytes, @ValueSerializerComponent.Vec>: SerializeIteratorWith, diff --git a/docs/skills/cgp/references/wiring.md b/docs/skills/cgp/references/wiring.md index 546ada4c..413b6fde 100644 --- a/docs/skills/cgp/references/wiring.md +++ b/docs/skills/cgp/references/wiring.md @@ -139,7 +139,7 @@ When a provider trait carries an extra generic parameter and the right provider ```rust delegate_components! { MyApp { - open { AreaCalculatorComponent }; + open AreaCalculatorComponent; @AreaCalculatorComponent.Rectangle: RectangleArea, @AreaCalculatorComponent.Circle: CircleArea, @@ -147,7 +147,7 @@ delegate_components! { } ``` -The leading `open { AreaCalculatorComponent };` header opens one or more components for per-value wiring — list several inside the braces to open them together. The header is a *leading* statement: when a single block mixes plain `Component: Provider` mappings with an `open` block, the `open { … };` header must come first, before any plain mappings, or the macro fails to parse. Each subsequent `@Component.Key: Provider` entry then assigns a provider for one value of that component's dispatch parameter: `@AreaCalculatorComponent.Rectangle: RectangleArea` says that when `Shape` is `Rectangle`, `MyApp` calculates area through `RectangleArea`, and the `Circle` line does the same for `Circle`. After this wiring, `MyApp` implements `CanCalculateArea` via `RectangleArea` and `CanCalculateArea` via `CircleArea`. +The leading `open AreaCalculatorComponent;` header opens one or more components for per-value wiring. The braces are optional when opening a single component, so `open AreaCalculatorComponent;` and `open { AreaCalculatorComponent };` are equivalent; to open several components together, list them inside the braces (`open { A, B };`). The header is a *leading* statement: when a single block mixes plain `Component: Provider` mappings with an `open` block, the `open` header must come first, before any plain mappings, or the macro fails to parse. Each subsequent `@Component.Key: Provider` entry then assigns a provider for one value of that component's dispatch parameter: `@AreaCalculatorComponent.Rectangle: RectangleArea` says that when `Shape` is `Rectangle`, `MyApp` calculates area through `RectangleArea`, and the `Circle` line does the same for `Circle`. After this wiring, `MyApp` implements `CanCalculateArea` via `RectangleArea` and `CanCalculateArea` via `CircleArea`. Two shorthands keep the entries compact. When several values of the dispatch parameter share one provider, an array on the final path segment expands to one entry each — `@AreaCalculatorComponent.[Rectangle, Circle]: SomeProvider` wires both shapes to `SomeProvider`. When a dispatch value needs generic parameters of its own, they precede the value: `@SomeComponent.<'a, T> &'a T: SomeProvider` dispatches on the type `&'a T` for all `'a` and `T`.