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 63a041cb..91f39165 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,13 +1,14 @@ use syn::parse::{Parse, ParseStream}; -use syn::token::{At, Brace, Comma, Dot}; +use syn::token::{Brace, Comma, Dot, In}; use syn::{Ident, Type}; use crate::parse_internal; use crate::types::attributes::UseTypeIdent; 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. +/// One `#[use_type(...)]` import spec: the owning trait path, one or more +/// associated types to import from it, and a rewrite target (`Self`, or a named +/// type set by a trailing `in Context`). #[derive(Clone)] pub struct UseTypeAttribute { pub context_type: Type, @@ -31,19 +32,10 @@ impl UseTypeAttribute { impl Parse for UseTypeAttribute { fn parse(input: ParseStream) -> syn::Result { - // A `.` (not `::`) separates the context, trait, and associated type. This + // A `.` (not `::`) separates the trait from the 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()?; - let context: PathWithTypeArgs = input.parse()?; - let _: Dot = input.parse()?; - context.into() - } else { - parse_internal! { Self } - }; - let trait_path: PathWithTypeArgs = input.parse()?; let _: Dot = input.parse()?; @@ -59,6 +51,18 @@ impl Parse for UseTypeAttribute { vec![input.parse()?] }; + // An optional `in Context` suffix sets the rewrite target to a named + // type; `in` is a reserved keyword, so it can never be confused with a + // trait, type, or associated-type name and reads as a clean delimiter. + // Without the suffix, the target defaults to `Self`. + let context_type: Type = if input.peek(In) { + let _: In = input.parse()?; + let context: PathWithTypeArgs = input.parse()?; + context.into() + } else { + parse_internal! { Self } + }; + Ok(Self { context_type, trait_path, 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 93cfaa3a..ea81307a 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 @@ -1,13 +1,13 @@ use quote::ToTokens; use syn::visit_mut::VisitMut; -use syn::{ItemImpl, ItemTrait}; +use syn::{ItemImpl, ItemTrait, Type}; use crate::functions::parse_internal; use crate::types::attributes::UseTypeAttribute; use crate::types::attributes::use_type::type_predicates::{ derive_use_type_predicates, forbid_duplicate_aliases, }; -use crate::visitors::SubstituteAbstractType; +use crate::visitors::SubstituteAbstractTypes; #[derive(Default, Clone)] pub struct UseTypeAttributes { @@ -15,16 +15,37 @@ pub struct UseTypeAttributes { } impl UseTypeAttributes { - pub fn substitute_abstract_types_in_item_trait(&self, item_trait: &mut ItemTrait) { - for type_spec in self.attributes.iter().rev() { - SubstituteAbstractType { type_spec }.visit_item_trait_mut(item_trait); - } - } + /// Resolve every spec's context type into fully-qualified form before it is + /// used, so that both the body substitution and the appended bounds agree on + /// one grounded context. + /// + /// An `in Context` suffix whose `Context` is itself imported by another spec — + /// as in `#[use_type(HasTypes.Types, HasScalarType.Scalar in Types)]` — is + /// rewritten from the bare alias `Types` to `::Types`. + /// Contexts that name a real generic parameter or `Self` are left untouched. + /// The pass iterates to a fixpoint so a chain of links resolves fully; each + /// pass grounds one more level, so `attributes.len()` passes cover any + /// acyclic chain, and a cyclic reference simply stops making progress and + /// surfaces later as an ordinary unresolved-type error rather than looping. + fn grounded_specs(&self) -> Vec { + let mut grounded = self.attributes.clone(); + + for _ in 0..grounded.len() { + let snapshot = grounded.clone(); + let mut changed = false; + + for spec in grounded.iter_mut() { + let mut visitor = SubstituteAbstractTypes::new(&snapshot); + visitor.visit_type_mut(&mut spec.context_type); + changed |= visitor.is_changed; + } - pub fn substitute_abstract_types_in_item_impl(&self, item_impl: &mut ItemImpl) { - for type_spec in self.attributes.iter().rev() { - SubstituteAbstractType { type_spec }.visit_item_impl_mut(item_impl); + if !changed { + break; + } } + + grounded } pub fn transform_item_trait(&self, item_trait: &mut ItemTrait) -> syn::Result<()> { @@ -34,16 +55,39 @@ impl UseTypeAttributes { forbid_duplicate_aliases(&self.attributes)?; - self.substitute_abstract_types_in_item_trait(item_trait); - - for use_type in self.attributes.iter() { - if use_type.context_type != parse_internal! { Self } { - continue; + let grounded = self.grounded_specs(); + + SubstituteAbstractTypes::new(&grounded).visit_item_trait_mut(item_trait); + + let self_type: Type = parse_internal! { Self }; + + for use_type in grounded.iter() { + let trait_path = &use_type.trait_path; + + if use_type.context_type == self_type { + // A `Self`-context import becomes a supertrait of the generated + // trait, so the abstract type is available to every signature. + item_trait + .supertraits + .push(parse_internal(trait_path.to_token_stream())?); + } else { + // A foreign `in Context` import rewrites signatures to name + // `::Assoc`, so the trait must require + // `Context: Trait` for those paths to be well-formed. Without + // this bound the constraint would be silently dropped, leaving a + // signature that only compiles when `Context`'s bound happens to + // be supplied elsewhere. The type-equality (`= T`) form is an + // impl-side pin and is deliberately *not* added here. + let context_type = &use_type.context_type; + + item_trait + .generics + .make_where_clause() + .predicates + .push(parse_internal! { + #context_type: #trait_path + }); } - - item_trait - .supertraits - .push(parse_internal(use_type.trait_path.to_token_stream())?); } Ok(()) @@ -56,9 +100,11 @@ impl UseTypeAttributes { forbid_duplicate_aliases(&self.attributes)?; - self.substitute_abstract_types_in_item_impl(item_impl); + let grounded = self.grounded_specs(); + + SubstituteAbstractTypes::new(&grounded).visit_item_impl_mut(item_impl); - let predicates = derive_use_type_predicates(&self.attributes)?; + let predicates = derive_use_type_predicates(&grounded)?; item_impl .generics 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 c83538f1..ebb9cfdd 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 @@ -7,6 +7,11 @@ use syn::{Ident, Type, WherePredicate}; use crate::functions::parse_internal; use crate::types::attributes::{UseTypeAttribute, UseTypeIdent}; +/// Derive the impl-side `where` predicates a set of `#[use_type]` specs +/// contributes: one `Context: Trait` bound per spec, carrying any type-equality +/// (`= T`) pins as associated-type bindings. The specs' contexts must already be +/// grounded (see [`UseTypeAttributes::grounded_specs`]), so this reads +/// `use_type.context_type` directly rather than re-resolving aliases. pub fn derive_use_type_predicates(specs: &[UseTypeAttribute]) -> syn::Result> { let mut predicates = Vec::new(); @@ -14,13 +19,7 @@ pub fn derive_use_type_predicates(specs: &[UseTypeAttribute]) -> syn::Result syn::Result syn::Result> { - let Ok(context_ident) = parse_internal::(context_type.to_token_stream()) else { - return Ok(None); - }; - - for spec in specs { - for ident in spec.type_idents.iter() { - if ident.alias_ident() == &context_ident { - let new_context_type = &spec.context_type; - let type_ident = &ident.type_ident; - let trait_path = &spec.trait_path; - - let new_type = parse_internal! { - <#new_context_type as #trait_path>::#type_ident - }; - - return Ok(Some(new_type)); - } - } - } - - 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 diff --git a/crates/macros/cgp-macro-core/src/visitors/substitute_abstract_type.rs b/crates/macros/cgp-macro-core/src/visitors/substitute_abstract_type.rs index 59dbfd39..71d85f7c 100644 --- a/crates/macros/cgp-macro-core/src/visitors/substitute_abstract_type.rs +++ b/crates/macros/cgp-macro-core/src/visitors/substitute_abstract_type.rs @@ -3,24 +3,56 @@ use syn::{PathArguments, Type, TypePath, parse_quote, visit_mut}; use crate::types::attributes::UseTypeAttribute; -pub struct SubstituteAbstractType<'a> { - pub type_spec: &'a UseTypeAttribute, +/// A single-pass `VisitMut` that rewrites every bare, single-segment, +/// argument-free type path matching an imported alias into its fully-qualified +/// `::AssocType` form. +/// +/// Unlike a per-spec visitor, this holds *every* `#[use_type]` spec at once, so +/// one traversal of the item handles all imports regardless of the order they +/// were written. Because the imported aliases are guaranteed unique (see +/// `forbid_duplicate_aliases`), at most one spec can match a given identifier, +/// so the match order among specs is irrelevant. +/// +/// Each spec's `context_type` must already be *grounded* — resolved to a fully +/// qualified path (`::Types`) with no remaining bare alias — +/// before the visitor runs. Grounding is what lets a single traversal suffice: +/// the replacement a spec emits contains no bare alias, so the visitor never has +/// to revisit its own output to finish a nested import. +/// +/// `is_changed` records whether any replacement was made during the traversal, +/// which the grounding fixpoint reads to decide when a further pass would be a +/// no-op. +pub struct SubstituteAbstractTypes<'a> { + pub specs: &'a [UseTypeAttribute], + pub is_changed: bool, } -impl VisitMut for SubstituteAbstractType<'_> { +impl<'a> SubstituteAbstractTypes<'a> { + pub fn new(specs: &'a [UseTypeAttribute]) -> Self { + Self { + specs, + is_changed: false, + } + } +} + +impl VisitMut for SubstituteAbstractTypes<'_> { fn visit_type_mut(&mut self, ty: &mut Type) { if let Type::Path(TypePath { qself: None, path }) = ty && path.leading_colon.is_none() && path.segments.len() == 1 { let segment = &path.segments[0]; - if matches!(segment.arguments, PathArguments::None) - && let Some(replacement_ident) = self.type_spec.replace_ident(&segment.ident) - { - let trait_path = &self.type_spec.trait_path; - let context_type = &self.type_spec.context_type; - *ty = parse_quote! { <#context_type as #trait_path>::#replacement_ident }; - return; + if matches!(segment.arguments, PathArguments::None) { + for spec in self.specs { + if let Some(replacement_ident) = spec.replace_ident(&segment.ident) { + let trait_path = &spec.trait_path; + let context_type = &spec.context_type; + *ty = parse_quote! { <#context_type as #trait_path>::#replacement_ident }; + self.is_changed = true; + return; + } + } } } visit_mut::visit_type_mut(self, ty); diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_auto_getter/option_slice.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_auto_getter/option_slice.rs index 9e6ae5cd..5eafc81c 100644 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_auto_getter/option_slice.rs +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_auto_getter/option_slice.rs @@ -9,8 +9,8 @@ //! given a bespoke rule. The same boundary holds for `#[cgp_getter]` and for a //! `#[cgp_fn]` `#[implicit]` argument, since all three share `parse_field_type`. //! -//! See docs/implementation/entrypoints/cgp_auto_getter.md (Behavior and corner -//! cases). +//! See docs/errors/lowering/ill-formed-generated-type.md; the parser detail is in +//! docs/implementation/entrypoints/cgp_auto_getter.md (Behavior and corner cases). use cgp::prelude::*; diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/use_type_foreign_unsatisfied.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/use_type_foreign_unsatisfied.rs new file mode 100644 index 00000000..34e8c8fe --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/use_type_foreign_unsatisfied.rs @@ -0,0 +1,33 @@ +//! Acceptable failure: a foreign `#[use_type(HasScalarType.Scalar in Types)]` import +//! adds `Types: HasScalarType` to the generated trait, so naming the component for +//! a `Types` that does not implement `HasScalarType` is rejected by the compiler. +//! +//! This is the constraint that used to be *silently dropped* — before the trait +//! carried the foreign bound, `NoScalar` would have slipped through here and only +//! failed much later (or not at all, if the abstract type went unused). CGP is now +//! working as designed: it emits the bound and defers the actual check to `rustc`, +//! which reports the missing `NoScalar: HasScalarType` at the use site. +//! +//! See docs/reference/attributes/use_type.md and docs/errors/checks/check-trait-failure.md. + +use cgp::prelude::*; + +#[cgp_type] +pub trait HasScalarType { + type Scalar; +} + +#[cgp_component(AreaCalculator)] +#[use_type(HasScalarType.Scalar in Types)] +pub trait CanCalculateArea { + fn area(&self) -> Scalar; +} + +// `NoScalar` deliberately does not implement `HasScalarType`. +pub struct NoScalar; + +// Asserting the component for `NoScalar` requires `NoScalar: HasScalarType`, which +// the foreign import now demands — so this fails to compile. +pub trait CheckMissingScalar: CanCalculateArea {} + +fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/use_type_foreign_unsatisfied.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/use_type_foreign_unsatisfied.stderr new file mode 100644 index 00000000..8dea52a6 --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/use_type_foreign_unsatisfied.stderr @@ -0,0 +1,83 @@ +error[E0277]: the trait bound `NoScalar: HasScalarType` is not satisfied + --> tests/acceptable/cgp_component/use_type_foreign_unsatisfied.rs:31:31 + | +31 | pub trait CheckMissingScalar: CanCalculateArea {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ unsatisfied trait bound + | +help: the trait `DelegateComponent` is not implemented for `NoScalar` + --> tests/acceptable/cgp_component/use_type_foreign_unsatisfied.rs:27:1 + | +27 | pub struct NoScalar; + | ^^^^^^^^^^^^^^^^^^^ + = help: the following other types implement trait `DelegateComponent`: + `BuildAndMergeOutputs` implements `DelegateComponent` + `BuildAndMergeOutputs` implements `DelegateComponent` + `BuildAndMergeOutputs` implements `DelegateComponent` + `BuildAndMergeOutputs` implements `DelegateComponent` + `BuildAndMergeOutputs` implements `DelegateComponent` + `BuildAndMergeOutputs` implements `DelegateComponent` + `MatchFirstWithFieldHandlersInputs` implements `DelegateComponent<(Input, Args)>` + `MatchFirstWithFieldHandlersInputsMut` implements `DelegateComponent<(&'a mut Input, Args)>` + and $N others +note: required for `NoScalar` to implement `ScalarTypeProvider` + --> tests/acceptable/cgp_component/use_type_foreign_unsatisfied.rs:15:1 + | +15 | #[cgp_type] + | ^^^^^^^^^^^ +note: required for `NoScalar` to implement `HasScalarType` + --> tests/acceptable/cgp_component/use_type_foreign_unsatisfied.rs:15:1 + | +15 | #[cgp_type] + | ^^^^^^^^^^^ +16 | pub trait HasScalarType { + | ^^^^^^^^^^^^^ +note: required by a bound in `CanCalculateArea` + --> tests/acceptable/cgp_component/use_type_foreign_unsatisfied.rs:21:12 + | +21 | #[use_type(HasScalarType.Scalar in Types)] + | ^^^^^^^^^^^^^ required by this bound in `CanCalculateArea` +22 | pub trait CanCalculateArea { + | ---------------- required by a bound in this trait + = note: this error originates in the attribute macro `cgp_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `NoScalar: HasScalarType` is not satisfied + --> tests/acceptable/cgp_component/use_type_foreign_unsatisfied.rs:31:31 + | +31 | pub trait CheckMissingScalar: CanCalculateArea {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ unsatisfied trait bound + | +help: the trait `IsProviderFor` is not implemented for `NoScalar` + --> tests/acceptable/cgp_component/use_type_foreign_unsatisfied.rs:27:1 + | +27 | pub struct NoScalar; + | ^^^^^^^^^^^^^^^^^^^ + = help: the following other types implement trait `IsProviderFor`: + `BindErr` implements `IsProviderFor)>` + `BindErr` implements `IsProviderFor)>` + `BindOk` implements `IsProviderFor)>` + `BindOk` implements `IsProviderFor)>` + `BuildAndMerge` implements `IsProviderFor` + `BuildAndMerge` implements `IsProviderFor` + `BuildAndMerge` implements `IsProviderFor` + `BuildAndMergeOutputs` implements `IsProviderFor` + and $N others +note: required for `NoScalar` to implement `ScalarTypeProvider` + --> tests/acceptable/cgp_component/use_type_foreign_unsatisfied.rs:15:1 + | +15 | #[cgp_type] + | ^^^^^^^^^^^ +note: required for `NoScalar` to implement `HasScalarType` + --> tests/acceptable/cgp_component/use_type_foreign_unsatisfied.rs:15:1 + | +15 | #[cgp_type] + | ^^^^^^^^^^^ +16 | pub trait HasScalarType { + | ^^^^^^^^^^^^^ +note: required by a bound in `CanCalculateArea` + --> tests/acceptable/cgp_component/use_type_foreign_unsatisfied.rs:21:12 + | +21 | #[use_type(HasScalarType.Scalar in Types)] + | ^^^^^^^^^^^^^ required by this bound in `CanCalculateArea` +22 | pub trait CanCalculateArea { + | ---------------- required by a bound in this trait + = note: this error originates in the attribute macro `cgp_type` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_cyclic_context.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_cyclic_context.rs new file mode 100644 index 00000000..85e7a5e5 --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_cyclic_context.rs @@ -0,0 +1,37 @@ +//! Acceptable failure: two nested `#[use_type]` imports whose `in Context` clauses +//! reference *each other*, so there is no valid order in which to ground them. +//! +//! `HasA.A in B` resolves `A` against `B`, and `HasB.B in A` resolves `B` against +//! `A` — a cycle. Grounding runs to a fixpoint and deliberately stops making +//! progress on a cycle rather than looping, so the context aliases are never +//! resolved and the rewrite leaves the bare `A` and `B` from the `in` clauses in +//! type position. CGP lowers the input faithfully and defers to the compiler, +//! which reports `E0425` "cannot find type" with the caret on the unresolved +//! context alias the user wrote in the attribute. +//! +//! An *acyclic* chain in any order (`HasC.C in B, HasB.B in A, HasA.A` written +//! back-to-front) grounds fine — see the passing `use_type_fn_reverse_order` +//! behavioral test. Only a genuine cycle, which has no valid ordering, fails. +//! +//! See docs/reference/attributes/use_type.md and +//! docs/errors/lowering/unresolved-imported-type.md. + +use cgp::prelude::*; + +#[cgp_type] +pub trait HasA { + type A; +} + +#[cgp_type] +pub trait HasB { + type B; +} + +#[cgp_fn] +#[use_type(HasA.A in B, HasB.B in A)] +pub fn deep(&self) -> A { + todo!() +} + +fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_cyclic_context.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_cyclic_context.stderr new file mode 100644 index 00000000..fc3f994d --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_cyclic_context.stderr @@ -0,0 +1,11 @@ +error[E0425]: cannot find type `A` in this scope + --> tests/acceptable/cgp_fn/use_type_cyclic_context.rs:32:35 + | +32 | #[use_type(HasA.A in B, HasB.B in A)] + | ^ not found in this scope + +error[E0425]: cannot find type `B` in this scope + --> tests/acceptable/cgp_fn/use_type_cyclic_context.rs:32:22 + | +32 | #[use_type(HasA.A in B, HasB.B in A)] + | ^ not found in this scope diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs new file mode 100644 index 00000000..0794e242 --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs @@ -0,0 +1,53 @@ +//! Acceptable failure: a *nested* foreign `#[use_type(HasTypes.Types, HasScalarType.Scalar in Types)]` +//! import adds the two-hop bound `::Types: HasScalarType` to the +//! generated trait, so a context whose `Types` associated type does not implement +//! `HasScalarType` is rejected — proof the transitively-grounded foreign bound is +//! enforced at depth, not just for a directly-named parameter. +//! +//! Before the foreign bound was carried onto the trait, this nested constraint was +//! silently dropped. CGP is now working as designed: it emits the grounded bound +//! and defers the check to `rustc`, which reports the missing `NoScalar: HasScalarType` +//! at the site that requires `App: GetScalar`. +//! +//! See docs/reference/attributes/use_type.md and docs/errors/checks/check-trait-failure.md. + +use cgp::prelude::*; + +#[cgp_type] +pub trait HasTypes { + type Types; +} + +#[cgp_type] +pub trait HasScalarType { + type Scalar; +} + +// The generated `GetScalar` trait becomes, after the rewrite: +// pub trait GetScalar: HasTypes +// where ::Types: HasScalarType +// { fn get_scalar(&self) -> <::Types as HasScalarType>::Scalar; } +#[cgp_fn] +#[use_type(HasTypes.Types, HasScalarType.Scalar in Types)] +pub fn get_scalar(&self) -> Scalar { + todo!() +} + +// `App::Types` is `NoScalar`, which deliberately does not implement `HasScalarType`. +pub struct NoScalar; + +pub struct App; + +impl HasTypes for App { + type Types = NoScalar; +} + +// Requires `App: GetScalar`, which the blanket impl reduces to +// `::Types: HasScalarType`, i.e. `NoScalar: HasScalarType`. +fn assert_app() +where + App: GetScalar, +{ +} + +fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_nested_unsatisfied.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_nested_unsatisfied.stderr new file mode 100644 index 00000000..fbf25663 --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_nested_unsatisfied.stderr @@ -0,0 +1,173 @@ +error[E0277]: the trait bound `NoScalar: HasScalarType` is not satisfied + --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:49:10 + | +49 | App: GetScalar, + | ^^^^^^^^^ unsatisfied trait bound + | +help: the trait `DelegateComponent` is not implemented for `NoScalar` + --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:37:1 + | +37 | pub struct NoScalar; + | ^^^^^^^^^^^^^^^^^^^ + = help: the following other types implement trait `DelegateComponent`: + `BuildAndMergeOutputs` implements `DelegateComponent` + `BuildAndMergeOutputs` implements `DelegateComponent` + `BuildAndMergeOutputs` implements `DelegateComponent` + `BuildAndMergeOutputs` implements `DelegateComponent` + `BuildAndMergeOutputs` implements `DelegateComponent` + `BuildAndMergeOutputs` implements `DelegateComponent` + `MatchFirstWithFieldHandlersInputs` implements `DelegateComponent<(Input, Args)>` + `MatchFirstWithFieldHandlersInputsMut` implements `DelegateComponent<(&'a mut Input, Args)>` + and $N others +note: required for `NoScalar` to implement `ScalarTypeProvider` + --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:21:1 + | +21 | #[cgp_type] + | ^^^^^^^^^^^ +note: required for `NoScalar` to implement `HasScalarType` + --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:21:1 + | +21 | #[cgp_type] + | ^^^^^^^^^^^ +22 | pub trait HasScalarType { + | ^^^^^^^^^^^^^ +note: required by a bound in `GetScalar` + --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:31:28 + | +31 | #[use_type(HasTypes.Types, HasScalarType.Scalar in Types)] + | ^^^^^^^^^^^^^ required by this bound in `GetScalar` +32 | pub fn get_scalar(&self) -> Scalar { + | ---------- required by a bound in this trait + = note: this error originates in the attribute macro `cgp_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `NoScalar: HasScalarType` is not satisfied + --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:49:10 + | +49 | App: GetScalar, + | ^^^^^^^^^ unsatisfied trait bound + | +help: the trait `IsProviderFor` is not implemented for `NoScalar` + --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:37:1 + | +37 | pub struct NoScalar; + | ^^^^^^^^^^^^^^^^^^^ + = help: the following other types implement trait `IsProviderFor`: + `BindErr` implements `IsProviderFor)>` + `BindErr` implements `IsProviderFor)>` + `BindOk` implements `IsProviderFor)>` + `BindOk` implements `IsProviderFor)>` + `BuildAndMerge` implements `IsProviderFor` + `BuildAndMerge` implements `IsProviderFor` + `BuildAndMerge` implements `IsProviderFor` + `BuildAndMergeOutputs` implements `IsProviderFor` + and $N others +note: required for `NoScalar` to implement `ScalarTypeProvider` + --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:21:1 + | +21 | #[cgp_type] + | ^^^^^^^^^^^ +note: required for `NoScalar` to implement `HasScalarType` + --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:21:1 + | +21 | #[cgp_type] + | ^^^^^^^^^^^ +22 | pub trait HasScalarType { + | ^^^^^^^^^^^^^ +note: required by a bound in `GetScalar` + --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:31:28 + | +31 | #[use_type(HasTypes.Types, HasScalarType.Scalar in Types)] + | ^^^^^^^^^^^^^ required by this bound in `GetScalar` +32 | pub fn get_scalar(&self) -> Scalar { + | ---------- required by a bound in this trait + = note: this error originates in the attribute macro `cgp_type` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: NoScalar does not contain any DelegateComponent entry for ScalarTypeProviderComponent + --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:49:5 + | +49 | App: GetScalar, + | ^^^^^^^^^^^^^^ unsatisfied trait bound + | +help: the trait `DelegateComponent` is not implemented for `NoScalar` + --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:37:1 + | +37 | pub struct NoScalar; + | ^^^^^^^^^^^^^^^^^^^ + = note: You might want to implement the provider trait for ScalarTypeProviderComponent on NoScalar + = help: the following other types implement trait `DelegateComponent`: + `BuildAndMergeOutputs` implements `DelegateComponent` + `BuildAndMergeOutputs` implements `DelegateComponent` + `BuildAndMergeOutputs` implements `DelegateComponent` + `BuildAndMergeOutputs` implements `DelegateComponent` + `BuildAndMergeOutputs` implements `DelegateComponent` + `BuildAndMergeOutputs` implements `DelegateComponent` + `MatchFirstWithFieldHandlersInputs` implements `DelegateComponent<(Input, Args)>` + `MatchFirstWithFieldHandlersInputsMut` implements `DelegateComponent<(&'a mut Input, Args)>` + and $N others +note: required for `NoScalar` to implement `ScalarTypeProvider` + --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:21:1 + | +21 | #[cgp_type] + | ^^^^^^^^^^^ +note: required for `NoScalar` to implement `HasScalarType` + --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:21:1 + | +21 | #[cgp_type] + | ^^^^^^^^^^^ +22 | pub trait HasScalarType { + | ^^^^^^^^^^^^^ +note: required for `App` to implement `GetScalar` + --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:30:1 + | +30 | #[cgp_fn] + | ^^^^^^^^^ +31 | #[use_type(HasTypes.Types, HasScalarType.Scalar in Types)] + | ------------- unsatisfied trait bound introduced here +32 | pub fn get_scalar(&self) -> Scalar { + | ^^^^^^^^^^ + = note: this error originates in the attribute macro `cgp_type` which comes from the expansion of the attribute macro `cgp_fn` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `NoScalar: IsProviderFor` is not satisfied + --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:49:5 + | +49 | App: GetScalar, + | ^^^^^^^^^^^^^^ unsatisfied trait bound + | +help: the trait `IsProviderFor` is not implemented for `NoScalar` + --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:37:1 + | +37 | pub struct NoScalar; + | ^^^^^^^^^^^^^^^^^^^ + = note: You need to add `#[cgp_provider(ScalarTypeProviderComponent)]` on the impl block for CGP provider traits + = help: the following other types implement trait `IsProviderFor`: + `BindErr` implements `IsProviderFor)>` + `BindErr` implements `IsProviderFor)>` + `BindOk` implements `IsProviderFor)>` + `BindOk` implements `IsProviderFor)>` + `BuildAndMerge` implements `IsProviderFor` + `BuildAndMerge` implements `IsProviderFor` + `BuildAndMerge` implements `IsProviderFor` + `BuildAndMergeOutputs` implements `IsProviderFor` + and $N others +note: required for `NoScalar` to implement `ScalarTypeProvider` + --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:21:1 + | +21 | #[cgp_type] + | ^^^^^^^^^^^ +note: required for `NoScalar` to implement `HasScalarType` + --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:21:1 + | +21 | #[cgp_type] + | ^^^^^^^^^^^ +22 | pub trait HasScalarType { + | ^^^^^^^^^^^^^ +note: required for `App` to implement `GetScalar` + --> tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs:30:1 + | +30 | #[cgp_fn] + | ^^^^^^^^^ +31 | #[use_type(HasTypes.Types, HasScalarType.Scalar in Types)] + | ------------- unsatisfied trait bound introduced here +32 | pub fn get_scalar(&self) -> Scalar { + | ^^^^^^^^^^ + = note: this error originates in the attribute macro `cgp_type` which comes from the expansion of the attribute macro `cgp_fn` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_unknown_assoc.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_unknown_assoc.rs new file mode 100644 index 00000000..c453a5d7 --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_unknown_assoc.rs @@ -0,0 +1,30 @@ +//! Acceptable failure: `#[use_type]` imports an associated type name the owning +//! trait does not declare, so the substituted `::WrongName` path +//! names an associated type that does not exist and the compiler rejects it. +//! +//! `HasScalarType` declares `Scalar`, but the import names `Scalr` (a typo), so the +//! bare `Scalr` in the signature is rewritten to `::Scalr`. +//! CGP cannot know the trait's associated types at expansion time — it performs a +//! textual rewrite — so it lowers the name faithfully and defers to the compiler, +//! which reports `E0576` "cannot find associated type `Scalr`". Because the +//! substitution preserves the *span* of the identifier the user wrote, the caret +//! lands on the `Scalr` in the signature, not on the macro attribute — so this +//! fixture also guards that span behavior. +//! +//! See docs/reference/attributes/use_type.md and +//! docs/errors/lowering/unresolved-imported-type.md. + +use cgp::prelude::*; + +#[cgp_type] +pub trait HasScalarType { + type Scalar; +} + +#[cgp_fn] +#[use_type(HasScalarType.Scalr)] +pub fn get_scalar(&self) -> Scalr { + todo!() +} + +fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_unknown_assoc.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_unknown_assoc.stderr new file mode 100644 index 00000000..0b804cae --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_unknown_assoc.stderr @@ -0,0 +1,13 @@ +error[E0576]: cannot find associated type `Scalr` in trait `HasScalarType` + --> tests/acceptable/cgp_fn/use_type_unknown_assoc.rs:26:29 + | +21 | type Scalar; + | ------------ similarly named associated type `Scalar` defined here +... +26 | pub fn get_scalar(&self) -> Scalr { + | ^^^^^ + | +help: an associated type with a similar name exists + | +26 | pub fn get_scalar(&self) -> Scalar { + | + diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_component.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_component.rs new file mode 100644 index 00000000..1d6d6cf3 --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_component.rs @@ -0,0 +1,38 @@ +//! Acceptable failure: a downstream crate registers a `#[default_impl]` for a +//! foreign *unprefixed* component into a foreign namespace — an orphan violation +//! even though the component key is a bare marker rather than a prefix path. +//! +//! `GreeterComponent` (unprefixed) and `AppNamespace` both come from +//! `cgp-test-crate-a`, so `#[default_impl(GreeterComponent in AppNamespace)]` +//! expands to `impl<__Components__> AppNamespace<__Components__> for GreeterComponent`, +//! whose trait (`AppNamespace`) and self type (`GreeterComponent`) are both foreign +//! to this crate. Rust accepts a foreign-trait impl only when a local type covers +//! its type parameters, and here none does, so `__Components__` is an uncovered +//! type parameter and the orphan rule rejects it (`E0210`, caret on the +//! `#[cgp_impl]` attribute that generated the impl). Registering a per-component +//! default therefore needs the crate to own *either* the namespace or the component +//! key; owning neither, this crate cannot. +//! +//! This is the bare-marker sibling of default_impl_foreign_prefix_path.rs, whose +//! key is a foreign `PathCons<..>` path: both are the same orphan violation, and +//! this one shows the restriction is not specific to prefixed components. The +//! orphan-*safe* counterpart — a *local* component key registered into the foreign +//! `AppNamespace` — is exercised in `cgp-test-crate-b`. +//! +//! See docs/errors/wiring/orphan-rule.md. + +use cgp::prelude::*; +use cgp_test_crate_a::{AppNamespace, Greeter, GreeterComponent, HasName}; + +#[cgp_impl(new GreetPolitely)] +#[default_impl(GreeterComponent in AppNamespace)] +impl Greeter +where + Self: HasName, +{ + fn greet(&self) -> String { + format!("Good day, {}", self.name()) + } +} + +fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_component.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_component.stderr new file mode 100644 index 00000000..fa3ca580 --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_component.stderr @@ -0,0 +1,9 @@ +error[E0210]: type parameter `__Components__` must be used as the type parameter for some local type (e.g., `MyStruct<__Components__>`) + --> tests/acceptable/cgp_namespace/default_impl_foreign_component.rs:27:1 + | +27 | #[cgp_impl(new GreetPolitely)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type parameter `__Components__` must be used as the type parameter for some local type + | + = note: implementing a foreign trait is only possible if at least one of the types for which it is implemented is local + = note: only traits defined in the current crate can be implemented for a type parameter + = note: this error originates in the attribute macro `cgp_impl` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/for_loop_bare_key.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/for_loop_bare_key.rs index 42e903d2..5f082a83 100644 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/for_loop_bare_key.rs +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/for_loop_bare_key.rs @@ -14,10 +14,11 @@ //! than on every key. CGP lowers both blanket impls faithfully; only the whole //! program reveals the overlap, so it defers to the compiler. //! -//! This is the blanket-vs-blanket shape of the conflicting-wiring class; contrast -//! the specific-key override in override_registered_path.rs. +//! This is the blanket-vs-blanket shape of the overlapping-forwarding class, +//! alongside two_namespaces_joined.rs (two `namespace` joins on one context); +//! contrast the specific-vs-blanket override in override_registered_path.rs. //! -//! See docs/errors/wiring/conflicting-wiring.md. +//! See docs/errors/wiring/namespace-forwarding-conflict.md. use cgp::prelude::*; diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/for_loop_bare_key.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/for_loop_bare_key.stderr index c9f2e845..f8f88c87 100644 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/for_loop_bare_key.stderr +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/for_loop_bare_key.stderr @@ -1,17 +1,17 @@ error[E0119]: conflicting implementations of trait `IsProviderFor<_, _, _>` for type `App` - --> tests/acceptable/cgp_namespace/for_loop_bare_key.rs:49:13 + --> tests/acceptable/cgp_namespace/for_loop_bare_key.rs:50:13 | -46 | namespace DefaultNamespace; +47 | namespace DefaultNamespace; | ---------------- first implementation here ... -49 | Key: Value, +50 | Key: Value, | ^^^ conflicting implementation for `App` error[E0119]: conflicting implementations of trait `DelegateComponent<_>` for type `App` - --> tests/acceptable/cgp_namespace/for_loop_bare_key.rs:49:13 + --> tests/acceptable/cgp_namespace/for_loop_bare_key.rs:50:13 | -46 | namespace DefaultNamespace; +47 | namespace DefaultNamespace; | ---------------- first implementation here ... -49 | Key: Value, +50 | Key: Value, | ^^^ conflicting implementation for `App` diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/inherited_override_conflict.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/inherited_override_conflict.rs new file mode 100644 index 00000000..86693105 --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/inherited_override_conflict.rs @@ -0,0 +1,59 @@ +//! Acceptable failure: a child namespace that inherits a parent and then +//! *redefines* a key the parent already binds — a namespace entry cannot be +//! overridden by an inheriting namespace. +//! +//! `new ChildNs: BaseNs` emits the inheritance blanket impl `impl ChildNs for Key where Key: BaseNs<__ChildNsComponents>, Key: +//! BaseNs`, which forwards *every* key `BaseNs` resolves — +//! including `GreeterComponent`, since `BaseNs` binds it. The child's own +//! `GreeterComponent: GreetBye` entry emits a second impl `impl
ChildNs
+//! for GreeterComponent`, and the two overlap for that key, so coherence rejects the +//! pair (`E0119`, a *single* conflict on `ChildNs<_> for GreeterComponent`, since a +//! namespace emits only its own lookup-trait impl, not the context-side +//! `DelegateComponent`/`IsProviderFor` pair). Inheritance layers new keys onto a +//! parent; it cannot revise the parent's existing keys. To vary a key per +//! configuration, leave it *unbound* in the shared base and bind it in each child, +//! rather than binding it in the base and overriding it. CGP lowers both impls +//! faithfully; only the whole program reveals the overlap, so it defers to the +//! compiler. +//! +//! This is the namespace-level (inheritance) shape of the override-conflict class; +//! contrast the context-level shape in override_registered_path.rs, where a context +//! joining a namespace tries to override a path the namespace registers. +//! +//! See docs/errors/wiring/namespace-override-conflict.md. + +use cgp::prelude::*; + +#[cgp_component(Greeter)] +pub trait CanGreet { + fn greet(&self) -> String; +} + +#[cgp_impl(new GreetHello)] +impl Greeter { + fn greet(&self) -> String { + "Hello".to_owned() + } +} + +#[cgp_impl(new GreetBye)] +impl Greeter { + fn greet(&self) -> String { + "Bye".to_owned() + } +} + +cgp_namespace! { + new BaseNs { + GreeterComponent: GreetHello, + } +} + +cgp_namespace! { + new ChildNs: BaseNs { + GreeterComponent: GreetBye, + } +} + +fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/inherited_override_conflict.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/inherited_override_conflict.stderr new file mode 100644 index 00000000..2ec41d9e --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/inherited_override_conflict.stderr @@ -0,0 +1,7 @@ +error[E0119]: conflicting implementations of trait `ChildNs<_>` for type `GreeterComponent` + --> tests/acceptable/cgp_namespace/inherited_override_conflict.rs:55:9 + | +54 | new ChildNs: BaseNs { + | ------ first implementation here +55 | GreeterComponent: GreetBye, + | ^^^^^^^^^^^^^^^^ conflicting implementation for `GreeterComponent` diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/override_registered_path.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/override_registered_path.rs index 78b7368a..4f6a1fe5 100644 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/override_registered_path.rs +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/override_registered_path.rs @@ -15,7 +15,11 @@ //! or wire the override on a path the namespace never registers. A namespace that //! registers the leaf path leaves nothing for the context to override there. //! -//! See docs/errors/wiring/conflicting-wiring.md. +//! This is the context-level (join) shape of the override-conflict class; contrast +//! the namespace-level (inheritance) shape in inherited_override_conflict.rs, where +//! a child namespace tries to override an entry its parent binds. +//! +//! See docs/errors/wiring/namespace-override-conflict.md. use cgp::prelude::*; diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/override_registered_path.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/override_registered_path.stderr index 356a93b5..3fa49dd9 100644 --- a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/override_registered_path.stderr +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/override_registered_path.stderr @@ -1,20 +1,20 @@ error[E0119]: conflicting implementations of trait `IsProviderFor>>>, PathCons>, _, _>` for type `App` - --> tests/acceptable/cgp_namespace/override_registered_path.rs:53:14 + --> tests/acceptable/cgp_namespace/override_registered_path.rs:57:14 | -51 | namespace AppNamespace; +55 | namespace AppNamespace; | ------------ first implementation here -52 | -53 | @app.GreeterComponent: GreetBye, +56 | +57 | @app.GreeterComponent: GreetBye, | ^^^^^^^^^^^^^^^^ conflicting implementation for `App` | = note: downstream crates may implement trait `cgp::prelude::IsProviderFor>>>, cgp::prelude::PathCons>, _, _>` for type `GreetHello` = note: downstream crates may implement trait `cgp::prelude::IsProviderFor>>>, cgp::prelude::PathCons>, _, _>` for type `GreetBye` error[E0119]: conflicting implementations of trait `DelegateComponent>>>, PathCons>>` for type `App` - --> tests/acceptable/cgp_namespace/override_registered_path.rs:53:14 + --> tests/acceptable/cgp_namespace/override_registered_path.rs:57:14 | -51 | namespace AppNamespace; +55 | namespace AppNamespace; | ------------ first implementation here -52 | -53 | @app.GreeterComponent: GreetBye, +56 | +57 | @app.GreeterComponent: GreetBye, | ^^^^^^^^^^^^^^^^ conflicting implementation for `App` diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/reopen_foreign_namespace.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/reopen_foreign_namespace.rs new file mode 100644 index 00000000..29bdea3e --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/reopen_foreign_namespace.rs @@ -0,0 +1,34 @@ +//! Acceptable failure: a `cgp_namespace!` block *without* `new` re-opens a foreign +//! namespace to add an entry keyed on a foreign component — an orphan violation. +//! +//! Omitting `new` tells the macro the namespace trait is declared elsewhere and to +//! emit only the entry impls. Here `AppNamespace` and `GreeterComponent` both come +//! from `cgp-test-crate-a`, so the `GreeterComponent => @foo` entry expands to +//! `impl<__Table__> AppNamespace<__Table__> for GreeterComponent { type Delegate = +//! RedirectLookup<..> }`, whose trait and self type are both foreign. No local type +//! covers the table parameter, so `__Table__` is uncovered and the orphan rule +//! rejects it (`E0210`, caret on the whole `cgp_namespace!` block, offending +//! parameter `__Table__` — the namespace table parameter, distinct from the +//! `__Components__` of a `#[default_impl]`). A crate may only add entries to a +//! namespace whose trait it owns; to extend a foreign namespace, define a *new* +//! local namespace that *inherits* it (`new Local: AppNamespace { .. }`), which is +//! orphan-safe because the emitted impls are for the local trait. CGP lowers the +//! entry faithfully; only the whole program reveals the impl is foreign, so it +//! defers to the compiler. +//! +//! This is the `cgp_namespace!` trigger of the orphan class, alongside the +//! `#[default_impl]` triggers in default_impl_foreign_prefix_path.rs and +//! default_impl_foreign_component.rs. +//! +//! See docs/errors/wiring/orphan-rule.md. + +use cgp::prelude::*; +use cgp_test_crate_a::{AppNamespace, GreeterComponent}; + +cgp_namespace! { + AppNamespace { + GreeterComponent => @foo, + } +} + +fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/reopen_foreign_namespace.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/reopen_foreign_namespace.stderr new file mode 100644 index 00000000..cd2c78e8 --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/reopen_foreign_namespace.stderr @@ -0,0 +1,13 @@ +error[E0210]: type parameter `__Table__` must be used as the type parameter for some local type (e.g., `MyStruct<__Table__>`) + --> tests/acceptable/cgp_namespace/reopen_foreign_namespace.rs:28:1 + | +28 | / cgp_namespace! { +29 | | AppNamespace { +30 | | GreeterComponent => @foo, +31 | | } +32 | | } + | |_^ type parameter `__Table__` must be used as the type parameter for some local type + | + = note: implementing a foreign trait is only possible if at least one of the types for which it is implemented is local + = note: only traits defined in the current crate can be implemented for a type parameter + = note: this error originates in the macro `cgp_namespace` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/two_namespaces_joined.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/two_namespaces_joined.rs new file mode 100644 index 00000000..38a539dd --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/two_namespaces_joined.rs @@ -0,0 +1,57 @@ +//! Acceptable failure: a context that joins **two** namespaces at once — +//! `namespace NamespaceA; namespace NamespaceB;` — cannot compile, because each +//! join emits a *blanket* forwarding impl over every key and the two overlap. +//! +//! Each `namespace N;` header emits `impl DelegateComponent for +//! App where Key: N` (plus the matching `IsProviderFor` forwarding), a +//! blanket impl that covers *every* key. Joining two namespaces emits two such +//! blanket impls — one keyed through `NamespaceA`, one through `NamespaceB` — and +//! because a key could satisfy both `where` clauses, coherence cannot prove they +//! never overlap and rejects the pair (`E0119`, fully generic `DelegateComponent<_>` +//! / `IsProviderFor<_, _, _>`, carets on the two `namespace` lines, no downstream +//! note). A context therefore forwards through at most one namespace; layer several +//! by having that one namespace *inherit* the others (`new Combined: A { .. }` +//! inheriting further), not by joining several on the context. CGP lowers both +//! blanket impls faithfully; only the whole program reveals the overlap, so it +//! defers to the compiler. +//! +//! This is the blanket-vs-blanket shape of the overlapping-forwarding class, +//! alongside for_loop_bare_key.rs (a namespace join plus a bare-key `for` loop); +//! contrast the specific-vs-blanket override in override_registered_path.rs. +//! +//! See docs/errors/wiring/namespace-forwarding-conflict.md. + +use cgp::prelude::*; + +#[cgp_component(Greeter)] +pub trait CanGreet { + fn greet(&self) -> String; +} + +#[cgp_impl(new GreetHello)] +impl Greeter { + fn greet(&self) -> String { + "Hello".to_owned() + } +} + +cgp_namespace! { + new NamespaceA { + GreeterComponent: GreetHello, + } +} + +cgp_namespace! { + new NamespaceB {} +} + +pub struct App; + +delegate_components! { + App { + namespace NamespaceA; + namespace NamespaceB; + } +} + +fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/two_namespaces_joined.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/two_namespaces_joined.stderr new file mode 100644 index 00000000..d713dfb3 --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/two_namespaces_joined.stderr @@ -0,0 +1,15 @@ +error[E0119]: conflicting implementations of trait `IsProviderFor<_, _, _>` for type `App` + --> tests/acceptable/cgp_namespace/two_namespaces_joined.rs:53:19 + | +52 | namespace NamespaceA; + | ---------- first implementation here +53 | namespace NamespaceB; + | ^^^^^^^^^^ conflicting implementation for `App` + +error[E0119]: conflicting implementations of trait `DelegateComponent<_>` for type `App` + --> tests/acceptable/cgp_namespace/two_namespaces_joined.rs:53:19 + | +52 | namespace NamespaceA; + | ---------- first implementation here +53 | namespace NamespaceB; + | ^^^^^^^^^^ conflicting implementation for `App` diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/generic_context_ordinary_bound.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/generic_context_ordinary_bound.rs new file mode 100644 index 00000000..ed1648f8 --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/generic_context_ordinary_bound.rs @@ -0,0 +1,54 @@ +//! Acceptable failure: the ordinary-trait-bound dependency reached through *impl +//! generics* in `delegate_components!`, rather than a concrete context. +//! +//! A generic context ` Wrapper` wires its abstract `Scalar` type to the impl +//! generic `T` (`ScalarTypeProviderComponent: UseType`), and `CompareScalars` +//! needs `Scalar: Eq` — i.e. `T: Eq`. The generic wiring is accepted unconditionally; +//! the bound only bites at a concrete instantiation. Checking `Wrapper` surfaces +//! `f64: Eq` unsatisfied through `IsProviderFor>`, +//! exactly as the concrete-context case does — showing the ordinary-trait-bound class +//! arises anywhere impl generics carry a bound, including a generic +//! `delegate_components!` table checked at one instantiation. +//! +//! See docs/errors/checks/ordinary-trait-bound.md. + +use cgp::prelude::*; + +#[cgp_type] +pub trait HasScalarType { + type Scalar; +} + +#[cgp_component(ScalarEquality)] +#[use_type(HasScalarType.Scalar)] +pub trait CanCompareScalars { + fn scalars_equal(&self, a: &Scalar, b: &Scalar) -> bool; +} + +#[cgp_impl(new CompareScalars)] +#[use_type(HasScalarType.Scalar)] +impl ScalarEquality +where + Scalar: Eq, +{ + fn scalars_equal(&self, a: &Scalar, b: &Scalar) -> bool { + a == b + } +} + +pub struct Wrapper(pub T); + +delegate_components! { + Wrapper { + ScalarTypeProviderComponent: UseType, + ScalarEqualityComponent: CompareScalars, + } +} + +check_components! { + Wrapper { + ScalarEqualityComponent, + } +} + +fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/generic_context_ordinary_bound.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/generic_context_ordinary_bound.stderr new file mode 100644 index 00000000..c2266a33 --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/generic_context_ordinary_bound.stderr @@ -0,0 +1,35 @@ +error[E0277]: the trait bound `f64: Eq` is not satisfied + --> tests/acceptable/check_components/generic_context_ordinary_bound.rs:50:9 + | +50 | ScalarEqualityComponent, + | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `Eq` is not implemented for `f64` + | + = help: the following other types implement trait `Eq`: + i128 + i16 + i32 + i64 + i8 + isize + u128 + u16 + and $N others +note: required for `CompareScalars` to implement `IsProviderFor>` + --> tests/acceptable/check_components/generic_context_ordinary_bound.rs:28:1 + | +28 | #[cgp_impl(new CompareScalars)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +32 | Scalar: Eq, + | -- unsatisfied trait bound introduced here + = note: required for `Wrapper` to implement `CanUseComponent` +note: required by a bound in `__CheckWrapper` + --> tests/acceptable/check_components/generic_context_ordinary_bound.rs:48:1 + | +48 | / check_components! { +49 | | Wrapper { +50 | | ScalarEqualityComponent, +51 | | } +52 | | } + | |_^ required by this bound in `__CheckWrapper` + = note: this error originates in the attribute macro `cgp_impl` which comes from the expansion of the macro `check_components` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/ordinary_bound_unsatisfied.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/ordinary_bound_unsatisfied.rs new file mode 100644 index 00000000..5dcd8619 --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/ordinary_bound_unsatisfied.rs @@ -0,0 +1,63 @@ +//! Acceptable failure: a provider's impl-side dependency is an *ordinary Rust +//! trait bound* — a standard trait (`Eq`), not a CGP capability — on an abstract +//! type, and the concrete type the context wires for that abstract type does not +//! implement it. +//! +//! `CompareScalars` requires `Scalar: Eq` (rewritten by `#[use_type]` to +//! `::Scalar: Eq`). `App` wires its `Scalar` type to `f64`, +//! which is `PartialEq` but not `Eq`, so the dependency is unmet. The wiring is +//! accepted lazily; forcing it through `check_components!` surfaces the failure via +//! `IsProviderFor` as `E0277` — but unlike a missing `HasField` (whose leaf sits in +//! a `help:` note under a `CanUseComponent` primary), the *primary* error names the +//! ordinary bound on the concrete type directly (`f64: Eq` is not satisfied), the +//! `help:` lists the standard types that *do* implement `Eq`, and the `IsProviderFor` +//! note points at the `Scalar: Eq` bound as "introduced here". The fix is to satisfy +//! the ordinary trait (wire an `Eq` type such as an integer, or derive/impl `Eq`), +//! not to wire a component or add a field. +//! +//! CGP lowers the bound faithfully and cannot see the wired type violates it, so it +//! defers to the compiler. This is the same lazy-wiring mechanism as a CGP-capability +//! dependency; only the *kind of leaf* (an ordinary trait) and the fix differ. +//! +//! See docs/errors/checks/ordinary-trait-bound.md. + +use cgp::prelude::*; + +#[cgp_type] +pub trait HasScalarType { + type Scalar; +} + +#[cgp_component(ScalarEquality)] +#[use_type(HasScalarType.Scalar)] +pub trait CanCompareScalars { + fn scalars_equal(&self, a: &Scalar, b: &Scalar) -> bool; +} + +#[cgp_impl(new CompareScalars)] +#[use_type(HasScalarType.Scalar)] +impl ScalarEquality +where + Scalar: Eq, +{ + fn scalars_equal(&self, a: &Scalar, b: &Scalar) -> bool { + a == b + } +} + +pub struct App; + +delegate_components! { + App { + ScalarTypeProviderComponent: UseType, + ScalarEqualityComponent: CompareScalars, + } +} + +check_components! { + App { + ScalarEqualityComponent, + } +} + +fn main() {} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/ordinary_bound_unsatisfied.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/ordinary_bound_unsatisfied.stderr new file mode 100644 index 00000000..6496051a --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/ordinary_bound_unsatisfied.stderr @@ -0,0 +1,35 @@ +error[E0277]: the trait bound `f64: Eq` is not satisfied + --> tests/acceptable/check_components/ordinary_bound_unsatisfied.rs:59:9 + | +59 | ScalarEqualityComponent, + | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `Eq` is not implemented for `f64` + | + = help: the following other types implement trait `Eq`: + i128 + i16 + i32 + i64 + i8 + isize + u128 + u16 + and $N others +note: required for `CompareScalars` to implement `IsProviderFor` + --> tests/acceptable/check_components/ordinary_bound_unsatisfied.rs:37:1 + | +37 | #[cgp_impl(new CompareScalars)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +41 | Scalar: Eq, + | -- unsatisfied trait bound introduced here + = note: required for `App` to implement `CanUseComponent` +note: required by a bound in `__CheckApp` + --> tests/acceptable/check_components/ordinary_bound_unsatisfied.rs:57:1 + | +57 | / check_components! { +58 | | App { +59 | | ScalarEqualityComponent, +60 | | } +61 | | } + | |_^ required by this bound in `__CheckApp` + = note: this error originates in the attribute macro `cgp_impl` which comes from the expansion of the macro `check_components` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/ordinary_bound_unsatisfied.rs b/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/ordinary_bound_unsatisfied.rs new file mode 100644 index 00000000..737f4969 --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/ordinary_bound_unsatisfied.rs @@ -0,0 +1,55 @@ +//! Acceptable failure: the same unmet *ordinary Rust trait bound* dependency as +//! check_components/ordinary_bound_unsatisfied.rs (`Scalar: Eq` with `f64` wired), +//! but exercised by calling the consumer method rather than a check — so the cause +//! is *hidden*. +//! +//! Calling `app.scalars_equal(..)` produces the `E0599` "method exists but its +//! trait bounds were not satisfied" shape: it names `App: CanCompareScalars` / +//! `App: ScalarEquality`, misclassifies the method as an associated function +//! (the provider method has no `self` receiver), and suggests `App::scalars_equal()` +//! — but never mentions the unmet `f64: Eq`. This is byte-for-shape identical to the +//! HasName hidden case in delegate_components/missing_dependency.rs: the compiler's +//! method-probe heuristic drops the nested `where`-clause bound regardless of whether +//! that bound is a `HasField`, a CGP capability, or an ordinary trait. Promote it with +//! `check_components!` to surface the `f64: Eq` cause. +//! +//! See docs/errors/hidden/unsatisfied-dependency.md; the surfaced counterpart is +//! docs/errors/checks/ordinary-trait-bound.md. + +use cgp::prelude::*; + +#[cgp_type] +pub trait HasScalarType { + type Scalar; +} + +#[cgp_component(ScalarEquality)] +#[use_type(HasScalarType.Scalar)] +pub trait CanCompareScalars { + fn scalars_equal(&self, a: &Scalar, b: &Scalar) -> bool; +} + +#[cgp_impl(new CompareScalars)] +#[use_type(HasScalarType.Scalar)] +impl ScalarEquality +where + Scalar: Eq, +{ + fn scalars_equal(&self, a: &Scalar, b: &Scalar) -> bool { + a == b + } +} + +pub struct App; + +delegate_components! { + App { + ScalarTypeProviderComponent: UseType, + ScalarEqualityComponent: CompareScalars, + } +} + +fn main() { + let app = App; + let _ = app.scalars_equal(&1.0, &2.0); +} diff --git a/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/ordinary_bound_unsatisfied.stderr b/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/ordinary_bound_unsatisfied.stderr new file mode 100644 index 00000000..ee0632d6 --- /dev/null +++ b/crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/ordinary_bound_unsatisfied.stderr @@ -0,0 +1,44 @@ +error[E0599]: the method `scalars_equal` exists for struct `App`, but its trait bounds were not satisfied + --> tests/acceptable/delegate_components/ordinary_bound_unsatisfied.rs:54:17 + | +43 | pub struct App; + | -------------- method `scalars_equal` not found for this struct because it doesn't satisfy `App: CanCompareScalars` or `App: ScalarEquality` +... +54 | let _ = app.scalars_equal(&1.0, &2.0); + | ^^^^^^^^^^^^^ this is an associated function, not a method + | + = note: found the following associated functions; to be used as methods, functions must have a `self` parameter +note: the candidate is defined in the trait `ScalarEquality` + --> tests/acceptable/delegate_components/ordinary_bound_unsatisfied.rs:29:5 + | +29 | fn scalars_equal(&self, a: &Scalar, b: &Scalar) -> bool; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +note: trait bound `App: ScalarEquality` was not satisfied + --> tests/acceptable/delegate_components/ordinary_bound_unsatisfied.rs:26:1 + | +26 | #[cgp_component(ScalarEquality)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +27 | #[use_type(HasScalarType.Scalar)] +28 | pub trait CanCompareScalars { + | ^^^^^^^^^^^^^^^^^ +note: the trait `ScalarEquality` must be implemented + --> tests/acceptable/delegate_components/ordinary_bound_unsatisfied.rs:28:1 + | +28 | / pub trait CanCompareScalars { +29 | | fn scalars_equal(&self, a: &Scalar, b: &Scalar) -> bool; +30 | | } + | |_^ + = help: items from traits can only be used if the trait is implemented and in scope +note: `CanCompareScalars` defines an item `scalars_equal`, perhaps you need to implement it + --> tests/acceptable/delegate_components/ordinary_bound_unsatisfied.rs:28:1 + | +28 | / pub trait CanCompareScalars { +29 | | fn scalars_equal(&self, a: &Scalar, b: &Scalar) -> bool; +30 | | } + | |_^ + = note: this error originates in the attribute macro `cgp_component` (in Nightly builds, run with -Z macro-backtrace for more info) +help: use associated function syntax instead + | +54 - let _ = app.scalars_equal(&1.0, &2.0); +54 + let _ = App::scalars_equal(&1.0, &2.0); + | diff --git a/crates/tests/cgp-tests/tests/abstract_types/mod.rs b/crates/tests/cgp-tests/tests/abstract_types/mod.rs index da8165e5..612a5c40 100644 --- a/crates/tests/cgp-tests/tests/abstract_types/mod.rs +++ b/crates/tests/cgp-tests/tests/abstract_types/mod.rs @@ -12,8 +12,9 @@ pub mod cgp_type_self_referential; pub mod cgp_type_unsized; // The `#[use_type]` attribute (and the `#[extend]` alternative) importing an -// abstract type into a `#[cgp_component]`/`#[cgp_impl]`. +// abstract type into a `#[cgp_component]`/`#[cgp_impl]`/`#[cgp_auto_getter]`. pub mod extend_component; +pub mod use_type_auto_getter; pub mod use_type_component; pub mod use_type_foreign; pub mod use_type_generic_param; @@ -21,9 +22,11 @@ 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 -// sources. These keep the `#[cgp_fn]` snapshot because the abstract-type rewrite -// is the point (the `#[cgp_fn]` expansion itself is owned by `implicit_arguments`). +// sources (including a two-hop foreign chain). These keep the `#[cgp_fn]` snapshot +// because the abstract-type rewrite is the point (the `#[cgp_fn]` expansion itself +// is owned by `implicit_arguments`). pub mod use_type_fn_alias; +pub mod use_type_fn_deep_foreign; pub mod use_type_fn_equality; pub mod use_type_fn_equality_cross_trait; pub mod use_type_fn_extend; @@ -31,3 +34,6 @@ pub mod use_type_fn_foreign; pub mod use_type_fn_foreign_equality; pub mod use_type_fn_foreign_equality_cross_trait; pub mod use_type_fn_nested_foreign; +pub mod use_type_fn_reverse_order; +pub mod use_type_foreign_getter; +pub mod use_type_uses_supertrait; diff --git a/crates/tests/cgp-tests/tests/abstract_types/use_type_auto_getter.rs b/crates/tests/cgp-tests/tests/abstract_types/use_type_auto_getter.rs new file mode 100644 index 00000000..f6e26c66 --- /dev/null +++ b/crates/tests/cgp-tests/tests/abstract_types/use_type_auto_getter.rs @@ -0,0 +1,48 @@ +//! `#[use_type]` importing an abstract type into a `#[cgp_auto_getter]` trait. +//! +//! A getter macro routes its companion attributes through the same +//! `CgpComponentAttributes` collector as `#[cgp_component]`, so `#[use_type]` +//! works on it too: `#[use_type(HasScalarType.Scalar)]` adds `HasScalarType` as a +//! supertrait of the generated getter trait and rewrites the bare `Scalar` return +//! type to `::Scalar`. The derived blanket impl then reads +//! a `base_value` field whose `HasField` value type is that qualified associated +//! type, so `App` supplies both the concrete scalar (via `UseType`) and the +//! field. This pins that `#[use_type]` is supported on `#[cgp_auto_getter]`, not +//! only on the three implementation macros. +//! +//! See docs/reference/attributes/use_type.md and docs/concepts/abstract-types.md. + +use cgp::prelude::*; + +#[cgp_type] +pub trait HasScalarType { + type Scalar; +} + +// Expands, after the `#[use_type]` rewrite, to roughly: +// pub trait HasBaseValue: HasScalarType { +// fn base_value(&self) -> &::Scalar; +// } +// with a blanket impl reading a `base_value` field of that type. +#[cgp_auto_getter] +#[use_type(HasScalarType.Scalar)] +pub trait HasBaseValue { + fn base_value(&self) -> &Scalar; +} + +#[derive(HasField)] +pub struct App { + pub base_value: f64, +} + +delegate_and_check_components! { + App { + ScalarTypeProviderComponent: UseType, + } +} + +#[test] +fn test_base_value() { + let app = App { base_value: 42.0 }; + assert_eq!(*app.base_value(), 42.0); +} diff --git a/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_deep_foreign.rs b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_deep_foreign.rs new file mode 100644 index 00000000..38069d99 --- /dev/null +++ b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_deep_foreign.rs @@ -0,0 +1,91 @@ +//! `#[use_type]` chaining three foreign imports so a context is resolved through +//! two hops: `#[use_type(HasA.A, HasB.B in A, HasC.C in B)]`. +//! +//! `HasA.A` imports `A` from `Self`, `HasB.B in A` imports `B` from that `A`, and +//! `HasC.C in B` imports `C` from that `B`. Grounding each spec's context up front +//! resolves the chain fully: `A` grounds to `::A`, `B` to +//! `<::A as HasB>::B`, so the bare `C` rewrites to the three-hop +//! `<<::A as HasB>::B as HasC>::C`, and the appended bounds name the +//! same fully-grounded contexts (`<::A as HasB>::B: HasC`, not a +//! half-resolved `::B: HasC`). None of the three abstract-type traits +//! declares a bound on its associated type, so every required bound is supplied by +//! the imports themselves. `CheckApp` asserts a concrete context satisfies the +//! generated trait. The `#[cgp_fn]` snapshot is kept because the multi-hop rewrite +//! is the point. +//! +//! See docs/reference/attributes/use_type.md and docs/concepts/abstract-types.md. + +use cgp::prelude::*; +use cgp_macro_test_util::snapshot_cgp_fn; + +#[cgp_type] +pub trait HasA { + type A; +} + +#[cgp_type] +pub trait HasB { + type B; +} + +#[cgp_type] +pub trait HasC { + type C; +} + +snapshot_cgp_fn! { + #[cgp_fn] + #[use_type(HasA.A, HasB.B in A, HasC.C in B)] + pub fn deep(&self) -> C { + todo!() + } + + expand_deep(output) { + insta::assert_snapshot!(output, @" + pub trait Deep: HasA + where + ::A: HasB, + <::A as HasB>::B: HasC, + { + fn deep(&self) -> <<::A as HasB>::B as HasC>::C; + } + impl<__Context__> Deep for __Context__ + where + Self: HasA, + ::A: HasB, + <::A as HasB>::B: HasC, + { + fn deep(&self) -> <<::A as HasB>::B as HasC>::C { + todo!() + } + } + ") + } +} + +pub struct Cc; + +impl HasC for Cc { + type C = u32; +} + +pub struct Bb; + +impl HasB for Bb { + type B = Cc; +} + +pub struct App; + +impl HasA for App { + type A = Bb; +} + +pub trait CheckApp: Deep +where + ::A: HasB, + <::A as HasB>::B: HasC, +{ +} + +impl CheckApp for App {} 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 1dd3a89d..67718ed6 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,12 +1,15 @@ //! `#[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(HasScalarType.Scalar in Types)]`. //! -//! The function is generic over `Types: HasScalarType`, and the `@Types::` prefix -//! resolves `Scalar` against that parameter, rewriting the bare alias to -//! `::Scalar` throughout — the generated trait becomes -//! `RectangleArea`. `CheckRectangle` is a compile-time -//! assertion that `Rectangle` implements the generated trait for the concrete -//! `Types`. The `#[cgp_fn]` snapshot is kept for the rewrite; `#[cgp_type]` is plain. +//! The function is generic over a plain `Types` (declared *without* a +//! `HasScalarType` bound), and the `in Types` suffix resolves `Scalar` against +//! that parameter, rewriting the bare alias to `::Scalar` +//! throughout. The foreign import supplies the `Types: HasScalarType` bound on +//! the generated trait itself — the regression guard for that bound being +//! dropped — so the trait becomes `RectangleArea where Types: HasScalarType`. +//! `CheckRectangle` is a compile-time assertion that `Rectangle` implements the +//! generated trait for the concrete `Types`. The `#[cgp_fn]` snapshot is kept for +//! the rewrite; `#[cgp_type]` is plain. //! //! See docs/reference/attributes/use_type.md and docs/concepts/abstract-types.md. @@ -22,8 +25,8 @@ pub trait HasScalarType { snapshot_cgp_fn! { #[cgp_fn] - #[use_type(@Types.HasScalarType.Scalar)] - pub fn rectangle_area( + #[use_type(HasScalarType.Scalar in Types)] + pub fn rectangle_area( &self, #[implicit] width: Scalar, #[implicit] height: Scalar, @@ -37,10 +40,13 @@ snapshot_cgp_fn! { expand_rectangle_area(output) { insta::assert_snapshot!(output, @" - pub trait RectangleArea { + pub trait RectangleArea + where + Types: HasScalarType, + { fn rectangle_area(&self) -> ::Scalar; } - impl<__Context__, Types: HasScalarType> RectangleArea for __Context__ + impl<__Context__, Types> RectangleArea for __Context__ where ::Scalar: Mul::Scalar> + Copy, 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 1e9c86ee..687fd465 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,9 +1,9 @@ //! `#[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, HasScalarType.{Scalar = f64} in Types)]`. //! //! `HasTypes::Types` imports the abstract `Types` from `Self`, then -//! `@Types::HasScalarType::{Scalar = f64}` resolves `Scalar` against *that* +//! `HasScalarType.{Scalar = f64} in Types` resolves `Scalar` against *that* //! imported type and pins it to `f64`, so the bare `Scalar` alias rewrites to the //! two-hop `<::Types as HasScalarType>::Scalar` and the impl //! gains `::Types: HasScalarType`. The `#[cgp_fn]` @@ -28,7 +28,7 @@ snapshot_cgp_fn! { #[cgp_fn] #[use_type( HasTypes.Types, - @Types.HasScalarType.{Scalar = f64}, + HasScalarType.{Scalar = f64} in Types, )] pub fn rectangle_area(&self, #[implicit] width: Scalar, #[implicit] height: Scalar) -> Scalar { let res: f64 = width * height; @@ -37,7 +37,10 @@ snapshot_cgp_fn! { expand_rectangle_area(output) { insta::assert_snapshot!(output, @" - pub trait RectangleArea: HasTypes { + pub trait RectangleArea: HasTypes + where + ::Types: HasScalarType, + { fn rectangle_area(&self) -> <::Types as HasScalarType>::Scalar; } impl<__Context__> RectangleArea for __Context__ 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 e062d958..9a48d45b 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,8 +1,8 @@ //! `#[use_type]` reaching through a *nested foreign* associated type across traits -//! in `#[cgp_fn]`: `#[use_type(HasBarType.Bar, @Bar.HasFooType.Foo)]` and the -//! equality form `@Bar::HasFooType::{Foo as BarFoo = Foo}`. +//! in `#[cgp_fn]`: `#[use_type(HasBarType.Bar, HasFooType.Foo in Bar)]` and the +//! equality form `HasFooType.{Foo as BarFoo = Foo} in Bar`. //! -//! Here `HasBarType::Bar` itself implements `HasFooType`, so `@Bar::HasFooType::Foo` +//! Here `HasBarType::Bar` itself implements `HasFooType`, so `HasFooType.Foo in Bar` //! resolves the alias to the two-hop `<::Bar as HasFooType>::Foo` //! and adds `::Bar: HasFooType` to the impl. The final function //! equates that nested type to `Self`'s own `Foo` (`{Foo as BarFoo = Foo}`), @@ -49,14 +49,17 @@ snapshot_cgp_fn! { snapshot_cgp_fn! { #[cgp_fn] - #[use_type(HasBarType.Bar, @Bar.HasFooType.Foo)] + #[use_type(HasBarType.Bar, HasFooType.Foo in Bar)] pub fn do_bar(&self) -> Foo { todo!() } expand_do_bar(output) { insta::assert_snapshot!(output, @" - pub trait DoBar: HasBarType { + pub trait DoBar: HasBarType + where + ::Bar: HasFooType, + { fn do_bar(&self) -> <::Bar as HasFooType>::Foo; } impl<__Context__> DoBar for __Context__ @@ -77,7 +80,7 @@ snapshot_cgp_fn! { #[use_type( HasFooType.Foo, HasBarType.Bar, - @Bar.HasFooType.{Foo as BarFoo = Foo}, + HasFooType.{Foo as BarFoo = Foo} in Bar, )] #[uses(DoFoo, DoBar)] fn return_foo_or_bar(&self, flag: bool, #[implicit] foo: &Foo, #[implicit] bar: &BarFoo) -> Foo { @@ -92,7 +95,10 @@ snapshot_cgp_fn! { expand_return_foo_or_bar(output) { insta::assert_snapshot!(output, @" - trait ReturnFooOrBar: HasFooType + HasBarType { + trait ReturnFooOrBar: HasFooType + HasBarType + where + ::Bar: HasFooType, + { fn return_foo_or_bar(&self, flag: bool) -> ::Foo; } impl<__Context__> ReturnFooOrBar for __Context__ 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 1bb56ec2..04b8c634 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,15 +1,17 @@ //! `#[use_type]` reaching a nested foreign type in `#[cgp_fn]` combined with -//! `#[extend_where]`: `#[use_type(HasTypes.Types, @Types.HasScalarType.Scalar)]` -//! plus `#[extend_where(Types: HasScalarType)]`. +//! `#[extend_where]`: `#[use_type(HasTypes.Types, HasScalarType.Scalar in Types)]` +//! plus `#[extend_where(Scalar: Copy)]`. //! -//! `HasTypes::Types` imports the abstract `Types`, then `@Types::HasScalarType::Scalar` +//! `HasTypes::Types` imports the abstract `Types`, then `HasScalarType.Scalar in Types` //! resolves `Scalar` against it, so the bare alias rewrites to the two-hop -//! `<::Types as HasScalarType>::Scalar`. Unlike the equality -//! variant, `Types` is *not* pinned to a concrete type, so `#[extend_where(...)]` -//! adds the `::Types: HasScalarType` bound to the generated trait -//! definition (the aliased `Types` in the attribute is likewise rewritten). The -//! `#[cgp_fn]` snapshot is kept for the rewrite; both `#[cgp_type]` traits are plain. -//! `CheckRectangle` asserts the concrete `Rectangle` implements the generated trait. +//! `<::Types as HasScalarType>::Scalar`. The foreign import adds +//! the nested `::Types: HasScalarType` bound to the generated +//! trait on its own, so `#[extend_where(...)]` here contributes a *further* +//! trait-level bound — `Scalar: Copy` — that `#[use_type]` would not add, and the +//! aliased `Scalar` in the attribute is likewise rewritten to the two-hop path. +//! The `#[cgp_fn]` snapshot is kept for the rewrite; both `#[cgp_type]` traits are +//! plain. `CheckRectangle` asserts the concrete `Rectangle` implements the +//! generated trait. //! //! See docs/reference/attributes/use_type.md and docs/concepts/abstract-types.md. @@ -30,8 +32,8 @@ pub trait HasTypes { snapshot_cgp_fn! { #[cgp_fn] - #[use_type(HasTypes.Types, @Types.HasScalarType.Scalar)] - #[extend_where(Types: HasScalarType)] + #[use_type(HasTypes.Types, HasScalarType.Scalar in Types)] + #[extend_where(Scalar: Copy)] pub fn rectangle_area(&self, #[implicit] width: Scalar, #[implicit] height: Scalar) -> Scalar where Scalar: Mul + Copy, @@ -44,6 +46,7 @@ snapshot_cgp_fn! { insta::assert_snapshot!(output, @" pub trait RectangleArea: HasTypes where + <::Types as HasScalarType>::Scalar: Copy, ::Types: HasScalarType, { fn rectangle_area(&self) -> <::Types as HasScalarType>::Scalar; @@ -53,7 +56,7 @@ snapshot_cgp_fn! { <::Types as HasScalarType>::Scalar: Mul< Output = <::Types as HasScalarType>::Scalar, > + Copy, - ::Types: HasScalarType, + <::Types as HasScalarType>::Scalar: Copy, Self: HasField< Symbol<5, Chars<'w', Chars<'i', Chars<'d', Chars<'t', Chars<'h', Nil>>>>>>, Value = <::Types as HasScalarType>::Scalar, @@ -125,6 +128,7 @@ impl HasTypes for Rectangle { pub trait CheckRectangle: RectangleArea where Self::Types: HasScalarType, + ::Scalar: Copy, { } diff --git a/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_reverse_order.rs b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_reverse_order.rs new file mode 100644 index 00000000..e992b66c --- /dev/null +++ b/crates/tests/cgp-tests/tests/abstract_types/use_type_fn_reverse_order.rs @@ -0,0 +1,67 @@ +//! `#[use_type]` grounds a chain of nested foreign imports regardless of the order +//! the specs are written: `#[use_type(HasC.C in B, HasB.B in A, HasA.A)]` writes +//! the three-hop chain back-to-front, with each `in Context` referencing an alias +//! declared *after* it. +//! +//! This is the order-independence counterpart to `use_type_fn_deep_foreign`, which +//! writes the same chain front-to-back. Grounding iterates to a fixpoint over all +//! specs at once, so a spec may name a context imported by any other spec no matter +//! where it sits in the list; the bare `C` still rewrites to the three-hop +//! `<<::A as HasB>::B as HasC>::C`. Only a genuine *cycle* — which has +//! no valid order at all — fails to ground; that acceptable failure is pinned in +//! the `use_type_cyclic_context` compile-fail fixture. +//! +//! `deep` takes and returns a value of the deep type, so the test asserts a +//! concrete value flows through the fully-grounded signature at runtime. +//! +//! See docs/reference/attributes/use_type.md and docs/concepts/abstract-types.md. + +use cgp::prelude::*; + +#[cgp_type] +pub trait HasA { + type A; +} + +#[cgp_type] +pub trait HasB { + type B; +} + +#[cgp_type] +pub trait HasC { + type C; +} + +// The imports are written in reverse dependency order: `C in B` before `B in A` +// before the `A` that both ultimately resolve through. +#[cgp_fn] +#[use_type(HasC.C in B, HasB.B in A, HasA.A)] +pub fn deep(&self, value: C) -> C { + value +} + +pub struct Cc; + +impl HasC for Cc { + type C = u32; +} + +pub struct Bb; + +impl HasB for Bb { + type B = Cc; +} + +pub struct App; + +impl HasA for App { + type A = Bb; +} + +#[test] +fn test_reverse_order_grounds() { + // `<<::A as HasB>::B as HasC>::C` grounds to `u32`, so `deep` + // takes and returns a `u32` through the fully-grounded signature. + assert_eq!(App.deep(42), 42); +} 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 294a319f..4d86b6c6 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,14 +1,20 @@ //! Importing an abstract type from a *foreign* type parameter with the -//! `#[use_type(@Types.HasScalarType.Scalar)]` form on a generic component. +//! `#[use_type(HasScalarType.Scalar in Types)]` 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 `in Types` suffix 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 //! the concrete scalar, while `Rectangle` supplies the fields and error type. //! `#[cgp_type]`, wiring, and check are incidental and use the plain macros. //! +//! The generic `Types` is declared *without* a `HasScalarType` bound: the foreign +//! `in Types` import supplies `Types: HasScalarType` on both the consumer and +//! provider traits on its own, so the component compiles and checks even though +//! nothing else names the bound. This is the regression guard for the foreign +//! bound being dropped from the trait. +//! //! See docs/reference/attributes/use_type.md and docs/concepts/abstract-types.md. use std::convert::Infallible; @@ -23,13 +29,13 @@ pub trait HasScalarType { } #[cgp_component(AreaCalculator)] -#[use_type(@Types.HasScalarType.Scalar, HasErrorType.Error)] -pub trait CanCalculateArea { +#[use_type(HasScalarType.Scalar in Types, HasErrorType.Error)] +pub trait CanCalculateArea { fn area(&self) -> Result; } #[cgp_impl(new RectangleArea)] -#[use_type(@Types.HasScalarType.Scalar, HasErrorType.Error)] +#[use_type(HasScalarType.Scalar in Types, HasErrorType.Error)] impl AreaCalculator where Scalar: Mul + Copy, diff --git a/crates/tests/cgp-tests/tests/abstract_types/use_type_foreign_getter.rs b/crates/tests/cgp-tests/tests/abstract_types/use_type_foreign_getter.rs new file mode 100644 index 00000000..2a7557d6 --- /dev/null +++ b/crates/tests/cgp-tests/tests/abstract_types/use_type_foreign_getter.rs @@ -0,0 +1,58 @@ +//! `#[use_type]` importing a *foreign* abstract type into a `#[cgp_auto_getter]` +//! whose type parameter owns the type: `#[use_type(HasUserIdType.UserId in App)]`. +//! +//! This is the recommended form for a getter whose return type names an abstract +//! type living on another type (here the `App` parameter, not `Self`). The `in App` +//! clause rewrites the bare `UserId` to `::UserId` and adds +//! `App: HasUserIdType` as a `where` bound on the generated trait, so the author +//! writes neither the bound nor the qualified `App::UserId` by hand. It replaces +//! the verbose form that declares `where App: HasUserIdType` and writes +//! `&Option` at the use site. +//! +//! The derived blanket impl reads a `logged_in_user` field whose `HasField` value +//! type is that qualified associated type, so the context supplies the field while +//! `App` supplies the concrete `UserId`. +//! +//! See docs/guides/importing-abstract-types.md and +//! docs/reference/attributes/use_type.md. + +use cgp::prelude::*; + +#[cgp_type] +pub trait HasUserIdType { + type UserId; +} + +// Recommended: import the foreign type with `#[use_type]` and write the bare +// alias, rather than a hand-written `where App: HasUserIdType` plus `App::UserId`. +#[cgp_auto_getter] +#[use_type(HasUserIdType.UserId in App)] +pub trait HasLoggedInUser { + fn logged_in_user(&self) -> &Option; +} + +// `App` owns the concrete user-id type; it need not be the context. +pub struct App; + +impl HasUserIdType for App { + type UserId = u64; +} + +#[derive(HasField)] +pub struct Server { + pub logged_in_user: Option, +} + +#[test] +fn test_logged_in_user() { + let server = Server { + logged_in_user: Some(7), + }; + // `Server: HasLoggedInUser` holds through the derived blanket impl, with + // `UserId` grounded to `::UserId` = `u64`. The `App` + // parameter is named explicitly here since the getter is generic over it. + assert_eq!( + *>::logged_in_user(&server), + Some(7u64) + ); +} diff --git a/crates/tests/cgp-tests/tests/abstract_types/use_type_uses_supertrait.rs b/crates/tests/cgp-tests/tests/abstract_types/use_type_uses_supertrait.rs new file mode 100644 index 00000000..94b89803 --- /dev/null +++ b/crates/tests/cgp-tests/tests/abstract_types/use_type_uses_supertrait.rs @@ -0,0 +1,66 @@ +//! Re-importing an abstract type with `#[use_type]` even though it already arrives +//! transitively as the supertrait of a trait pulled in by `#[uses]`. +//! +//! `CanCreateFoo` carries `HasFooType` as a supertrait (added by its own +//! `#[use_type(HasFooType.Foo)]`), so `#[uses(CanCreateFoo)]` on `bar` makes +//! `Self: HasFooType` hold transitively. Even so, the recommended form imports +//! `Foo` again with `#[use_type(HasFooType.Foo)]` on `bar`, which lets the body and +//! signature write the bare alias `Foo` instead of a qualified `Self::Foo` that +//! relies on the supertrait being reachable and unambiguous. +//! +//! `bar` forwards to `create_foo`, so this asserts a value flows end to end through +//! the transitively-required, then explicitly re-imported, abstract type. +//! +//! See docs/guides/declaring-dependencies.md and +//! docs/guides/importing-abstract-types.md. + +use cgp::prelude::*; + +#[cgp_type] +pub trait HasFooType { + type Foo: Clone; +} + +#[cgp_component(FooCreator)] +#[use_type(HasFooType.Foo)] +pub trait CanCreateFoo { + fn create_foo(&self) -> Foo; +} + +#[cgp_impl(new CreateFooFromField)] +#[use_type(HasFooType.Foo)] +impl FooCreator { + fn create_foo(&self, #[implicit] foo: Foo) -> Foo { + foo + } +} + +// Recommended: `#[uses(CanCreateFoo)]` supplies `Foo` transitively via the +// `HasFooType` supertrait, but re-importing it with `#[use_type]` lets `bar` write +// the bare `Foo` (in both the return type and the body's binding) rather than a +// qualified `Self::Foo`. +#[cgp_fn] +#[uses(CanCreateFoo)] +#[use_type(HasFooType.Foo)] +fn bar(&self) -> Foo { + let foo: Foo = self.create_foo(); + foo +} + +#[derive(HasField)] +pub struct MyContext { + pub foo: u64, +} + +delegate_and_check_components! { + MyContext { + FooTypeProviderComponent: UseType, + FooCreatorComponent: CreateFooFromField, + } +} + +#[test] +fn test_bar_forwards_foo() { + let context = MyContext { foo: 99 }; + assert_eq!(context.bar(), 99); +} diff --git a/docs/errors/AGENTS.md b/docs/errors/AGENTS.md index 8d27843d..04fb0301 100644 --- a/docs/errors/AGENTS.md +++ b/docs/errors/AGENTS.md @@ -38,6 +38,10 @@ Each error document follows the same shape so a reader can navigate any of them - **Backing fixtures** — a bullet list of the [cgp-compile-fail-tests](../../crates/tests/cgp-compile-fail-tests) fixtures that pin this class, each linked with a one-line note of what it exercises. This is where the catalog's test pointers live; every documented class must have at least one fixture. - **Related** — links to the relevant reference documents, the [debugging guide](../guides/debugging.md) section that handles the class, the sibling error classes it contrasts with, and — for a problematic/defect class — the owning macro's `## Known issues`. +## The error-code reference + +The [error_codes/](error_codes/README.md) directory is a supporting reference, not a set of classes: one entry per `rustc` error code the catalog surfaces, stating what the code means in plain Rust, the rule behind it, and the RFC or issue that defines it. It is the forward index (code → meaning → the classes that emit it) that complements the class documents (mistake → diagnostic). Three rules keep it honest. First, **every fact in an entry is verified against the official Rust documentation** — the [error index](https://doc.rust-lang.org/error_codes/), the [reference](https://doc.rust-lang.org/reference/), and the linked RFCs and `rust-lang/rust` issues — not against memory; the message wording is confirmed against a real compilation (a fixture's `.stderr`) where the class docs already pin it. Second, **a class document cites the local `error_codes/` entry in place of a raw `doc.rust-lang.org/error_codes/…` URL**, so the official links live in one place; the entry's "Where CGP produces it" section links back to every class that emits the code, and the two directions must stay in sync when a class is added, moved, or renamed. Third, an entry states the *Rust* rule only — the CGP-specific anatomy stays in the class document, so the two never duplicate each other. Add an entry when a class first surfaces a code not already covered. + ## Backing every class with a fixture, and organizing the fixtures Every error class must be backed by at least one compile-fail fixture, and reciprocally every post-codegen compile-fail fixture must be cataloged by a class here. When you write a class that has no fixture yet, add one under [cgp-compile-fail-tests](../../crates/tests/cgp-compile-fail-tests) following [crates/tests/AGENTS.md](../../crates/tests/AGENTS.md): place it under `acceptable/` or `problematic/` by the split above, write a header comment stating what it exercises and why it must not compile, and cross-link the fixture's header to the class document here (and, for a problematic case, to the owning macro's Known issues as well). Regenerate the `.stderr` and review it before committing. @@ -50,4 +54,4 @@ A class's home is decided by the same three-way split the fixtures use, and gett ## Gathering an error with a sub-agent -Writing or verifying a document here means reading real compiler output, and that output is often long enough to waste the context of the agent doing the writing. Delegate the reading. The [error-extraction sub-skill](../skills/cgp/references/error-extraction.md) defines how a sub-agent compiles a fixture or a scratch reproduction, captures the diagnostic, and returns only the compact anatomy this catalog records — the class, whether the root cause is present, and its position — rather than the raw dump. Spawn a sub-agent with that skill to gather the facts for a class, then write the document from its summary. The same delegation applies in an ordinary debugging session: when a CGP error is too long to read inline, hand it to a sub-agent and act on the summary. Keeping the extraction technique in the skill and the extracted facts in the catalog means the two describe the same anatomy from two directions. +Writing or verifying a document here means reading real compiler output, and that output is often long enough to waste the context of the agent doing the writing. When you are confirming a single fact about a class you already know — the error code a fixture emits, whether a `downstream crates may implement` note appears, which `Symbol` a leaf names — grep the captured `.stderr` for that line first (`grep -nE '^error'` to classify, then the class's [signature pattern](../guides/debugging.md#grep-for-the-suspected-line-instead-of-reading-the-whole-log)), which is cheaper than a sub-agent. Delegate the reading when the grep will not settle it — a new class you have not characterized, or output tangled across several classes at once. The [error-extraction sub-skill](../skills/cgp/references/error-extraction.md) defines how a sub-agent compiles a fixture or a scratch reproduction, captures the diagnostic, and returns only the compact anatomy this catalog records — the class, whether the root cause is present, and its position — rather than the raw dump. Spawn a sub-agent with that skill to gather the facts for a class, then write the document from its summary. The same delegation applies in an ordinary debugging session: when a CGP error is too long to read inline, hand it to a sub-agent and act on the summary. Keeping the extraction technique in the skill and the extracted facts in the catalog means the two describe the same anatomy from two directions. diff --git a/docs/errors/README.md b/docs/errors/README.md index 4a15e13a..14efa6e4 100644 --- a/docs/errors/README.md +++ b/docs/errors/README.md @@ -8,7 +8,7 @@ A CGP macro expands to ordinary Rust, so many mistakes are not caught by the mac The first reader is a **tool author**. The long-term goal is specialized tooling — a `cargo-cgp` that post-processes `rustc`'s output into a compact, root-cause-first form, much as Clippy layers its own analysis on top of the compiler. Such a tool needs a complete map of which error classes CGP produces, which of them hide the root cause, and where the root cause sits when it is present, before it can decide what to extract, what to suppress, and how to re-present it. A tool that reaches into compiler internals through `rustc_driver` to recover *suppressed* information especially needs to know, class by class, which information the compiler hides. This catalog is that map. -The second reader is an **agent debugging CGP code**, in this repository or any project that uses CGP. Recognizing an error's class on sight — before decoding a single nested type — tells the agent what kind of mistake to look for and which technique will surface it. The [debugging guide](../guides/debugging.md) is the playbook; this catalog is the reference the playbook indexes into. +The second reader is an **agent debugging CGP code**, in this repository or any project that uses CGP. Recognizing an error's class on sight — before decoding a single nested type — tells the agent what kind of mistake to look for and which technique will surface it. Grepping the error headlines (`grep -nE '^error'`) is the mechanical form of that recognition, and each class here has a signature line an agent can grep to confirm its cause without reading the whole cascade — the [debugging guide](../guides/debugging.md) collects those patterns in a table. The guide is the playbook; this catalog is the reference the playbook indexes into. The third reader is a **sub-agent extracting an error message on the main agent's behalf**. CGP error output is frequently long enough that reading it wastes a main agent's context window, so the [error-extraction sub-skill](https://github.com/contextgeneric/cgp/blob/main/docs/skills/cgp/references/error-extraction.md) delegates the reading to a sub-agent that returns a compact summary. That summary is only useful if it reports the same facts every document here records — the class, whether the root cause is present, and its position — so the catalog and the sub-skill describe the same anatomy from two directions. @@ -36,13 +36,17 @@ Because of this, **the documents here never record verbatim error output.** Repr ## Organization -The catalog is divided into three subdirectories by the axis above, so a reader lands in the right class before decoding any type. Each document is registered in the catalog below in the same change that adds it. +The catalog is divided into four subdirectories, so a reader lands in the right class before decoding any type. The first three follow the hidden-versus-surfaced axis above; the fourth holds a failure that arises earlier, in the macro's lowering itself. Each document is registered in the catalog below in the same change that adds it. The [hidden/](hidden/) directory holds the classes where the compiler **suppresses** the root cause — the errors a user meets by exercising broken wiring through a consumer trait rather than a check. These are isolated precisely because their diagnostics report nothing about the true cause, so a reader must know not to look for one. The [checks/](checks/) directory holds the classes where a check trait **surfaces** the root cause through `IsProviderFor`, and the classes that are dominated by the *volume* of a surfaced cascade rather than by any single message. -The [wiring/](wiring/) directory holds the whole-program **structural** failures — coherence conflicts, orphan-rule violations, wiring cycles, and unconstrained generics — where the compiler reports a definite error code (`E0119`, `E0210`, `E0275`, `E0207`) and the difficulty is mapping that code back to the wiring mistake rather than a hidden or cascading cause. +The [wiring/](wiring/) directory holds the whole-program **structural** failures — coherence conflicts, orphan-rule violations, wiring cycles, unconstrained generics, and the namespace-specific coherence collisions — where the compiler reports a definite error code (`E0119`, `E0210`, `E0275`, `E0207`) and the difficulty is mapping that code back to the wiring mistake rather than a hidden or cascading cause. Because the catalog groups by usage as well as by error code, the namespace collisions live in their own documents even where they share a code (`E0119`) with a plain duplicate declaration. + +The [lowering/](lowering/) directory holds the classes where the failure is not in the wiring at all but in what a macro *lowered* the user's input into — an accepted shorthand or type combination expanded into Rust that is ill-formed on its own terms (an unsized type, for instance), so the compiler rejects the generated code rather than any wiring decision. + +A fifth directory, [error_codes/](error_codes/), is a supporting *reference* rather than a class of error: one entry per `rustc` error code the catalog surfaces (`E0119`, `E0117`, `E0207`, `E0210`, `E0275`, `E0277`, `E0428`, `E0599`), stating what the code means in plain Rust, the rule behind it, and the RFC or issue that defines it, grounded in the official documentation. The class documents cite these entries in place of the raw `doc.rust-lang.org` URLs, so the Rust-language facts live in one verified place and the class docs carry only the CGP-specific anatomy. ## Catalog @@ -54,18 +58,30 @@ Hidden-cause errors — [hidden/](hidden/): Surfaced and cascading errors — [checks/](checks/): -- [Check-trait failure (surfaced)](checks/check-trait-failure.md) — the same unmet dependency forced through `check_components!`, where `IsProviderFor` surfaces the concrete missing bound at the wiring site. +- [Check-trait failure (surfaced)](checks/check-trait-failure.md) — the same unmet dependency forced through `check_components!`, where `IsProviderFor` surfaces the concrete missing bound (a `HasField` or CGP capability) at the wiring site. +- [Unsatisfied ordinary trait bound (surfaced)](checks/ordinary-trait-bound.md) — an impl-side dependency that is an *ordinary* Rust trait (`Eq`, `Clone`, …) on an abstract type or impl generic, unmet by the concrete type the context supplies; a check surfaces the ordinary bound (`f64: Eq`) as the primary `E0277`. - [Verbose dependency cascade](checks/verbose-cascade.md) — one deep mistake reported at every transitively dependent provider, and how to locate the single root cause among the repeats. - [Unregistered namespace path](checks/unregistered-namespace-path.md) — a component routed through a joined namespace to a path that no entry ever binds, so the *lookup* finds no delegate; a check surfaces it as an `E0277` on the path-keyed `DefaultNamespace`/`DelegateComponent` bound. Structural wiring errors — [wiring/](wiring/): -- [Conflicting wiring](wiring/conflicting-wiring.md) — the same key or name wired twice, producing coherence (`E0119`) or duplicate-definition (`E0428`) errors. -- [Orphan-rule violation](wiring/orphan-rule.md) — a generated impl for a foreign trait and a fully foreign type (`E0210`, or `E0117`), as when a prefixed `#[default_impl]` is registered from the wrong crate. +- [Conflicting wiring](wiring/conflicting-wiring.md) — the same key or name wired or declared twice, producing coherence (`E0119`) or duplicate-definition (`E0428`) errors. +- [Overlapping namespace forwarding](wiring/namespace-forwarding-conflict.md) — two blanket forwarding impls that each cover every key (joining two namespaces, or a namespace join plus a bare-key `for` loop), a fully-generic `E0119` with no downstream note. +- [Namespace override conflict](wiring/namespace-override-conflict.md) — a specific entry that overrides a key a namespace already claims (a context re-wiring a registered path, or a child namespace redefining an inherited entry), an `E0119` on a concrete key. +- [Orphan-rule violation](wiring/orphan-rule.md) — a generated impl registering into a foreign namespace with no local type (`E0210`, or `E0117`), as when a `#[default_impl]` or a `cgp_namespace!` re-open targets a namespace and key the crate does not own. - [Wiring cycle](wiring/wiring-cycle.md) — a delegation that chases its own tail: an `E0275` overflow when forced through a check, but the hidden `E0599` when reached by a plain method call. - [Namespace inheritance cycle](wiring/namespace-inheritance-cycle.md) — namespaces whose parent chain loops (`A: B`, `B: A`, or `A: A`), an `E0275` overflow caught *eagerly at the `cgp_namespace!` definitions* rather than lazily at a use site. - [Unconstrained generic](wiring/unconstrained-generic.md) — a per-entry generic that never reaches the key, leaving an impl parameter unconstrained (`E0207`). +Lowering errors — [lowering/](lowering/): + +- [Ill-formed generated type](lowering/ill-formed-generated-type.md) — a macro lowers an unsupported field- or argument-type shorthand (such as `Option<&[T]>`) into a generated bound naming an ill-formed, unsized type, which the compiler rejects with the `E0277` `Sized` form. +- [Unresolved imported abstract type](lowering/unresolved-imported-type.md) — a `#[use_type]` import names an associated type the owning trait does not declare, so the rewritten `::WrongName` path resolves to nothing and the compiler rejects it with `E0576`, its caret on the name the user wrote. + +Error-code reference — [error_codes/](error_codes/): + +- One entry per `rustc` code the catalog surfaces — [`E0119`](error_codes/e0119.md), [`E0117`](error_codes/e0117.md), [`E0207`](error_codes/e0207.md), [`E0210`](error_codes/e0210.md), [`E0275`](error_codes/e0275.md), [`E0277`](error_codes/e0277.md), [`E0428`](error_codes/e0428.md), [`E0576`](error_codes/e0576.md), [`E0599`](error_codes/e0599.md) — each recording what the code means in plain Rust, the rule behind it, and where CGP produces it. See the [error-code reference index](error_codes/README.md). + ## Relationship to the rest of the knowledge base This catalog is one of the views of CGP's truth and is bound by the [synchronization rule](../AGENTS.md): a class whose diagnostic the code no longer produces is a bug in the change that made it stale. It leans on the other sections rather than restating them. It links to the [reference](../reference/README.md) for the traits and macros a class involves, to the [check-traits concept](../concepts/check-traits.md) for why wiring is lazy, and above all to the [debugging guide](../guides/debugging.md), which is the prescriptive playbook this reference-style catalog supports. The rules for authoring and maintaining these documents — including how they stay in sync with the compile-fail fixtures and how a sub-agent extracts the errors they describe — live in [AGENTS.md](AGENTS.md). diff --git a/docs/errors/checks/check-trait-failure.md b/docs/errors/checks/check-trait-failure.md index 6bd67abb..2f5fed59 100644 --- a/docs/errors/checks/check-trait-failure.md +++ b/docs/errors/checks/check-trait-failure.md @@ -46,6 +46,8 @@ check_components! { } ``` +A `check_components!` is the canonical way to force this diagnostic, but it is not the only one: any *direct* obligation on a capability bound produces the same shape. A [`#[use_type]`](../../reference/attributes/use_type.md) foreign import (`HasScalarType.Scalar in Types`) puts the capability bound `Types: HasScalarType` — grounded to `::Types: HasScalarType` for a nested import — onto the *generated trait itself*, so asserting or using that trait for a context whose supplied type does not implement the capability surfaces the identical `E0277` with a capability leaf, without any `check_components!`. Here the `required for …` chain runs through the trait's own `where` bound (`required by a bound in CanCalculateArea` / `GetScalar`) rather than through `CanUseComponent`, but the leaf, the `DelegateComponent`/`IsProviderFor` `help:`, and the position of the cause are the same. + ## The diagnostic This is a **surfaced** class: the compiler prints an `E0277` that names the concrete missing bound, unlike the hidden class that omits it. The primary error reports that `Person: CanUseComponent` is not satisfied, and its caret lands on the `GreeterComponent` entry *inside the `check_components!` block* — not on the `Person` context type — because the check re-spans the shared context token onto each listed component in turn. Immediately below, a `help:` note gives the actual unmet leaf bound: that `HasField` is not implemented for `Person`, "but trait `HasField` is implemented for it." That second half is a useful landmark — the compiler is pointing at the *nearest existing* field impl, which tells you the context has a field, just not the one the provider expects. @@ -69,10 +71,13 @@ For a `cargo-cgp`-style post-processor, the fact to extract as the headline is t ## Backing fixtures - [acceptable/check_components/missing_dependency.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_dependency.rs) — the surfaced `E0277` for `GreetHello`'s unmet `Self: HasName`, whose `.stderr` pins the `help:` note naming `HasField` and the caret landing on `GreeterComponent` inside the block. Its unchecked counterpart, [acceptable/delegate_components/missing_dependency.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/missing_dependency.rs), pins the [hidden](../hidden/unsatisfied-dependency.md) `E0599` for the same mistake. The fused `delegate_and_check_components!` form produces the same surfaced shape. +- [acceptable/cgp_component/use_type_foreign_unsatisfied.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/use_type_foreign_unsatisfied.rs) — the capability bound reached through a `#[use_type]` foreign import instead of a check: naming a component for a `Types` that does not implement the imported `HasScalarType` surfaces `E0277` on `NoScalar: HasScalarType`, pinning that the foreign bound is *enforced on the generated trait* rather than silently dropped. +- [acceptable/cgp_fn/use_type_nested_unsatisfied.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs) — the same, but through a *nested* two-hop import, so the grounded bound `::Types: HasScalarType` is the one enforced; its `.stderr` pins the `required by this bound in GetScalar` note at the `HasScalarType.Scalar in Types` attribute, confirming the transitively-grounded foreign bound is checked at depth. ## Related - [Unsatisfied dependency (hidden)](../hidden/unsatisfied-dependency.md) — the hidden counterpart; the two are the two halves of one phenomenon, and promoting the hidden one yields this class. +- [Unsatisfied ordinary trait bound (surfaced)](ordinary-trait-bound.md) — the sibling surfaced class whose leaf is an ordinary Rust trait (`Eq`, `Clone`) on a concrete type rather than a CGP capability like `HasField`; the leaf's kind changes the `help:` note and the position of the cause. - [Verbose dependency cascade](verbose-cascade.md) — this diagnostic multiplied when many providers depend transitively on one leaf. - [`check_components!`](../../reference/macros/check_components.md), [`CanUseComponent`](../../reference/traits/can_use_component.md), and [`IsProviderFor`](../../reference/traits/is_provider_for.md) — the macro and traits this class is expressed through. - [Debugging CGP compile errors](../../guides/debugging.md) and the [check-traits concept](../../concepts/check-traits.md) — why the check moves the error here and how to read it. diff --git a/docs/errors/checks/ordinary-trait-bound.md b/docs/errors/checks/ordinary-trait-bound.md new file mode 100644 index 00000000..71e91856 --- /dev/null +++ b/docs/errors/checks/ordinary-trait-bound.md @@ -0,0 +1,78 @@ +# Unsatisfied ordinary trait bound + +A provider's impl-side dependency is an *ordinary* Rust trait bound — a standard-library, foreign, or plain user trait such as `Eq`, `Clone`, `Ord`, or `From`, not a CGP capability — and the concrete type the context supplies for an abstract type or generic parameter does not implement it, so a check surfaces the failure as `E0277` naming that ordinary bound. + +## What triggers it + +This class is the ordinary-trait cousin of a [check-trait failure](check-trait-failure.md): the mechanism is the same lazy wiring, but the unmet leaf is a plain Rust trait rather than a `HasField` or a CGP component. A provider lists a `where`-clause bound that is an ordinary trait applied to an abstract type or an impl generic — `Scalar: Eq`, `Item: Ord`, `T: Clone` — and the context wires a concrete type that does not satisfy it. Because [wiring](../../reference/macros/delegate_components.md) is lazy, the entry is accepted without checking the bound; it fails only when the wiring is exercised. + +The canonical case is an ordinary bound on an abstract type. `CompareScalars` needs the context's `Scalar` type to be `Eq`, and the context wires `Scalar` to `f64`, which is `PartialEq` but not `Eq`: + +```rust +#[cgp_type] +pub trait HasScalarType { + type Scalar; +} + +#[cgp_component(ScalarEquality)] +#[use_type(HasScalarType.Scalar)] +pub trait CanCompareScalars { + fn scalars_equal(&self, a: &Scalar, b: &Scalar) -> bool; +} + +#[cgp_impl(new CompareScalars)] +#[use_type(HasScalarType.Scalar)] +impl ScalarEquality +where + Scalar: Eq, // ordinary trait bound — rewritten to ::Scalar: Eq +{ + fn scalars_equal(&self, a: &Scalar, b: &Scalar) -> bool { + a == b + } +} + +delegate_components! { + App { + ScalarTypeProviderComponent: UseType, // f64: !Eq + ScalarEqualityComponent: CompareScalars, + } +} +``` + +The same failure arises wherever CGP or Rust accepts a generic trait bound, which is what makes the class broad. An ordinary bound on an **impl generic** in `delegate_components!` behaves identically: a generic context ` Wrapper` that wires its abstract type to `T` carries the provider's `Scalar: Eq` bound as `T: Eq`, unconditionally accepted, and checking `Wrapper` surfaces `f64: Eq` at that one instantiation. The bound may equally sit on an explicit `impl` generic in `#[cgp_impl]`, on a `#[cgp_fn]` `#[extend_where]` clause, or on a `for … in … where` loop — anywhere an ordinary bound rides on a parameter the context ultimately fills. CGP lowers the bound faithfully and cannot see that the wired type violates it, so it defers to the compiler. + +## The diagnostic + +This is a **surfaced** class: forcing the wiring through a [`check_components!`](../../reference/macros/check_components.md) produces an `E0277` that names the ordinary bound on the concrete type, and its shape is the tell that distinguishes it from a `HasField` check-trait failure. The **primary** error is the leaf itself — "the trait bound `f64: Eq` is not satisfied," with the caret on the checked component entry and the label "the trait `Eq` is not implemented for `f64`." Beneath it, a `help:` note lists the standard types that *do* implement the trait (`i128`, `i16`, `i32`, …), which is `rustc`'s stock "these types implement the trait" hint for a missing ordinary impl — not the near-contradiction "but trait `HasField` is implemented" hint a missing field produces. + +The position of the cause is the sharp contrast with a [check-trait failure](check-trait-failure.md), and knowing it tells the two apart on sight. A missing `HasField` roots the *primary* error at `Ctx: CanUseComponent` and tucks the concrete leaf into a `help:` note below it; an unmet ordinary bound roots the primary error at the leaf (`f64: Eq`) directly, and the `CanUseComponent` obligation appears only as a `note` further down. Between them runs the same scaffolding: a `note: required for CompareScalars to implement IsProviderFor` that points at the `Scalar: Eq` bound with "unsatisfied trait bound introduced here", then `required for App to implement CanUseComponent<…>`, then the bound in the generated `__CheckApp` trait. [`IsProviderFor`](../../reference/traits/is_provider_for.md) is again the supertrait that carries the provider's `where` clause into the proof, which is why the ordinary bound is named here and suppressed in the hidden form. The whole thing is ordinary `rustc` obligation resolution for [`E0277`](../error_codes/e0277.md); only the leaf's *kind* — a standard trait on a concrete type — changes what the message and its `help:` look like. + +Exercised without a check — by calling the consumer method — the same broken wiring instead produces the [hidden `E0599`](../hidden/unsatisfied-dependency.md), which names only `App: CanCompareScalars` / `App: ScalarEquality` and never mentions `f64: Eq`. The method-probe heuristic drops the nested `where`-clause bound regardless of whether it is a `HasField`, a capability, or an ordinary trait, so the ordinary-bound dependency hides exactly as any other does. + +## Where the root cause is + +The root cause is **present and sits at the very top** of the checked output: the primary `E0277` names the concrete type and the ordinary trait it fails (`f64: Eq`) outright. This is the most direct of the surfaced classes — there is no `help:`-note indirection as with a `HasField` leaf, and the `required for` notes below build *outward* from the leaf toward the check. The caret lands on the checked component entry, so the error also points at a site the user controls. The one thing the diagnostic does not state is which *wired type* is at fault when the bound is on an abstract type: the leaf names `f64`, and the `IsProviderFor` note names the `Scalar: Eq` bound, but connecting "`f64` is what `Scalar` resolves to" is left to the reader — trace it to the `ScalarTypeProviderComponent: UseType` wiring. + +## Resolving it + +The fix is to satisfy the ordinary trait, which is a different remedy from every capability-dependency class — not "wire a component" or "add a field," but make the concrete type implement the trait. Wire the abstract type (or instantiate the generic) to a type that *does* satisfy the bound — an integer type for `Eq`, say, instead of `f64` — or, when the offending type is one you own, derive or implement the trait for it (`#[derive(Eq)]`, a manual `impl Ord`). When the bound is stricter than the behavior needs — requiring `Eq` where `PartialEq` would do — relax the provider's `where` clause instead, so more concrete types qualify. The diagnostic already names both halves: the concrete type in the primary error and the bound in the `IsProviderFor` note. + +## Notes for tooling + +For a `cargo-cgp`-style post-processor, this class is nearly self-describing, so the value is small and mostly connective. The headline to extract is already the primary error — the concrete type and the ordinary trait (`f64: Eq`) — and the tool should **link it to the wiring that chose the type** by following the abstract-type component to its `UseType<…>` entry, reporting "`Scalar` is wired to `f64`, which the provider `CompareScalars` requires to be `Eq`." The `IsProviderFor` / `CanUseComponent` / `__Check…` frames are CGP scaffolding to collapse, and `rustc`'s list of conforming standard types is worth keeping as the actionable hint. Recognizing that the leaf is an ordinary trait rather than a `HasField` lets the tool phrase the fix as "satisfy or relax the trait bound" rather than "wire a component." + +## Backing fixtures + +- [acceptable/check_components/ordinary_bound_unsatisfied.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/ordinary_bound_unsatisfied.rs) — a provider requiring `Scalar: Eq` on a context that wires `Scalar` to `f64`, checked; its `.stderr` pins the primary `E0277` on `f64: Eq`, the `help:` list of conforming standard types, and the `IsProviderFor` note pointing at the `Scalar: Eq` bound. +- [acceptable/check_components/generic_context_ordinary_bound.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/generic_context_ordinary_bound.rs) — the same bound reached through impl generics: a generic ` Wrapper` table checked at `Wrapper`, surfacing `f64: Eq` through `IsProviderFor<…, Wrapper>`. + +The hidden (method-call) form of the same mistake is [acceptable/delegate_components/ordinary_bound_unsatisfied.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/ordinary_bound_unsatisfied.rs), cataloged with the [hidden unsatisfied dependency](../hidden/unsatisfied-dependency.md) class it shares its shape with. + +## Related + +- [Check-trait failure (surfaced)](check-trait-failure.md) — the sibling surfaced class whose leaf is a CGP capability (`HasField`, a component); contrast the leaf's *kind* (an ordinary trait here) and its *position* (the primary error here, a `help:` note there). +- [Unsatisfied dependency (hidden)](../hidden/unsatisfied-dependency.md) — the `E0599` form this takes when reached by a method call, where the ordinary bound is suppressed just like any other leaf. +- [Verbose dependency cascade](verbose-cascade.md) — when several providers share the unmet ordinary bound, this diagnostic multiplies the same way. +- [`E0277`](../error_codes/e0277.md) — the Rust error code this class is reported under, and its `Sized` special case. +- [`#[use_type]`](../../reference/attributes/use_type.md), [`#[cgp_type]`](../../reference/macros/cgp_type.md), and [`check_components!`](../../reference/macros/check_components.md) — the abstract-type import, the abstract-type component, and the check that surfaces the bound. +- [Debugging CGP compile errors](../../guides/debugging.md) — the `E0277` entry in the decoder. diff --git a/docs/errors/checks/unregistered-namespace-path.md b/docs/errors/checks/unregistered-namespace-path.md index 51c9e8ff..69a45326 100644 --- a/docs/errors/checks/unregistered-namespace-path.md +++ b/docs/errors/checks/unregistered-namespace-path.md @@ -46,6 +46,8 @@ Two notes below the headline confirm the reading and are worth recognizing. A `n One landmark reads at first like a contradiction and is in fact diagnostic. Beneath the headline the compiler prints `help: the following other types implement trait DefaultNamespace:` and lists the component *markers* that the namespace does resolve — including the bare `GreeterComponent` marker. That the marker implements `DefaultNamespace` while the *path* `PathCons` does not tells you the `#[prefix]` registration succeeded — the component is in the namespace — but the path it routes to was never filled. The problem is a missing binding at the leaf, not a missing prefix. +The diagnostic itself is ordinary `rustc` trait-solving, which is worth seeing so the shape is not mistaken for something exotic. [`E0277`](../error_codes/e0277.md) is the code for an unsatisfied trait bound, and everything here is standard obligation resolution. Proving the check's `App: CanUseComponent` obligation drives the solver through the `RedirectLookup` provider, which implements the component's provider trait only `where Components: DelegateComponent` — so the solver must discharge `App: DelegateComponent>`, finds no impl at that path, and reports *that* bound as the unsatisfied one. The `required for …` lines are `rustc`'s ordinary obligation-tracing output for the proof, and the `help: the following other types implement trait …` list is its stock "an impl of the same trait exists" hint, pointing at the keys the namespace *does* resolve. What makes this class read as "surfaced at the top" rather than buried is only that the unmet bound is a *lookup* (`DelegateComponent`/`DefaultNamespace`) with no impl at all, so it is the first obligation to fail — unlike a [check-trait failure](check-trait-failure.md), where a provider *is* found and the solver descends into its `where` clause before reporting the leaf in a `help:` note. + ## Where the root cause is The root cause is **present and sits at the top of the output**, in the primary `E0277` line and its first `required for … DelegateComponent>` note. This is the opposite position from a [check-trait failure](check-trait-failure.md), whose concrete leaf lives in a `help:` note *below* the primary error; here the unsatisfied lookup bound *is* the primary error, and the `required for` notes build outward from it toward the check. Reading the `PathCons<…>` key of that bound tells you which path is unbound — decode it with the [`Symbol!`](../../reference/macros/symbol.md) segments, or read the `long-type-….txt` file the compiler names when the path is elided as `...`. diff --git a/docs/errors/error_codes/README.md b/docs/errors/error_codes/README.md new file mode 100644 index 00000000..5f8d0886 --- /dev/null +++ b/docs/errors/error_codes/README.md @@ -0,0 +1,36 @@ +# Rust error-code reference + +This directory is a compact reference for the handful of `rustc` error codes CGP's post-codegen failures surface under, so a reader who has an error code in hand can look up what it means in plain Rust — the message `rustc` prints, the language rule behind it, and the RFC or issue that defines that rule — before turning to the CGP-specific class that produced it. It is the *forward* index (error code → meaning → the CGP classes that emit it) that complements the [catalog's class documents](../README.md), which run the other way (a CGP mistake → the diagnostic it produces). + +## Why this exists + +The per-class documents in the catalog each explain one CGP failure, and several of them lean on the same underlying Rust rule — coherence, the orphan rule, the trait-solver recursion limit. Rather than restate that rule in every class that touches it, each code has one entry here that states it once, grounded in the official Rust documentation and the RFCs and issues that define it, and the class documents link to that entry. This keeps the Rust-language facts in one verified place and the CGP-specific anatomy in the class docs, so neither drifts and neither repeats the other. + +Each entry records the same four things: the message `rustc` prints, when the compiler emits it in ordinary Rust, the rule it enforces and where that rule is defined, and which CGP error classes produce it. The facts here are verified against the official [error index](https://doc.rust-lang.org/error_codes/), the [Rust reference](https://doc.rust-lang.org/reference/), and the linked RFCs and `rust-lang/rust` issues, not against memory; the class documents remain the source of truth for the *exact* diagnostic a CGP mistake produces, pinned by their `.stderr` fixtures. + +## The codes + +The catalog surfaces nine codes, split by the kind of rule they enforce. Three are **coherence and orphan** rules — the compiler refusing overlapping or foreign impls: + +- [`E0119`](e0119.md) — conflicting implementations of a trait for a type. +- [`E0210`](e0210.md) — orphan rule: an uncovered type parameter in a foreign-trait impl. +- [`E0117`](e0117.md) — orphan rule: a foreign trait implemented for a foreign (arbitrary) type. + +One is a **well-formedness** rule on impl parameters: + +- [`E0207`](e0207.md) — an unconstrained type parameter on an impl. + +Two are **trait-solving** outcomes — the solver failing to satisfy or terminate a bound: + +- [`E0277`](e0277.md) — a trait bound is not satisfied (including the `Sized` special case). +- [`E0275`](e0275.md) — overflow evaluating a requirement (a recursion-limit or cycle failure). + +Three are **name-resolution and method-probe** diagnostics: + +- [`E0428`](e0428.md) — a name is defined more than once in one scope. +- [`E0576`](e0576.md) — an associated item is named that the trait or type does not declare. +- [`E0599`](e0599.md) — a method exists but its trait bounds are not satisfied. + +## Relationship to the rest of the catalog + +These entries are a supporting reference, not a class of error: they describe Rust, and the [class documents](../README.md) describe CGP. A code entry's "Where CGP produces it" section links out to every class that emits it, and each class links back to the code entry in place of the raw `doc.rust-lang.org` URL, so the official links live here and the classes cite this directory. The rules for keeping these entries accurate — verifying wording against a real compilation and the official docs rather than memory — are the same [synchronization rules](../AGENTS.md) that govern the whole catalog. diff --git a/docs/errors/error_codes/e0117.md b/docs/errors/error_codes/e0117.md new file mode 100644 index 00000000..16b842f3 --- /dev/null +++ b/docs/errors/error_codes/e0117.md @@ -0,0 +1,21 @@ +# E0117 — orphan rule: foreign trait for an arbitrary type + +`E0117` is the orphan rule's error for implementing a *foreign* trait for a *foreign* type when no local type is involved at all — the impl is wholly foreign, so this crate may not write it. + +## What it means + +The compiler prints "only traits defined in the current crate can be implemented for arbitrary types", with the canonical example `impl Drop for u32 {}` — both `Drop` and `u32` are foreign. It enforces the same **orphan rule** as [`E0210`](e0210.md), the cross-crate half of coherence, and the two differ only in shape: `E0117` fires when the offending types are foreign *concrete* (nominal) types with no local type anywhere, while `E0210` fires when a *type parameter* is left uncovered. In both, the fix is that at least one local type must take part in the impl. + +## The rule behind it + +The orphan rule exists so that coherence holds across crates: were a crate allowed to implement a foreign trait for a foreign type, two crates could do so incompatibly and adding a dependency could break a build. Requiring a local type in the impl guarantees the impl "belongs" to a crate that owns one of its pieces. The rule comes from [RFC 2451 (re-rebalancing coherence)](https://rust-lang.github.io/rfcs/2451-re-rebalancing-coherence.html), building on RFC 1023; the [`E0210`](e0210.md) entry describes the parameter-covering ordering that the rule also imposes when type parameters are present. Which of the two codes `rustc` emits for a given foreign-trait impl depends on whether an uncovered parameter is involved, and the boundary between them is a known `rustc` wart, so treat the pair as one rule with two messages. + +## Where CGP produces it + +- [Orphan-rule violation](../wiring/orphan-rule.md) — the same namespace-registration mistakes as [`E0210`](e0210.md); which code appears depends on the exact shape of the generated impl, but both are the orphan rule rejecting a foreign-trait-for-foreign-type registration. + +## References + +- [Error index: E0117](https://doc.rust-lang.org/error_codes/E0117.html) +- [RFC 2451 — re-rebalancing coherence](https://rust-lang.github.io/rfcs/2451-re-rebalancing-coherence.html) +- [Error index: E0210](https://doc.rust-lang.org/error_codes/E0210.html) — the sibling orphan error for an uncovered type parameter diff --git a/docs/errors/error_codes/e0119.md b/docs/errors/error_codes/e0119.md new file mode 100644 index 00000000..9c9081a6 --- /dev/null +++ b/docs/errors/error_codes/e0119.md @@ -0,0 +1,24 @@ +# E0119 — conflicting implementations + +`E0119` is Rust's coherence error: two trait implementations could both apply to the same type, so the compiler rejects the pair rather than pick one. + +## What it means + +The compiler prints "conflicting implementations of trait `MyTrait` for type `Foo`", with two carets — "first implementation here" on the earlier impl and "conflicting implementation for `Foo`" on the later one. It fires whenever two impls overlap: two impls for the same concrete type, or — the common CGP shape — a blanket impl (`impl Trait for T where …`) whose applicability overlaps another impl. The rule it enforces is **coherence**: for any trait and type there must be at most one applicable impl, so that a method call resolves unambiguously. + +## The rule behind it + +Coherence reasons not only about the impls in scope but about impls other crates *could add*, which is why some `E0119`s carry an extra note. When a blanket impl's applicability to a type hinges on a trait bound a downstream crate could later satisfy, the compiler cannot prove the two impls will never overlap, so it rejects them up front and cites the hypothetical impl: `note: downstream crates may implement trait `…` for type `…`` (or the `upstream crates may add a new impl … in future versions` variant). This future-compatibility, "negative reasoning" behavior is what makes adding a blanket impl a potential breaking change, and it is formalized by [RFC 2451 (re-rebalancing coherence)](https://rust-lang.github.io/rfcs/2451-re-rebalancing-coherence.html), building on RFC 1023. A conflict the compiler can prove *directly* from the impls in front of it — two blanket impls over the same key, say — needs no such reasoning and prints without the downstream note. + +## Where CGP produces it + +CGP emits overlapping impls whenever a key or a blanket forwarding is wired twice, and the catalog splits these by usage: + +- [Conflicting wiring](../wiring/conflicting-wiring.md) — the same key or generated name wired or declared twice (plus [`E0428`](e0428.md) for a duplicate name). +- [Overlapping namespace forwarding](../wiring/namespace-forwarding-conflict.md) — two blanket forwarding impls over every key (fully-generic types, no downstream note). +- [Namespace override conflict](../wiring/namespace-override-conflict.md) — a specific entry overriding a key a namespace already claims (the context-level shape carries the downstream note). + +## References + +- [Error index: E0119](https://doc.rust-lang.org/error_codes/E0119.html) +- [RFC 2451 — re-rebalancing coherence](https://rust-lang.github.io/rfcs/2451-re-rebalancing-coherence.html) (the orphan and coherence rules, and the downstream/upstream negative reasoning) diff --git a/docs/errors/error_codes/e0207.md b/docs/errors/error_codes/e0207.md new file mode 100644 index 00000000..1719244c --- /dev/null +++ b/docs/errors/error_codes/e0207.md @@ -0,0 +1,20 @@ +# E0207 — unconstrained type parameter + +`E0207` is Rust's rule that every generic parameter on an impl must be *determined* by the impl — appearing in the trait, the self type, or a predicate that pins it — so a parameter that reaches only an associated-type value is rejected. + +## What it means + +The compiler prints "the type parameter `T` is not constrained by the impl trait, self type, or predicates". An impl parameter must occupy at least one of three "constrained" positions: it must appear in the **implemented trait** reference, in the **self type**, or in a **`where`-clause predicate** that binds it as an associated type (`where T: Trait`). A parameter that appears only on the right of an associated-type equality — the *value* it projects to — does not constrain the impl, because a trait reference to that impl names no `T`, so any `T` would satisfy the header equally. + +## The rule behind it + +The requirement exists because impls cannot be named or instantiated explicitly, so an unconstrained parameter has no basis for inference and yields ill-defined semantics; it would also break coherence, since a downstream crate could add another value for the same parameter and make the choice ambiguous. This is the same negative-reasoning concern that underlies the orphan rule and [`E0119`](e0119.md). The rule comes from [RFC 447 (no unused impl parameters)](https://github.com/rust-lang/rfcs/blob/master/text/0447-no-unused-impl-parameters.md). + +## Where CGP produces it + +- [Unconstrained generic](../wiring/unconstrained-generic.md) — a per-entry generic in `delegate_components!` that appears only in the provider value and never reaches the key, or a generic provider registered as a per-type default, so the parameter lands only in the `Delegate` associated-type position. + +## References + +- [Error index: E0207](https://doc.rust-lang.org/error_codes/E0207.html) +- [RFC 447 — no unused impl parameters](https://github.com/rust-lang/rfcs/blob/master/text/0447-no-unused-impl-parameters.md) diff --git a/docs/errors/error_codes/e0210.md b/docs/errors/error_codes/e0210.md new file mode 100644 index 00000000..a76d9742 --- /dev/null +++ b/docs/errors/error_codes/e0210.md @@ -0,0 +1,21 @@ +# E0210 — orphan rule: uncovered type parameter + +`E0210` is the orphan rule's error for an impl of a *foreign* trait in which a type parameter is left uncovered by any local type, so the impl is not permitted in this crate. + +## What it means + +The compiler prints "type parameter `T` must be used as the type parameter for some local type (e.g., `MyStruct`)", with the note "only traits defined in the current crate can be implemented for a type parameter". The classic trigger is `impl ForeignTrait for T {}` — the parameter `T` is *uncovered*, appearing bare rather than wrapped in a local type. It enforces the **orphan rule**, the part of coherence that spans crates: a foreign trait may be implemented only when a local type is present and positioned so the impl cannot collide with one another crate might add. + +## The rule behind it + +For an impl `impl ForeignTrait for T0`, the orphan rule requires that at least one of `T0…Tn` be a local type — call the first such type `Ti` — and that no uncovered type parameter appear before `Ti`. A type is "covered" when it appears as a parameter to another type: the `T` in `Vec` is covered, a bare `T` is not. `E0210` is the specific failure where no local type covers the parameters, so a bare parameter stands in a position coherence cannot allow. Permitting it would let two unrelated crates implement the foreign trait for the parameter incompatibly, and adding a dependency could silently break a build — exactly what the rule prevents. The rule and its wording come from [RFC 2451 (re-rebalancing coherence)](https://rust-lang.github.io/rfcs/2451-re-rebalancing-coherence.html), building on RFC 1023. Its sibling [`E0117`](e0117.md) covers the case where the types are foreign but concrete (no parameter involved). + +## Where CGP produces it + +- [Orphan-rule violation](../wiring/orphan-rule.md) — a namespace registration (`#[default_impl]` on a prefixed component or a foreign marker, or a `cgp_namespace!` re-open without `new`) that implements a foreign namespace trait for a foreign key, leaving the table parameter (`__Components__` or `__Table__`) uncovered. + +## References + +- [Error index: E0210](https://doc.rust-lang.org/error_codes/E0210.html) +- [RFC 2451 — re-rebalancing coherence](https://rust-lang.github.io/rfcs/2451-re-rebalancing-coherence.html) (the covered/uncovered ordering rule) +- [Error index: E0117](https://doc.rust-lang.org/error_codes/E0117.html) — the sibling orphan error for foreign concrete types diff --git a/docs/errors/error_codes/e0275.md b/docs/errors/error_codes/e0275.md new file mode 100644 index 00000000..6c07c7a0 --- /dev/null +++ b/docs/errors/error_codes/e0275.md @@ -0,0 +1,23 @@ +# E0275 — overflow evaluating the requirement + +`E0275` is what the trait solver reports when proving a bound exceeds the recursion limit without terminating — either a genuinely deep obligation or, in CGP, an infinite cycle. + +## What it means + +The compiler prints "overflow evaluating the requirement `…`", naming the bound whose evaluation recursed too far, followed by a `note:` chain of the supporting requirements and the standard `help: consider increasing the recursion limit by adding a #![recursion_limit = "…"] attribute`. The trait solver walks a requirement's supporting bounds only to a bounded depth — the [default `recursion_limit` is 128](https://doc.rust-lang.org/reference/attributes/limits.html) — and raises `E0275` when it passes that depth without resolving. + +## The rule behind it + +The recursion limit is a termination safeguard, not a correctness rule: it stops the solver from looping forever on a self-supporting bound. The `help:` to raise the limit is generic advice printed for *every* overflow, and it applies only when the obligation is merely *deep* — a finite chain that a higher limit would clear. When the requirement is a true **cycle** — a bound that supports itself, so the chain never terminates at any depth — no limit is high enough, and the fix is to break the cycle, not raise the limit. Reading an `E0275` therefore starts with deciding whether the recurring requirement in the `note:` chain is deep or circular. + +## Where CGP produces it + +Both CGP sources are true cycles, where raising the limit never helps: + +- [Wiring cycle](../wiring/wiring-cycle.md) — a component delegated to `UseContext` whose only implementation is that delegation, so the lookup chases its own tail; surfaces only when forced through a check (hidden as [`E0599`](e0599.md) when called). +- [Namespace inheritance cycle](../wiring/namespace-inheritance-cycle.md) — namespaces whose parent chain loops, caught *eagerly* at the `cgp_namespace!` definitions. + +## References + +- [Error index: E0275](https://doc.rust-lang.org/error_codes/E0275.html) +- [Rust reference — the `recursion_limit` attribute](https://doc.rust-lang.org/reference/attributes/limits.html) (default 128) diff --git a/docs/errors/error_codes/e0277.md b/docs/errors/error_codes/e0277.md new file mode 100644 index 00000000..485090c4 --- /dev/null +++ b/docs/errors/error_codes/e0277.md @@ -0,0 +1,29 @@ +# E0277 — trait bound not satisfied + +`E0277` is Rust's general "a required trait bound is not satisfied" error — the code most CGP dependency and lookup failures surface under once a check forces them. + +## What it means + +The compiler prints "the trait bound `X: Trait` is not satisfied", with the label "the trait `Trait` is not implemented for `X`" and often a `help:` listing types that *do* implement the trait, followed by a `required for …` chain tracing why the bound was needed. It fires whenever the trait solver must prove a bound it cannot discharge — a missing impl, an unmet `where` clause, or a supertrait obligation. + +One special case is worth recognizing because its message does not match the generic form. When the unmet bound is `Sized` on an unsized type, `rustc` reports "the size for values of type `[u8]` cannot be known at compilation time" with the note "the trait `Sized` is not implemented for `[u8]`" — still `E0277`, still an unsatisfied `Sized` bound, but phrased in terms of size rather than as `[u8]: Sized`. `Sized` is an implicit bound on ordinary generic parameters (a `T` is `Sized` unless written `?Sized`), so a [dynamically sized type](https://doc.rust-lang.org/reference/dynamically-sized-types.html) reaching such a position triggers this form. + +## The rule behind it + +`E0277` is not a single rule but the solver's report that some obligation is unsatisfiable; its meaning is entirely in *which* bound is named and *why it was required*, which the `required for …` chain records. In CGP the required-for chain almost always runs through [`IsProviderFor`](../../reference/traits/is_provider_for.md) and `CanUseComponent`, the supertraits a check uses to force the compiler to evaluate a provider's `where` clause and name the real leaf — the same bound the [hidden `E0599`](e0599.md) form suppresses. + +## Where CGP produces it + +`E0277` is the shared code of the surfaced (checked) dependency and lookup failures: + +- [Check-trait failure](../checks/check-trait-failure.md) — a check surfacing an unmet CGP-capability leaf (a `HasField`), stated in a `help:` note. +- [Unsatisfied ordinary trait bound](../checks/ordinary-trait-bound.md) — a check surfacing an unmet *ordinary* trait (`f64: Eq`) as the primary error. +- [Unregistered namespace path](../checks/unregistered-namespace-path.md) — a namespace redirect landing on a path no entry binds, reported as an unsatisfied `DefaultNamespace`/`DelegateComponent` lookup. +- [Verbose dependency cascade](../checks/verbose-cascade.md) — the same surfaced `E0277` multiplied across transitively dependent providers. +- [Ill-formed generated type](../lowering/ill-formed-generated-type.md) — the `Sized` special case, from a macro lowering a shorthand into an unsized type. +- [Unsatisfied dependency (hidden)](../hidden/unsatisfied-dependency.md) — the `E0277` variant when a consumer trait is used as a `where` bound rather than called. + +## References + +- [Error index: E0277](https://doc.rust-lang.org/error_codes/E0277.html) +- [Rust reference — dynamically sized types](https://doc.rust-lang.org/reference/dynamically-sized-types.html) (the `Sized` special case) diff --git a/docs/errors/error_codes/e0428.md b/docs/errors/error_codes/e0428.md new file mode 100644 index 00000000..291d2c15 --- /dev/null +++ b/docs/errors/error_codes/e0428.md @@ -0,0 +1,19 @@ +# E0428 — name defined multiple times + +`E0428` is Rust's duplicate-definition error: two items define the same name in one namespace of one scope, so the compiler cannot tell which the name refers to. + +## What it means + +The compiler prints "the name `X` is defined multiple times", with "previous definition of the type `X` here" on the first definition and "`X` redefined here" on the second, closing with "`X` must be defined only once in the type namespace of this module". It fires when a struct, enum, trait, module, or other item is declared twice under the same name in the same scope. Unlike a conflicting *impl* ([`E0119`](e0119.md)), this is a collision between *definitions* — two items claiming one name, not two impls overlapping. + +## The rule behind it + +A name in a given namespace of a scope must resolve to exactly one item, so the compiler rejects a second definition of the same name rather than silently shadow or merge it. Rust keeps separate namespaces for types, values, and macros, so the message names which namespace the clash is in ("the type namespace"). The fix is to rename or remove one definition. + +## Where CGP produces it + +- [Conflicting wiring](../wiring/conflicting-wiring.md) — a generated name declared twice: a `#[cgp_component]`-derived `…Component` marker clashing with a hand-declared type, or two `#[cgp_impl(new …)]` declaring the same provider struct (which adds an [`E0119`](e0119.md) pair on the provider's own impls on top of the `E0428`). + +## References + +- [Error index: E0428](https://doc.rust-lang.org/error_codes/E0428.html) diff --git a/docs/errors/error_codes/e0576.md b/docs/errors/error_codes/e0576.md new file mode 100644 index 00000000..675225ea --- /dev/null +++ b/docs/errors/error_codes/e0576.md @@ -0,0 +1,20 @@ +# E0576 — associated item not found + +`E0576` is Rust's diagnostic for naming an associated item — a type, constant, or function — that the referenced trait or type does not declare, so the qualified path resolves to nothing. + +## What it means + +The compiler prints "cannot find associated type `Name` in trait `Trait`" (or the constant/function variant), pointing at the offending path segment. It arises when a fully-qualified associated-item reference such as `::Name` names an item the trait does not define — a misspelling, a stale name after a rename, or an item that lives on a different trait. When a similarly named item exists, `rustc` adds a "similarly named associated type … defined here" note and a `help:` suggesting the correct spelling, so the fix is usually visible in the diagnostic itself. + +## The rule behind it + +Associated-item resolution is name-based: a qualified path `::Name` is resolved by looking `Name` up among `Trait`'s declared associated items, and a name with no matching declaration is a resolution error rather than a trait-solving failure. It is reported early, during name resolution and type collection, before any bound is evaluated — which is why the caret lands on the path itself rather than on a `where` clause. This is ordinary path resolution as described in the [Rust reference on paths](https://doc.rust-lang.org/reference/paths.html); there is no RFC-level subtlety, only that the item must exist to be named. + +## Where CGP produces it + +- [Unresolved imported abstract type](../lowering/unresolved-imported-type.md) — a `#[use_type]` import names an associated type the owning trait does not declare, so the macro rewrites the bare alias into a `::WrongName` path that resolves to nothing. Because the substitution preserves the identifier's span, the caret lands on the name the user wrote. + +## References + +- [Error index: E0576](https://doc.rust-lang.org/error_codes/E0576.html) +- [Rust reference — paths](https://doc.rust-lang.org/reference/paths.html) diff --git a/docs/errors/error_codes/e0599.md b/docs/errors/error_codes/e0599.md new file mode 100644 index 00000000..b6bab0d4 --- /dev/null +++ b/docs/errors/error_codes/e0599.md @@ -0,0 +1,26 @@ +# E0599 — method exists but its trait bounds were not satisfied + +`E0599` is the method-resolution error Rust reports when a method cannot be called — and, in its "trait bounds were not satisfied" form, the one that *hides* which bound failed, which is why CGP's hidden failures surface under it. + +## What it means + +The compiler prints "the method `foo` exists for struct `X`, but its trait bounds were not satisfied", names the top-level traits it could not satisfy (`X: SomeTrait`), and then stops — it does not, in general, descend into and name the specific unmet `where`-clause bound of an inapplicable blanket or generic impl. It fires when the method probe finds a candidate method whose enabling impl does not apply to the receiver because some bound is unmet. + +## The rule behind it + +The suppression is a deliberate diagnostic heuristic, not a language rule. When a blanket impl sits among the candidate impls and one of its `where` bounds is unmet, expanding every candidate's failed sub-bound would usually be noise, so `rustc` reports only that the top-level trait is unimplemented. The cost is that the *actual* cause — the nested bound — is absent from the output, not merely buried. This is a long-standing, acknowledged limitation, tracked in [rust-lang/rust#61661](https://github.com/rust-lang/rust/issues/61661) (a closed issue whose fix improved some cases) and [#75222](https://github.com/rust-lang/rust/issues/75222) (still open). The way to recover the cause is to force the obligation to be proven *directly* — which is exactly what a CGP check does by requiring [`IsProviderFor`](../../reference/traits/is_provider_for.md), promoting the failure into a surfaced [`E0277`](e0277.md). + +In CGP this form also carries a stock misdirection: because a provider-trait method has no `self` receiver, the probe classifies it as an associated function and suggests `X::foo()`, which drops the context argument and says nothing about the missing dependency — noise to read past. + +## Where CGP produces it + +`E0599` is the shape of every *hidden* CGP failure — broken wiring reached by a consumer-trait method call rather than a check: + +- [Unsatisfied dependency (hidden)](../hidden/unsatisfied-dependency.md) — the central case; the unmet dependency (a `HasField`, a capability, or an [ordinary trait bound](../checks/ordinary-trait-bound.md)) is suppressed. +- [Wiring cycle](../wiring/wiring-cycle.md) and [Unregistered namespace path](../checks/unregistered-namespace-path.md) — a cycle or an empty lookup, which also hide as this `E0599` when reached by a call instead of a check. + +## References + +- [Error index: E0599](https://doc.rust-lang.org/error_codes/E0599.html) +- [rust-lang/rust#61661](https://github.com/rust-lang/rust/issues/61661) — improve E0599 for unsatisfied `where`-clause bounds (closed; partial fix) +- [rust-lang/rust#75222](https://github.com/rust-lang/rust/issues/75222) — confusing notes when per-method `where` bounds are unsatisfied (open) diff --git a/docs/errors/hidden/unsatisfied-dependency.md b/docs/errors/hidden/unsatisfied-dependency.md index edc93db7..b03f0568 100644 --- a/docs/errors/hidden/unsatisfied-dependency.md +++ b/docs/errors/hidden/unsatisfied-dependency.md @@ -4,7 +4,7 @@ A provider is wired onto a context that cannot satisfy the provider's impl-side ## What triggers it -This class arises whenever [wiring](../../reference/macros/delegate_components.md) is lazy and the mistake is exercised through the consumer trait rather than a check. A provider carries an impl-side dependency in its `where` clause; a context is wired to that provider but does not meet the dependency; and because `delegate_components!` records the entry without verifying the provider's transitive requirements, the wiring is accepted. The failure appears only when the context's consumer-trait method is finally called. +This class arises whenever [wiring](../../reference/macros/delegate_components.md) is lazy and the mistake is exercised through the consumer trait rather than a check. A provider carries an impl-side dependency in its `where` clause; a context is wired to that provider but does not meet the dependency; and because `delegate_components!` records the entry without verifying the provider's transitive requirements, the wiring is accepted. The failure appears only when the context's consumer-trait method is finally called. The dependency's leaf can be any bound the provider names — a missing `HasField`, an unmet CGP capability like `HasName`, or an [ordinary Rust trait bound](../checks/ordinary-trait-bound.md) such as `Scalar: Eq` on an abstract type — and the hidden form suppresses it identically in every case, because the compiler's method-probe heuristic drops the nested bound without inspecting what it is. ```rust #[cgp_component(Greeter)] @@ -83,10 +83,12 @@ A `cargo-cgp`-style post-processor **cannot extract the root cause from this dia ## Backing fixtures - [acceptable/delegate_components/missing_dependency.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/missing_dependency.rs) — `GreetHello` requires `Self: HasName`, `Person` lacks the `name` field, and the method is called directly; its `.stderr` pins the `E0599` shape that names `Person: Greeter` without descending to the missing field. Its checked counterpart, [acceptable/check_components/missing_dependency.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_dependency.rs), pins the surfaced `E0277` for the same mistake and belongs to the [check-trait failure](../checks/check-trait-failure.md) class. +- [acceptable/delegate_components/ordinary_bound_unsatisfied.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/ordinary_bound_unsatisfied.rs) — the same hidden `E0599` shape where the suppressed leaf is an *ordinary* trait bound (`Scalar: Eq`, with `f64` wired) rather than a `HasField`, confirming the heuristic drops the nested bound regardless of its kind. Its surfaced counterpart is the [unsatisfied ordinary trait bound](../checks/ordinary-trait-bound.md) class. ## Related - [Check-trait failure (surfaced)](../checks/check-trait-failure.md) — the same unmet dependency forced through a check, where the cause *is* reported; the two are the two halves of one phenomenon. +- [Unsatisfied ordinary trait bound (surfaced)](../checks/ordinary-trait-bound.md) — the surfaced class for the ordinary-trait-bound flavor of this hidden failure. - [Debugging CGP compile errors](../../guides/debugging.md) — the playbook: read the error's shape, then move the error to the wiring site with a check to surface a hidden cause. - [`IsProviderFor`](../../reference/traits/is_provider_for.md) and [`DelegateComponent`](../../reference/traits/delegate_component.md) — the traits every wiring error is ultimately about; `IsProviderFor` is the supertrait a check uses to defeat the suppression heuristic. - [`check_components!`](../../reference/macros/check_components.md) and the [check-traits concept](../../concepts/check-traits.md) — why wiring is lazy and how a check forces a readable error at the wiring site. diff --git a/docs/errors/lowering/ill-formed-generated-type.md b/docs/errors/lowering/ill-formed-generated-type.md new file mode 100644 index 00000000..67d1715c --- /dev/null +++ b/docs/errors/lowering/ill-formed-generated-type.md @@ -0,0 +1,49 @@ +# Ill-formed generated type + +A macro accepts a field- or argument-type *shorthand* it has no dedicated rule for, lowers it literally, and the generated bound names a type that is not well-formed — most often an unsized type — so the compiler rejects it with `E0277` (the `Sized` form). + +## What triggers it + +This class is distinct from every other in the catalog: it is neither a wiring mistake nor a lazy dependency, but a macro *lowering* that produces a type the compiler cannot accept. CGP's getter and implicit-argument macros recognize a small set of field-type shorthands — `Option<&T>` for an optional field, `&[T]` for a slice field, `&str` for a `String` field — and lower each to a `HasField` bound and an accessor. Each shorthand covers a single field shape. A *combination* they provide no rule for is not rejected at macro time; it is lowered literally by whichever single rule matches first, and the resulting bound names an ill-formed type that only the compiler catches. + +The worked case is a getter typed `Option<&[T]>`, which combines the `Option<&T>` and `&[T]` shorthands: + +```rust +#[cgp_auto_getter] +pub trait HasItems { + fn items(&self) -> Option<&[u8]>; +} +``` + +The shared `parse_field_type` applies the `Option<&T>` rule, reading an `Option` field whose `T` is the slice `[u8]`, so the generated `HasField` bound names the unsized `Option<[u8]>`. Because `[u8]` is a dynamically sized type, `Option<[u8]>` is ill-formed, and the compiler rejects it. This boundary is shared by [`#[cgp_auto_getter]`](../../reference/macros/cgp_auto_getter.md), [`#[cgp_getter]`](../../reference/macros/cgp_getter.md), and a [`#[cgp_fn]`](../../reference/macros/cgp_fn.md) [`#[implicit]`](../../guides/reading-context-fields.md) argument, since all three lower field types through `parse_field_type`. CGP is working as designed: the shorthands ease the common single-shape cases, and an unsupported combination is deferred to the compiler rather than given a bespoke rule — an [acceptable failure](../../implementation/AGENTS.md), not a defect. + +## The diagnostic + +This is a **surfaced** class, and unusually the root cause is the *primary* error, stated in plain terms. The compiler reports **`E0277`** in its dedicated `Sized` form — "the size for values of type `[u8]` cannot be known at compilation time," with the label "doesn't have a size known at compile-time" and a `help:` note "the trait `Sized` is not implemented for `[u8]`." A following `note: required by an implicit Sized bound in Option` names *where* the size is required: the `T` in `Option` carries an implicit `Sized` bound, and `[u8]` cannot meet it. The caret lands on the macro attribute (`#[cgp_auto_getter]`) because the offending type is synthesized in the expansion, and a closing note attributes the error to that attribute macro. + +Recognizing the exact message form matters, because rustc special-cases the missing-`Sized` case rather than printing a generic trait-bound line. It does **not** say "the trait bound `[u8]: Sized` is not satisfied"; it says "the size for values of type `[u8]` cannot be known at compilation time" and names `Sized` only in the `help:` note. The underlying unmet trait is still `Sized` under error code `E0277`, but the headline is the size-specific phrasing. This is the standard `rustc` diagnostic for a [dynamically sized type](https://doc.rust-lang.org/reference/dynamically-sized-types.html) reaching a position that requires a statically known size — a generic type parameter like `Option`'s `T`, which carries an implicit `Sized` bound unless written `T: ?Sized`. + +A second error usually follows and is derived noise, not a separate cause. The getter body calls `.as_ref()` on the ill-formed value, so the compiler adds an **`E0599`** — "the method `as_ref` exists for reference `&Option<[u8]>`, but its trait bounds were not satisfied" — whose notes restate `[u8]: Sized` and `Option<[u8]>: AsRef<_>` as the unmet bounds. It is a consequence of the same unsized type, downstream of the `E0277`, and points at no new mistake. + +## Where the root cause is + +The root cause is **present and sits at the top of the output**, in the primary `E0277` and its `help:` note naming `Sized` for the concrete unsized type. This is among the clearest diagnostics in the catalog: the compiler states exactly which type has no known size and where the size is required, so there is no note chain to walk and nothing suppressed. The `E0599` on `as_ref` below it is a derived consequence to skip. What the raw message does not supply is the CGP-specific reading — that the unsized type came from an *unsupported shorthand combination* the macro lowered literally, and that the fix is to change the field type rather than the wiring — because the compiler sees only the synthesized type, not the shorthand the user wrote. + +## Resolving it + +Change the field or argument type to a shape a single shorthand supports, so the macro lowers it to a well-formed bound. Use an owned optional container instead of an optional slice — `Option>` (returned by reference, `Option<&Vec>`, or by value) or `Option<&'static [u8]>` where a borrow of static data fits — rather than `Option<&[u8]>`, whose lowering names the unsized `Option<[u8]>`. When neither shorthand fits the shape you need, write the getter or accessor by hand: the macros only save boilerplate over a plain `HasField` impl, so a hand-written trait impl can name whatever well-formed type the field actually has. The rule to carry away is that the shorthands compose only where their single-shape lowering yields a `Sized` type; a combination that would produce a dynamically sized type is out of scope by design. + +## Notes for tooling + +For a `cargo-cgp`-style post-processor, the fact to extract is already the headline — the concrete unsized type and the `Sized` requirement — so the tool's job is **translation, not recovery**: recognize an `E0277` `Sized` error (and its trailing `as_ref`/accessor `E0599`) whose caret is a getter or implicit-argument macro, and report "the field type combines shorthands into the unsized `Option<[u8]>`; CGP has no rule for `Option<&[T]>` — use an owned container or write the accessor by hand." Suppressing the derived `E0599` and pointing at the shorthand rather than the synthesized `Option<[u8]>` is the whole value added; the cause itself needs no compiler-internal introspection. + +## Backing fixtures + +- [acceptable/cgp_auto_getter/option_slice.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_auto_getter/option_slice.rs) — a `#[cgp_auto_getter]` returning `Option<&[u8]>`, lowered to a `HasField` bound over the unsized `Option<[u8]>`; its `.stderr` pins the primary `E0277` "the size for values of type `[u8]` cannot be known at compilation time" with the `Sized` `help:` note, the "required by an implicit `Sized` bound in `Option`" note, and the derived `E0599` on `as_ref`. + +## Related + +- [`#[cgp_auto_getter]`](../../reference/macros/cgp_auto_getter.md), [`#[cgp_getter]`](../../reference/macros/cgp_getter.md), [`#[cgp_fn]`](../../reference/macros/cgp_fn.md), and [reading context fields](../../guides/reading-context-fields.md) — the macros whose shared `parse_field_type` lowers the shorthands, and the field-type forms they support. +- [`cgp_auto_getter` implementation document](../../implementation/entrypoints/cgp_auto_getter.md) — the Behavior-and-corner-cases and Failure-modes account of why the unsupported combination is deferred to the compiler. +- [`HasField`](../../reference/traits/has_field.md) — the trait the generated bound targets, whose value type must be `Sized` here. +- [`E0277`](../error_codes/e0277.md) and [dynamically sized types](https://doc.rust-lang.org/reference/dynamically-sized-types.html) — the error-code reference (including the `Sized` special case) and the Rust reference for the size requirement the generated type violates. diff --git a/docs/errors/lowering/unresolved-imported-type.md b/docs/errors/lowering/unresolved-imported-type.md new file mode 100644 index 00000000..05ce2d70 --- /dev/null +++ b/docs/errors/lowering/unresolved-imported-type.md @@ -0,0 +1,56 @@ +# Unresolved imported abstract type + +A `#[use_type]` import names an associated type the owning trait does not declare, so the macro rewrites the bare alias into a `::WrongName` path that resolves to nothing and the compiler rejects it with `E0576`. + +## What triggers it + +This class is a *lowering* failure, not a wiring one: the mistake is a misnamed associated type in the `#[use_type]` attribute itself, which the macro cannot validate at expansion time. `#[use_type]` works by textual substitution — it rewrites every bare use of an imported alias into `::Assoc` — and it has no knowledge of which associated types the trait actually declares. So an alias that names a nonexistent associated type is lowered faithfully into a qualified path, and only the compiler, resolving that path, discovers the name is not there. + +The canonical case is a typo in the imported type name: + +```rust +#[cgp_type] +pub trait HasScalarType { + type Scalar; +} + +#[cgp_fn] +#[use_type(HasScalarType.Scalr)] // typo: the trait declares `Scalar`, not `Scalr` +pub fn get_scalar(&self) -> Scalr { + todo!() +} +``` + +The import declares the alias `Scalr`, so the bare `Scalr` in the return type is rewritten to `::Scalr`, which names an associated type `HasScalarType` never declares. The same failure arises from a stale name after a trait rename, or from importing an item that lives on a different trait than the one named. CGP is working as designed here: it cannot see the trait's item list during expansion, so it lowers the name literally and defers to the compiler — an [acceptable failure](../../implementation/AGENTS.md), not a defect. + +## The diagnostic + +This is a **surfaced** class with the simplest diagnostic in the catalog: a single [`E0576`](../error_codes/e0576.md), "cannot find associated type `Scalr` in trait `HasScalarType`", with the caret on the offending name. Because the substitution copies the *span* of the identifier the user wrote onto the rewritten path, the caret lands on the `Scalr` in the user's signature — the token they actually typed — rather than on the `#[use_type]` attribute or the whole macro block. When a similarly named item exists, `rustc` adds a "similarly named associated type `Scalar` defined here" note pointing at the trait's real declaration and a `help:` that suggests the correct spelling inline. There is no note chain, no CGP scaffolding, and no `IsProviderFor`/`DelegateComponent` frame, because the failure is caught during name resolution, before any bound is evaluated. + +## Where the root cause is + +The root cause is **present and is the entire diagnostic** — the primary `E0576` names both the missing associated type and the trait it was sought in, and the caret points at the exact token to change. This is the opposite of the hidden and cascading classes: nothing is suppressed, nothing is buried, and there is no verbosity to wade through. The `help:` note, when present, even supplies the fix. The only CGP-specific fact the message does not state is that the name came from a `#[use_type]` import — but the caret sits on the alias in the signature, which is the same name written in the attribute, so following it to the attribute is immediate. + +## Resolving it + +Correct the imported name so it matches an associated type the trait declares — change `#[use_type(HasScalarType.Scalr)]` and every use of the alias to `Scalar`, or use an `as` clause (`#[use_type(HasScalarType.{Scalar as Scalr})]`) if the short local name was intended. The `help:` note usually names the correct spelling outright. If the type genuinely lives on a different trait, point the import at that trait instead. Because the diagnostic is a plain resolution error at the user's own token, no CGP-specific tracing is needed. + +## Notes for tooling + +This class needs no special tool handling beyond recognizing its origin. A `cargo-cgp`-style post-processor should pass the `E0576` through essentially unchanged — the caret and the `help:` already point at the fix — and at most annotate that the name was introduced by a `#[use_type]` import so a user who does not see the connection between the signature alias and the attribute can find it. There is nothing to suppress and no hidden cause to recover. + +## A sibling: the unresolved *context* + +The same textual-substitution mechanism produces a related failure when the part that cannot be resolved is the `in Context`, not the associated-type name. Two nested imports whose `in Context` clauses reference each other — `#[use_type(HasA.A in B, HasB.B in A)]` — form a cycle with no valid grounding order. Grounding iterates to a fixpoint and deliberately stops rather than loops, so the context aliases are never resolved and the rewrite leaves the bare `A` and `B` in type position. The compiler reports this as [`E0425`](https://doc.rust-lang.org/error_codes/E0425.html) "cannot find type" — a *type*, not an associated type, so a different code from the misnamed-name case above — with the caret on the unresolved alias the user wrote in the attribute. The fix is the same in spirit: make the imports form an acyclic chain (any acyclic order grounds fine). CGP could detect the cycle locally and reject it at macro time, but currently lowers it faithfully and defers to the compiler. + +## Backing fixtures + +- [acceptable/cgp_fn/use_type_unknown_assoc.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_unknown_assoc.rs) — a `#[cgp_fn]` importing `HasScalarType.Scalr` where the trait declares `Scalar`; its `.stderr` pins the `E0576` with the caret on the `Scalr` in the signature and the "similarly named associated type" `help:`, doubling as a guard that the substitution preserves the user's identifier span. +- [acceptable/cgp_fn/use_type_cyclic_context.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_cyclic_context.rs) — the unresolved-*context* sibling: two `in Context` clauses referencing each other, whose `.stderr` pins the `E0425` "cannot find type" with the caret on the unresolved `in` alias. + +## Related + +- [`#[use_type]`](../../reference/attributes/use_type.md) and [`#[cgp_type]`](../../reference/macros/cgp_type.md) — the import attribute whose textual substitution produces the unresolved path, and the macro defining the abstract-type trait it imports from. +- [Ill-formed generated type](ill-formed-generated-type.md) — the sibling lowering class, where the generated type is *resolvable* but not well-formed (an unsized type) rather than unresolvable by name. +- [`E0576`](../error_codes/e0576.md) — the Rust error code this class is reported under. +- [Debugging CGP compile errors](../../guides/debugging.md) — the prescriptive playbook this catalog supports. diff --git a/docs/errors/wiring/conflicting-wiring.md b/docs/errors/wiring/conflicting-wiring.md index a10f35c6..2de3b855 100644 --- a/docs/errors/wiring/conflicting-wiring.md +++ b/docs/errors/wiring/conflicting-wiring.md @@ -20,15 +20,17 @@ delegate_components! { Wrapper { GreeterComponent: GreetHello } } #[cgp_impl(new GreetHello)] impl Greeter { /* … */ } ``` -The same shape appears with an `open` header colliding with an explicit mapping, a `@`-path duplicated under a `namespace` header, a context that joins a namespace and *also* directly wires a path the namespace registers, a `for … in` loop that wires a *bare* key alongside a `namespace` join (both generate a blanket `DelegateComponent` impl over every key), a duplicate `cgp_namespace!` entry, a `#[cgp_component]`-derived marker clashing with a hand-declared type, a duplicate `#[default_impl]` key, or a duplicate `check_components!` entry. +The same shape appears with an `open` header colliding with an explicit mapping, a `@`-path duplicated under a `namespace` header, a duplicate `cgp_namespace!` entry, a `#[cgp_component]`-derived marker clashing with a hand-declared type, a duplicate `#[default_impl]` key, or a duplicate `check_components!` entry. + +Two namespace collisions that also produce `E0119` are documented separately, because their mistake is not a duplicate declaration but a namespace usage: two blanket forwardings that overlap (joining two namespaces, or a namespace join plus a bare-key `for` loop) are the [overlapping namespace forwarding](namespace-forwarding-conflict.md) class, and a specific entry that overrides a key a namespace already claims (a context re-wiring a registered path, or a child namespace redefining an inherited entry) is the [namespace override conflict](namespace-override-conflict.md) class. This document covers the plain duplicate-or-overlapping *declaration*. ## The diagnostic -Two error codes, by whether the collision is between *impls* or between *definitions*. A duplicate key or overlapping generic produces **`E0119` conflicting implementations**; a duplicate generated *name* produces **`E0428` "the name … is defined multiple times"**. Both point precisely: `E0119` carries two carets — "first implementation here" on the earlier entry and "conflicting implementation for ``" on the later one, aimed at the offending keys rather than the whole block — and `E0428` carries "previous definition here" / "redefined here". +Two error codes, by whether the collision is between *impls* or between *definitions*. A duplicate key or overlapping generic produces **[`E0119`](../error_codes/e0119.md) conflicting implementations**; a duplicate generated *name* produces **[`E0428`](../error_codes/e0428.md) "the name … is defined multiple times"**. Both point precisely: `E0119` carries two carets — "first implementation here" on the earlier entry and "conflicting implementation for ``" on the later one, aimed at the offending keys rather than the whole block — and `E0428` carries "previous definition here" / "redefined here". How many `E0119`s one duplicate produces depends on how many impls the entry generates, and knowing this lets a reader treat the pair as one logical conflict rather than two mistakes. A **context-wiring** entry — a `delegate_components!` mapping, or a `namespace`/`open`/`for` header — generates *both* a `DelegateComponent` table impl and an [`IsProviderFor`](../../reference/traits/is_provider_for.md) forwarding impl (so dependency errors stay diagnosable through checks), so a duplicate yields a **pair** of `E0119`s at the same caret, one for each trait. An entry that generates only a single lookup impl yields a **single** `E0119`: a duplicate [`#[default_impl]`](../../reference/traits/default_namespace.md) conflicts on its one `DefaultImpls…` impl, and a duplicate [`cgp_namespace!`](../../reference/macros/cgp_namespace.md) body entry (a `:` mapping or a `=>` redirect) conflicts on its one namespace-trait impl. A duplicated provider *name* is the compound case: `E0428` on the struct, plus the `E0119` pair on the provider's own provider-trait and `IsProviderFor` impls. -Two wrinkles round out the shape, and the second is where Rust's coherence reasoning shows through. When the key is a `@`-path, the conflicting trait's name expands into a long `PathCons>` type that dominates the message — the caret still lands on the path leaf, so read the caret, not the type. And a collision in which one impl is a *blanket* — an `open` header, a `namespace` join, or a bare-key `for` loop, each lowering to `impl for Ctx` over every key — often carries an extra "downstream crates may implement …" note. That note is `rustc` making its coherence rule explicit, not a second, separate problem. Coherence forbids two impls that *could* both apply to some type, and it reasons about types a downstream crate might add, not only the types in scope; because the blanket's applicability to a given key hinges on a trait bound a downstream crate could later satisfy, the compiler cannot prove the two impls will never overlap, so it rejects them and cites the hypothetical future impl as the reason. This future-compatibility (negative-reasoning) rule is what [RFC 2451](https://rust-lang.github.io/rfcs/2451-re-rebalancing-coherence.html) formalizes. A pure blanket-versus-blanket overlap — two `for`/`namespace` headers each keyed over every key — needs no such reasoning and prints the bare conflict with fully-generic `DelegateComponent<_>` / `IsProviderFor<_, _, _>` carets and no downstream note. +Two wrinkles round out the shape, and the second is where Rust's coherence reasoning shows through. When the key is a `@`-path, the conflicting trait's name expands into a long `PathCons>` type that dominates the message — the caret still lands on the path leaf, so read the caret, not the type. And a collision in which one impl is a *blanket* — such as an `open` header, which lowers to `impl for Ctx` over every key, colliding with a specific mapping — often carries an extra "downstream crates may implement …" note. That note is `rustc` making its coherence rule explicit, not a second, separate problem. Coherence forbids two impls that *could* both apply to some type, and it reasons about types a downstream crate might add, not only the types in scope; because the blanket's applicability to a given key hinges on a trait bound a downstream crate could later satisfy, the compiler cannot prove the two impls will never overlap, so it rejects them and cites the hypothetical future impl as the reason. This future-compatibility (negative-reasoning) rule is what [RFC 2451](https://rust-lang.github.io/rfcs/2451-re-rebalancing-coherence.html) formalizes. The two namespace classes split off above turn on the same reasoning: the [override conflict](namespace-override-conflict.md) carries this note where a specific entry meets a namespace's blanket forwarding, while the [forwarding conflict](namespace-forwarding-conflict.md) is a pure blanket-versus-blanket overlap that prints fully-generic `DelegateComponent<_>` / `IsProviderFor<_, _, _>` carets with no downstream note. ## Where the root cause is @@ -36,7 +38,7 @@ The root cause is **present and precise**: the two carets name the two conflicti ## Resolving it -Remove one of the two entries. The one case with a subtler fix is the context that joins a namespace and also wires a path the namespace itself registers: there, keep the override by targeting a path the namespace *routes to* but does not itself terminate, so the context supplies the leaf without overlapping the namespace's own impl (see [`#[cgp_namespace]`](../../reference/macros/cgp_namespace.md) and its Known issues). For a duplicate check-trait name from two tables over one context, add a `#[check_trait(Name)]` to one. +Remove one of the two entries. For a duplicate check-trait name from two tables over one context, add a `#[check_trait(Name)]` to one. The namespace collisions split off above have their own remedies — inheriting rather than joining several namespaces, or overriding only a path the namespace leaves unbound — covered in [overlapping namespace forwarding](namespace-forwarding-conflict.md) and [namespace override conflict](namespace-override-conflict.md). ## Notes for tooling @@ -50,10 +52,11 @@ The `E0119` conflicts: - [acceptable/delegate_components/overlapping_generic.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/overlapping_generic.rs) — a generic ` Wrapper` table overlapping a specific `Wrapper` table. - [acceptable/delegate_components/duplicate_open_key.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_open_key.rs) — an `open` header colliding with an explicit mapping, with the "downstream crates may implement" note. - [acceptable/delegate_components/duplicate_path_key.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_path_key.rs) — a duplicated `@`-path key, whose conflicting trait name expands into the long `PathCons>` type. -- [acceptable/cgp_namespace/duplicate_path_key.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/duplicate_path_key.rs) and [override_registered_path.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/override_registered_path.rs) — a duplicate `cgp_namespace!` entry (a *single* `E0119` on the namespace-trait impl), and a context overriding a path its joined namespace already registers (the specific-versus-blanket case, with the "downstream crates may implement" note). -- [acceptable/cgp_namespace/for_loop_bare_key.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/for_loop_bare_key.rs) — a bare-key `for … in` loop alongside a `namespace` join, the pure blanket-versus-blanket case; its `.stderr` pins the fully-generic `DelegateComponent<_>` / `IsProviderFor<_, _, _>` carets with no downstream note. +- [acceptable/cgp_namespace/duplicate_path_key.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/duplicate_path_key.rs) — a duplicate `cgp_namespace!` `@`-path entry, a *single* `E0119` on the namespace-trait impl. - [acceptable/cgp_impl/duplicate_default_impl.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_impl/duplicate_default_impl.rs) — two `#[default_impl]` registering the same key, a *single* `E0119` on the one `DefaultImpls1` impl (not a pair). +The two namespace collisions that are *not* duplicate declarations live with their own classes: `override_registered_path.rs` and `inherited_override_conflict.rs` under [namespace override conflict](namespace-override-conflict.md), and `for_loop_bare_key.rs` and `two_namespaces_joined.rs` under [overlapping namespace forwarding](namespace-forwarding-conflict.md). + The `E0428` name clashes: - [acceptable/cgp_component/duplicate_component_name.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/duplicate_component_name.rs) — a derived `…Component` marker clashing with a hand-declared type. @@ -62,5 +65,6 @@ The `E0428` name clashes: ## Related - [Orphan-rule violation](orphan-rule.md), [Wiring cycle](wiring-cycle.md), [Unconstrained generic](unconstrained-generic.md) — the sibling structural classes. +- [Overlapping namespace forwarding](namespace-forwarding-conflict.md) and [Namespace override conflict](namespace-override-conflict.md) — the two namespace `E0119` collisions split off from this class, by usage rather than by error code. - [`delegate_components!`](../../reference/macros/delegate_components.md), [`cgp_namespace!`](../../reference/macros/cgp_namespace.md), and [`DelegateComponent`](../../reference/traits/delegate_component.md). - [Debugging CGP compile errors](../../guides/debugging.md) — the `E0119`/`E0428` entries in the decoder. diff --git a/docs/errors/wiring/namespace-forwarding-conflict.md b/docs/errors/wiring/namespace-forwarding-conflict.md new file mode 100644 index 00000000..a73559f6 --- /dev/null +++ b/docs/errors/wiring/namespace-forwarding-conflict.md @@ -0,0 +1,65 @@ +# Overlapping namespace forwarding + +A context's wiring emits two *blanket* forwarding impls that each cover every component key — by joining two namespaces, or by joining one namespace alongside a bare-key `for` loop — and the compiler rejects the overlap with `E0119`. + +## What triggers it + +This class arises when a single [`delegate_components!`](../../reference/macros/delegate_components.md) block produces more than one blanket [`DelegateComponent`](../../reference/traits/delegate_component.md) impl over the *whole* key space. A `namespace N;` header forwards every unresolved key through the namespace `N`, so it lowers to a blanket `impl DelegateComponent for Ctx where Key: N` (paired with the matching [`IsProviderFor`](../../reference/traits/is_provider_for.md) forwarding). Two such headers — or one header plus a bare-key `for` loop, which lowers to the same all-keys shape — produce two blanket impls that overlap, because a key could satisfy both `where` clauses at once. + +The first form is **joining two namespaces on one context**: + +```rust +delegate_components! { + App { + namespace NamespaceA; + namespace NamespaceB; // second blanket DelegateComponent impl — overlaps the first + } +} +``` + +The second form is **a bare-key `for` loop beside a namespace join**, where the loop wires `Key` directly rather than embedding it in a path: + +```rust +delegate_components! { + App { + namespace DefaultNamespace; + + for in GreeterTable { + Key: Value, // blanket DelegateComponent impl — overlaps the namespace's + } + } +} +``` + +Both reduce to the same mistake: two impls that each claim every key. A context can forward through at most one namespace, and a `for` loop's key must sit inside a path (`@app.SomeComponent.Key: Value`) so it keys a *concrete* path rather than every key. CGP lowers each blanket impl faithfully and cannot see from one block that two of them span the same keys, so it defers the overlap to the compiler. + +## The diagnostic + +This is a **structural** class reported as a pair of **[`E0119`](../error_codes/e0119.md) conflicting implementations**, one for each trait a namespace join forwards — the `DelegateComponent<_>` table impl and the `IsProviderFor<_, _, _>` forwarding impl that keeps dependency errors diagnosable through [checks](../../reference/macros/check_components.md). Both are printed with fully-generic `DelegateComponent<_>` / `IsProviderFor<_, _, _>` types, and each carries the two-caret shape: "first implementation here" on the first `namespace` header, and "conflicting implementation for `App`" on the second header (or on the loop's `Key: Value` line). The carets land on the entries the user wrote, so the diagnostic points straight at the two lines to reconcile. + +The signature of this class is that **both** conflicting types are *fully generic* — `DelegateComponent<_>` and `IsProviderFor<_, _, _>`, with no concrete key — and the conflict carries **no** "downstream crates may implement …" note. That is what separates it from the [namespace override conflict](namespace-override-conflict.md), where one side of the overlap names a *specific* key (a path or a component marker). The generic-versus-generic shape follows from how Rust's coherence checker reasons here. Coherence forbids two impls that could both apply to one type, and both impls have the form `impl DelegateComponent for App where Key: SomeNamespace`: the self type is the same local `App`, and the key is a free parameter bounded only by a namespace trait. Nothing prevents a single key type from implementing both namespace traits, so the compiler proves the overlap *directly*, from the impls in front of it — it needs no hypothetical future impl to construct the conflict, which is why no downstream note appears. The [conflicting wiring](conflicting-wiring.md) class carries the full account of `E0119` and the RFC 2451 coherence reasoning this builds on; the point here is only that a note-free `E0119` on generic `DelegateComponent<_>`/`IsProviderFor<_, _, _>` is this class's signature. + +## Where the root cause is + +The root cause is **present and precise**: the two carets name the two overlapping entries directly, and the fully-generic trait types confirm the overlap spans every key. This is a structural class with no note chain to walk and nothing suppressed, so a reader trusts the carets and reconciles the two lines. The only reading the diagnostic does not supply is the CGP-specific fix — that a context forwards through one namespace, not several — because the message describes the collision in impl terms rather than in terms of the wiring intent. + +## Resolving it + +Emit only one blanket forwarding impl. When two namespaces are joined, keep a single `namespace N;` and fold the other namespace's entries into `N` through *inheritance* — define one namespace that inherits the rest (`cgp_namespace! { new Combined: NamespaceA { … } }`, itself inheriting further) and join that one, per the [namespaces guide](../../guides/namespaces-and-prefixes.md). When a bare-key `for` loop collides with a namespace join, embed the loop's key in a path (`@app.SomeComponent.Key: Value`) so it keys a concrete dispatch path instead of every key — which is also why a `for` loop is the natural tool for a generic-parameter component, whose dispatch path *is* the loop key. Either fix leaves the context with one forwarding impl and the overlap gone. + +## Notes for tooling + +For a `cargo-cgp`-style post-processor this class needs little beyond faithful relaying, since the two carets are already the answer. The value a tool adds is **recognizing the `E0119` pair (`DelegateComponent<_>` + `IsProviderFor<_, _, _>`) as one logical conflict, not two**, so it reports a single "two blanket forwardings overlap" rather than doubling the count, and **restating the fix in wiring terms**: "a context forwards through one namespace — inherit the others into it, or move a bare `for` key into a path." The absent downstream note is worth keying on to distinguish this from the override conflict and route the user to the right remedy. + +## Backing fixtures + +- [acceptable/cgp_namespace/two_namespaces_joined.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/two_namespaces_joined.rs) — two `namespace` joins on one context; its `.stderr` pins the `E0119` pair with carets on the two `namespace` lines and no downstream note. +- [acceptable/cgp_namespace/for_loop_bare_key.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/for_loop_bare_key.rs) — a bare-key `for` loop beside a namespace join; its `.stderr` pins the same `E0119` pair with the conflicting caret on the loop's `Key: Value` line. + +## Related + +- [Namespace override conflict](namespace-override-conflict.md) — the sibling namespace `E0119`, a *specific*-versus-blanket overlap where one side names a concrete key (a path or a component marker); contrast it by the generic-versus-generic types this class prints. +- [Conflicting wiring](conflicting-wiring.md) — the general `E0119`/`E0428` class for a key or name declared twice, and the full account of Rust's coherence reasoning ([RFC 2451](https://rust-lang.github.io/rfcs/2451-re-rebalancing-coherence.html)) that this class specializes. +- [Orphan-rule violation](orphan-rule.md), [Wiring cycle](wiring-cycle.md), [Namespace inheritance cycle](namespace-inheritance-cycle.md), [Unconstrained generic](unconstrained-generic.md) — the sibling structural classes. +- [`#[cgp_namespace]`](../../reference/macros/cgp_namespace.md), [`DefaultNamespace`](../../reference/traits/default_namespace.md), and the [namespaces guide](../../guides/namespaces-and-prefixes.md) — the `namespace`/`for` statements whose blanket impls overlap. +- [Debugging CGP compile errors](../../guides/debugging.md) — the `E0119` entry in the decoder. diff --git a/docs/errors/wiring/namespace-inheritance-cycle.md b/docs/errors/wiring/namespace-inheritance-cycle.md index 0c38d90d..29fe1d86 100644 --- a/docs/errors/wiring/namespace-inheritance-cycle.md +++ b/docs/errors/wiring/namespace-inheritance-cycle.md @@ -24,7 +24,7 @@ The compiler reports **`E0275` "overflow evaluating the requirement"**, and the This eager, definition-site failure is the sharp contrast with the sibling [wiring cycle](wiring-cycle.md). A [`UseContext`](../../reference/providers/use_context.md) delegation cycle is *lazy*: the wiring is accepted, and the overflow appears only when the wiring is forced through a check (and hides as an [`E0599`](../hidden/unsatisfied-dependency.md) when reached by a plain call). A namespace inheritance cycle is *eager*: the cycle lives in the `where` clause of a generated blanket impl, and the compiler evaluates that clause when it checks the impl, so the overflow fires at the definition before anything uses the namespace. A context that *does* join the cycle and is then checked simply adds more `E0275` blocks — an `App: DelegateComponent` overflow at the check — on top of the definition-site ones; it does not change the cause. -The overflow itself is ordinary `rustc` behavior. The trait solver walks a requirement's supporting bounds to a bounded depth — the default `recursion_limit` is 128 — and reports `E0275` when it exceeds that depth without terminating. A genuine cycle never terminates, so the limit is only ever reached, never cleared; the `help:` suggestion to raise it is generic advice the compiler prints for every overflow and does not apply here. +The overflow itself is ordinary `rustc` behavior. The trait solver walks a requirement's supporting bounds to a bounded depth — the [default `recursion_limit` is 128](https://doc.rust-lang.org/reference/attributes/limits.html) — and reports [`E0275`](../error_codes/e0275.md) when it exceeds that depth without terminating. A genuine cycle never terminates, so the limit is only ever reached, never cleared; the `help:` suggestion to raise it is generic advice the compiler prints for every overflow and does not apply here. ## Where the root cause is diff --git a/docs/errors/wiring/namespace-override-conflict.md b/docs/errors/wiring/namespace-override-conflict.md new file mode 100644 index 00000000..40c8e034 --- /dev/null +++ b/docs/errors/wiring/namespace-override-conflict.md @@ -0,0 +1,78 @@ +# Namespace override conflict + +Wiring tries to *override* a key or path that a namespace already claims — a context re-wiring a path its joined namespace registers, or a child namespace redefining an entry it inherits — and the specific entry collides with the namespace's blanket impl, so the compiler rejects the overlap with `E0119`. + +## What triggers it + +This class is the failure of a natural-seeming intent: "the namespace sets this, but I want something different here." A namespace supplies entries as *defaults*, and the inheritance-with-override pattern lets a context shadow one — but only for a key the namespace *routes* to without *terminating*. A key the namespace itself binds (through a `:` body entry, a [`#[default_impl]`](../../reference/traits/default_namespace.md), or an inherited entry) is already covered by the namespace's blanket impl, so a second, more specific impl for that same key overlaps it. The overlap takes two shapes. + +The **context-level** shape is a context that joins a namespace and then directly wires a path the namespace registers: + +```rust +#[cgp_impl(new GreetHello)] +#[default_impl(@app.GreeterComponent in AppNamespace)] // AppNamespace binds this path +impl Greeter { /* … */ } + +delegate_components! { + App { + namespace AppNamespace; + + @app.GreeterComponent: GreetBye, // tries to override a path the namespace binds + } +} +``` + +The **namespace-level** shape is a child namespace that inherits a parent and redefines one of the parent's keys: + +```rust +cgp_namespace! { + new BaseNs { + GreeterComponent: GreetHello, + } +} + +cgp_namespace! { + new ChildNs: BaseNs { + GreeterComponent: GreetBye, // tries to override an inherited entry + } +} +``` + +Both reduce to "a specific entry for a key the namespace's blanket impl already covers." In the context case the blanket impl is the `namespace N;` forwarding `impl DelegateComponent for App where Key: N`; in the namespace case it is the inheritance forwarding `impl ChildNs
for Key where Key: BaseNs<…>`. Either way, CGP lowers both the blanket impl and the specific entry faithfully and cannot see from one macro invocation that they claim the same key, so it defers the overlap to the compiler. + +## The diagnostic + +This is a **structural** class reported as **[`E0119`](../error_codes/e0119.md) conflicting implementations**, with the two-caret shape — "first implementation here" on the namespace's blanket source, and "conflicting implementation" on the specific entry — landing on the entries the user wrote. What the specific side of the overlap *names* is the signature that tells this class from the generic-versus-generic [overlapping namespace forwarding](namespace-forwarding-conflict.md): here one impl is keyed on a *concrete* key. The two shapes differ in the details, and recognizing each on sight tells a reader which override they attempted. + +The **context-level** shape produces a **pair** of `E0119`s, because a context is wired through both the `DelegateComponent` table and the `IsProviderFor` forwarding: one conflict on `DelegateComponent>` for `App` and one on `IsProviderFor, _, _>` for `App`, the conflicting key expanded into the long `PathCons>` path type (read the caret, not the type). This shape additionally carries a `note: downstream crates may implement trait IsProviderFor, _, _> for type GreetHello` (and for the overriding provider), which is `rustc` making its coherence reasoning explicit: whether the namespace's blanket forwarding and the direct path entry overlap hinges on whether the redirect's delegate provider implements `IsProviderFor` for that path, a bound a downstream crate could add, so the compiler cannot rule the overlap out and cites the hypothetical impl. That future-compatibility (negative-reasoning) rule is the same one [RFC 2451](https://rust-lang.github.io/rfcs/2451-re-rebalancing-coherence.html) formalizes and that the [conflicting wiring](conflicting-wiring.md) class explains in full. + +The **namespace-level** shape produces a **single** `E0119` — `conflicting implementations of trait ChildNs<_> for type GreeterComponent` — and **no** downstream note. There is only one conflict because a `cgp_namespace!` block emits only its own lookup-trait impls (`ChildNs
for Key`), never the context-side `DelegateComponent`/`IsProviderFor` pair, so nothing doubles it. There is no downstream note because the overlap is provable locally: the inheritance blanket impl covers `GreeterComponent` exactly when `GreeterComponent: BaseNs<…>`, an impl the same crate already emitted for the parent, so the compiler constructs the conflict without reasoning about any future impl. The self type of the conflict is the *component marker* (`GreeterComponent`), not the context, which is the tell that the collision is inside the namespace's own table rather than on a context. + +## Where the root cause is + +The root cause is **present and precise** in both shapes: the two carets name the namespace source and the overriding entry, and the error code names the collision. This is a structural class with no note chain to walk and nothing suppressed. The one reading skill it asks for is to look past the expanded `PathCons>` key type on the context-level shape and trust the caret, and to read the `downstream crates may implement …` note as coherence's *reason*, not a second, separate problem. What neither shape states is the CGP-specific remedy, since the message frames a namespace override decision as a bare coherence conflict. + +## Resolving it + +The fix depends on which override was attempted, and both follow one rule: **a namespace entry, once bound, cannot be overridden — only a path the namespace leaves unbound is overridable.** + +For the **context-level** shape, override by targeting a path the namespace *routes to* but does not itself *terminate*: register the component's [`#[prefix]`](../../reference/macros/cgp_namespace.md) redirect in a base namespace the context inherits, and leave the leaf path unclaimed so the context can supply it directly. If the namespace genuinely binds the path (a `:` body entry or a `#[default_impl]`), it is not overridable on the context — change it in the namespace instead, or move the binding out of the namespace so the leaf stays open. The [namespaces guide](../../guides/namespaces-and-prefixes.md) works this through: `MockApp` overrides `@app.finance.MoneyTransferrerComponent` precisely because `MockNamespace` deliberately does not register that path. + +For the **namespace-level** shape, do not bind the key in the base and redefine it in the child. To vary a key per configuration, leave it *unbound* in the shared base namespace and bind it in each inheriting namespace, so each child supplies the key without overriding an inherited one — the separation the guide recommends between a base namespace that describes an application's *structure* and inheriting namespaces that each describe one *configuration*. + +## Notes for tooling + +For a `cargo-cgp`-style post-processor, the value is translating the coherence conflict into the override intent it actually represents. On the **context-level** shape the tool should **collapse the `PathCons>` key back to its readable `@app.…` path**, recognize the `DelegateComponent` + `IsProviderFor` pair as one logical conflict, and report "the namespace already binds `@app.GreeterComponent`; a context cannot override a path the namespace terminates — route through it and terminate on the context instead." On the **namespace-level** shape, recognizing the self type is a *component marker* and the trait is the child namespace lets the tool report "`ChildNs` cannot override `GreeterComponent`, which it inherits from `BaseNs`; leave it unbound in `BaseNs` to vary it per child." The `downstream crates may implement …` note is coherence scaffolding to suppress once its meaning is folded into the headline. + +## Backing fixtures + +- [acceptable/cgp_namespace/override_registered_path.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/override_registered_path.rs) — the context-level shape: a context joining `AppNamespace` overrides a path bound with `#[default_impl]`; its `.stderr` pins the `E0119` pair on `DelegateComponent>` and `IsProviderFor, _, _>` for `App`, with the expanded path type and the `downstream crates may implement` note. +- [acceptable/cgp_namespace/inherited_override_conflict.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/inherited_override_conflict.rs) — the namespace-level shape: a child namespace redefining an inherited key; its `.stderr` pins the single `E0119` on `ChildNs<_>` for `GreeterComponent`, with "first implementation here" on the inherited parent and no downstream note. + +## Related + +- [Overlapping namespace forwarding](namespace-forwarding-conflict.md) — the sibling namespace `E0119`, a *blanket*-versus-blanket overlap over every key (fully-generic `DelegateComponent<_>`); contrast it by the concrete key one side names here. +- [Conflicting wiring](conflicting-wiring.md) — the general `E0119`/`E0428` class for a key or name declared twice, and the full account of the RFC 2451 coherence reasoning behind the `downstream crates may implement` note. +- [Orphan-rule violation](orphan-rule.md), [Wiring cycle](wiring-cycle.md), [Namespace inheritance cycle](namespace-inheritance-cycle.md), [Unconstrained generic](unconstrained-generic.md) — the sibling structural classes. +- [`#[cgp_namespace]`](../../reference/macros/cgp_namespace.md) (and its Known issues), [`DefaultNamespace`](../../reference/traits/default_namespace.md), and the [namespaces guide](../../guides/namespaces-and-prefixes.md) — the inherit-and-override mechanics and the rule that a bound entry is not overridable. +- [Debugging CGP compile errors](../../guides/debugging.md) — the `E0119` entry in the decoder. diff --git a/docs/errors/wiring/orphan-rule.md b/docs/errors/wiring/orphan-rule.md index cc425bdc..59aae4f3 100644 --- a/docs/errors/wiring/orphan-rule.md +++ b/docs/errors/wiring/orphan-rule.md @@ -1,10 +1,12 @@ # Orphan-rule violation -A generated impl targets a foreign trait and a type built entirely from foreign pieces, which Rust's orphan rule forbids, so the expansion fails with `E0210` (or `E0117`) — most often when a prefixed `#[default_impl]` is registered from a crate that owns neither the namespace nor the path. +A generated impl registers a component into a *foreign* namespace with no local type anywhere in the impl, which Rust's orphan rule forbids, so the expansion fails with `E0210` (or `E0117`) — the failure a crate hits when it tries to add namespace wiring for a namespace, component, or path it does not own. ## What triggers it -The mistake is registering a per-type default for a *prefixed* component into a namespace from a downstream crate that does not own it. When a component carries `#[prefix(@app in …)]`, its namespace key is a path, so a `#[default_impl(@path in Namespace)]` for it expands to an impl of the foreign namespace trait for a `Self` type built from the `cgp`-owned `PathCons`/`Symbol` spine and the upstream component marker — every element foreign to the registering crate. +The mistake is registering into a namespace whose *trait* the crate does not own, keyed on a type the crate also does not own — an impl of a foreign trait for a foreign type. A namespace registration lowers to `impl Namespace for Key`, and Rust accepts a foreign-trait impl only when a local type covers its parameters. When both the namespace and the key are foreign, nothing is local, so the orphan rule rejects it. Three constructs register into a namespace, and each hits the rule the same way when nothing local is in reach. + +The first is a **prefixed `#[default_impl]`**, where the component carries `#[prefix(@app in …)]` so its namespace key is a path built from the `cgp`-owned `PathCons`/`Symbol` spine and the upstream component marker — every element foreign to a downstream crate: ```rust // In a downstream crate, for an upstream prefixed component `Announcer`: @@ -18,32 +20,41 @@ where } ``` -Rust accepts a foreign-trait impl only when at least one type in it is local, and here none is, so the orphan rule rejects it. This is a whole-program coherence fact CGP cannot see from the macro invocation, so it lowers the impl faithfully and defers to the compiler. +The second is an **unprefixed `#[default_impl]` keyed on a foreign component marker**, which shows the restriction is not about prefixes: `#[default_impl(GreeterComponent in AppNamespace)]` for a foreign, unprefixed `GreeterComponent` expands to `impl AppNamespace for GreeterComponent` — a bare foreign marker as the key, still foreign trait for foreign type. + +The third is a **`cgp_namespace!` block without `new`** that re-opens a foreign namespace to add an entry. Omitting `new` tells the macro the namespace trait exists elsewhere and to emit only the entry impls, so `cgp_namespace! { AppNamespace { GreeterComponent => @foo } }` for a foreign `AppNamespace` emits `impl
AppNamespace
for GreeterComponent { type Delegate = … }` — again foreign trait for foreign key. + +Every form reduces to the same fact: a foreign namespace trait implemented for a foreign key, with no local type to satisfy the orphan rule. This is a whole-program coherence fact CGP cannot see from the macro invocation, so it lowers the impl faithfully and defers to the compiler. The orphan-*safe* counterpart is owning *either* end — a crate may register a *local* component's marker into a foreign namespace (the key is local), or add entries to a namespace whose trait it owns; only when both are foreign does the impl become an orphan. ## The diagnostic -The compiler reports **`E0210`** — "type parameter `__Components__` must be used as the type parameter for some local type" — with the caret on the `#[cgp_impl(new …)]` attribute that generated the impl, and two explanatory notes: "implementing a foreign trait is only possible if at least one of the types for which it is implemented is local," and "only traits defined in the current crate can be implemented for a type parameter." A final note attributes the error to the `cgp_impl` attribute macro. Depending on the exact shape of the generated impl, the sibling orphan error **`E0117`** ("only traits defined in the current crate can be implemented for arbitrary types") can appear instead; both are the orphan rule rejecting the same foreign-trait-for-foreign-type impl. +The compiler reports **`E0210`** — "type parameter `…` must be used as the type parameter for some local type" — naming the impl's uncovered table parameter, with two explanatory notes: "implementing a foreign trait is only possible if at least one of the types for which it is implemented is local," and "only traits defined in the current crate can be implemented for a type parameter." Which parameter is named, and where the caret lands, follows the construct that generated the impl: a `#[default_impl]` names **`__Components__`**, lands the caret on the `#[cgp_impl]` attribute, and attributes the error to the `cgp_impl` macro; a `cgp_namespace!` re-open names **`__Table__`** (the namespace table parameter), lands the caret on the whole `cgp_namespace!` block, and attributes the error to the `cgp_namespace` macro. The parameter differs only because the two constructs name their table parameter differently; the violation is identical. Depending on the exact shape of the generated impl, the sibling orphan error **`E0117`** ("only traits defined in the current crate can be implemented for arbitrary types") can appear instead; both are the orphan rule rejecting the same foreign-trait-for-foreign-type impl. -The rule the compiler is enforcing here is coherence, and understanding it explains why the error frames the fix as a matter of *ownership*. Coherence requires that for any trait and type there is at most one impl, and the orphan rule preserves that across crates by forbidding an impl of a foreign trait unless a local type is *covered* by it — appears before any uncovered type parameter. Were an orphan impl allowed, two unrelated crates could each implement the foreign trait for the foreign type in incompatible ways, and adding a dependency could silently break a build; the orphan rule rejects the impl up front precisely so that can never happen. `E0210` is the specific form of this rule for an impl whose only "type" in the covering position is a bare type parameter (`__Components__`), which no local type covers. The current rule and its wording come from [RFC 2451 (re-rebalancing coherence)](https://rust-lang.github.io/rfcs/2451-re-rebalancing-coherence.html); the [`E0210` error-index entry](https://doc.rust-lang.org/error_codes/E0210.html) is its reference description. +The rule the compiler is enforcing here is coherence, and understanding it explains why the error frames the fix as a matter of *ownership*. Coherence requires that for any trait and type there is at most one impl, and the orphan rule preserves that across crates by forbidding an impl of a foreign trait unless a local type is *covered* by it — appears before any uncovered type parameter. Were an orphan impl allowed, two unrelated crates could each implement the foreign trait for the foreign type in incompatible ways, and adding a dependency could silently break a build; the orphan rule rejects the impl up front precisely so that can never happen. `E0210` is the specific form of this rule for an impl whose only "type" in the covering position is a bare type parameter (`__Components__`), which no local type covers. The current rule and its wording come from [RFC 2451 (re-rebalancing coherence)](https://rust-lang.github.io/rfcs/2451-re-rebalancing-coherence.html); the [`E0210`](../error_codes/e0210.md) reference summarizes it, alongside its sibling [`E0117`](../error_codes/e0117.md). ## Where the root cause is -The mechanical cause is **present** — the error names the foreign trait and the offending type parameter — but the *actionable* cause is CGP-specific and the diagnostic does not state it. What the compiler cannot say is that the impl is foreign because the component is *prefixed* and the namespace lives *upstream*, nor that the remedy is a matter of crate ownership. So while this is not a hidden class, reading the raw `E0210` leaves a user who does not know CGP's namespace mechanics without the fix; the gap is knowledge, not information the compiler withheld. +The mechanical cause is **present** — the error names the foreign trait and the offending type parameter — but the *actionable* cause is CGP-specific and the diagnostic does not state it. What the compiler cannot say is that the impl is foreign because a namespace registration landed on a namespace and key the crate does not own, nor that the remedy is a matter of crate ownership. So while this is not a hidden class, reading the raw `E0210` leaves a user who does not know CGP's namespace mechanics without the fix; the gap is knowledge, not information the compiler withheld. ## Resolving it -Register the default from the crate that owns the namespace, or key the default on a **local** component whose marker is a type the registering crate owns (`#[default_impl(LocalComponent in Namespace)]`), which satisfies the orphan rule because a local type is present in the impl. When the wiring genuinely must live downstream of the namespace, use a namespace *body* entry rather than a per-component `#[default_impl]`, per the [namespaces guide](../../guides/namespaces-and-prefixes.md). See [`DefaultNamespace`](../../reference/traits/default_namespace.md) for the orphan-safe registration patterns. +Own one end of the impl. Register the default from the crate that owns the namespace, or key it on a **local** component whose marker the registering crate owns (`#[default_impl(LocalComponent in Namespace)]`), which satisfies the orphan rule because a local type is present. When the wiring genuinely must live downstream of the namespace, use a namespace *body* entry rather than a per-component `#[default_impl]`, per the [namespaces guide](../../guides/namespaces-and-prefixes.md). To *extend* a foreign namespace, do not re-open it with a bare `cgp_namespace!` block — define a **new local namespace that inherits it** (`cgp_namespace! { new Local: ForeignNamespace { … } }`), which is orphan-safe because every emitted impl is for the local namespace trait. See [`DefaultNamespace`](../../reference/traits/default_namespace.md) for the orphan-safe registration patterns. ## Notes for tooling -A `cargo-cgp`-style post-processor should recognize the shape — an `E0210`/`E0117` whose `Self` type is a `PathCons>` spine and whose trait is a namespace trait — and translate the generic orphan message into the CGP remedy: "a prefixed `#[default_impl]` for a foreign namespace must live in the namespace's own crate; use a local component key or a namespace body entry to register it downstream." The raw diagnostic is accurate but frames a CGP wiring decision as a bare coherence rule, so the value a tool adds is the translation, not the extraction. +A `cargo-cgp`-style post-processor should recognize the shape — an `E0210`/`E0117` whose trait is a namespace trait (a `#[cgp_namespace]` trait, `DefaultNamespace`, or a `DefaultImpls…`) and whose `Self` type is a foreign component marker or a `PathCons>` spine — and translate the generic orphan message into the CGP remedy: "registering into a foreign namespace needs the crate to own the namespace or a local key; register from the namespace's own crate, key on a local component, use a namespace body entry, or inherit the namespace into a new local one." The raw diagnostic is accurate but frames a CGP wiring decision as a bare coherence rule, so the value a tool adds is the translation, not the extraction. ## Backing fixtures -- [acceptable/cgp_namespace/default_impl_foreign_prefix_path.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_prefix_path.rs) — a downstream crate registering a `#[default_impl]` for an upstream *prefixed* component into the upstream namespace; its `.stderr` pins the `E0210` and the "implementing a foreign trait" notes. The orphan-safe counterpart — a *local* component key registered into a foreign namespace — is exercised in the cross-crate test packages. +- [acceptable/cgp_namespace/default_impl_foreign_prefix_path.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_prefix_path.rs) — a downstream crate registering a `#[default_impl]` for an upstream *prefixed* component into the upstream namespace, so the key is a foreign `PathCons>` path; its `.stderr` pins the `E0210` on `__Components__` and the "implementing a foreign trait" notes. +- [acceptable/cgp_namespace/default_impl_foreign_component.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_component.rs) — the same `#[default_impl]` orphan keyed on a foreign *unprefixed* component marker rather than a path, showing the restriction is not specific to prefixes; its `.stderr` pins the `E0210` on `__Components__`. +- [acceptable/cgp_namespace/reopen_foreign_namespace.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/reopen_foreign_namespace.rs) — a `cgp_namespace!` block without `new` re-opening a foreign namespace; its `.stderr` pins the `E0210` on `__Table__` with the caret on the whole `cgp_namespace!` block, attributed to the `cgp_namespace` macro. + +The orphan-*safe* counterparts — a *local* component key registered into the foreign `AppNamespace`, and a local context wiring a foreign component — are exercised in the cross-crate test packages (`cgp-test-crate-b`). ## Related - [Conflicting wiring](conflicting-wiring.md), [Wiring cycle](wiring-cycle.md), [Unconstrained generic](unconstrained-generic.md) — the sibling structural classes. +- [Overlapping namespace forwarding](namespace-forwarding-conflict.md), [Namespace override conflict](namespace-override-conflict.md), [Namespace inheritance cycle](namespace-inheritance-cycle.md), [Unregistered namespace path](../checks/unregistered-namespace-path.md) — the other namespace-specific failure classes. - [`DefaultNamespace`](../../reference/traits/default_namespace.md) and [`#[cgp_namespace]`](../../reference/macros/cgp_namespace.md) — the namespace mechanics behind the restriction. - [Debugging CGP compile errors](../../guides/debugging.md) — the `E0210`/`E0117` entry in the decoder. diff --git a/docs/errors/wiring/unconstrained-generic.md b/docs/errors/wiring/unconstrained-generic.md index ecfb0518..881535f6 100644 --- a/docs/errors/wiring/unconstrained-generic.md +++ b/docs/errors/wiring/unconstrained-generic.md @@ -24,7 +24,7 @@ The same shape arises when a *generic* provider is registered as a per-type defa The compiler reports a single **`E0207`** — "the type parameter `T` is not constrained by the impl trait, self type, or predicates" — with the caret on the `` the user wrote in the entry. It is one clean, well-localized error, with no note chain and no cascade. -The rule behind it is that an impl parameter must be *determined* by the impl, and knowing why makes the fix obvious. Rust requires every generic parameter on an impl to appear in the implemented trait, in the self type, or in a `where`-clause predicate that pins it as an associated type — one of the three "constrained" positions the message names — because otherwise, given a trait reference, the compiler could not decide which `T` the impl is for. Here `T` reaches only the `Delegate` *value* (`GreetWith`), an associated-type position on the right of the `=`, which does not constrain the impl: `DelegateComponent` names no `T`, so any `T` would satisfy the header equally. Permitting that would also break coherence, since a downstream crate adding another `GreetWith` would make the choice ambiguous. This is the rule introduced by [RFC 447](https://github.com/rust-lang/rfcs/pull/447) ("prohibit unused type parameters in impls"); the [`E0207` error-index entry](https://doc.rust-lang.org/error_codes/E0207.html) is its reference description. It is the same negative-reasoning concern that underlies the [orphan rule](orphan-rule.md) and coherence conflicts — an impl must be resolvable no matter what impls other crates add later. +The rule behind it is that an impl parameter must be *determined* by the impl, and knowing why makes the fix obvious. Rust requires every generic parameter on an impl to appear in the implemented trait, in the self type, or in a `where`-clause predicate that pins it as an associated type — one of the three "constrained" positions the message names — because otherwise, given a trait reference, the compiler could not decide which `T` the impl is for. Here `T` reaches only the `Delegate` *value* (`GreetWith`), an associated-type position on the right of the `=`, which does not constrain the impl: `DelegateComponent` names no `T`, so any `T` would satisfy the header equally. Permitting that would also break coherence, since a downstream crate adding another `GreetWith` would make the choice ambiguous. This is the rule introduced by [RFC 447](https://github.com/rust-lang/rfcs/pull/447) ("prohibit unused type parameters in impls"); the [`E0207`](../error_codes/e0207.md) reference summarizes it. It is the same negative-reasoning concern that underlies the [orphan rule](orphan-rule.md) and coherence conflicts — an impl must be resolvable no matter what impls other crates add later. ## Where the root cause is diff --git a/docs/errors/wiring/wiring-cycle.md b/docs/errors/wiring/wiring-cycle.md index f145a6c2..e2f92968 100644 --- a/docs/errors/wiring/wiring-cycle.md +++ b/docs/errors/wiring/wiring-cycle.md @@ -32,7 +32,7 @@ How the cycle surfaces depends on how it is exercised, and the two shapes are ve Exercised instead by a plain method call on the context, the same cycle does *not* overflow: the method probe treats the unresolvable requirement as simply unsatisfied and reports the [hidden `E0599`](../hidden/unsatisfied-dependency.md) — "method exists but its trait bounds were not satisfied" — with no hint that a cycle is the reason. So a wiring cycle is `E0275` when checked and a hidden `E0599` when called, and only the checked form actually reveals the cause. -The `E0275` itself is ordinary `rustc` behavior. The trait solver follows a requirement's supporting bounds only to a bounded depth — the default `recursion_limit` is 128 — and raises `E0275` when it exceeds that depth without resolving, appending its standard `help: consider increasing the recursion limit`. That advice suits a merely *deep* obligation, but a true cycle never terminates at any depth, so the limit here is only ever hit, never cleared; the suggestion does not apply and should be read past. This is why the cycle must be exercised through a check to surface at all: the overflow is computed only when the solver is driven into the loop, and `#[cgp_component]`'s blanket impl otherwise lets the method probe abandon the requirement as unsatisfied without ever entering it. +The [`E0275`](../error_codes/e0275.md) itself is ordinary `rustc` behavior. The trait solver follows a requirement's supporting bounds only to a bounded depth — the [default `recursion_limit` is 128](https://doc.rust-lang.org/reference/attributes/limits.html) — and raises `E0275` when it exceeds that depth without resolving, appending its standard `help: consider increasing the recursion limit`. That advice suits a merely *deep* obligation, but a true cycle never terminates at any depth, so the limit here is only ever hit, never cleared; the suggestion does not apply and should be read past. This is why the cycle must be exercised through a check to surface at all: the overflow is computed only when the solver is driven into the loop, and `#[cgp_component]`'s blanket impl otherwise lets the method probe abandon the requirement as unsatisfied without ever entering it. ## Where the root cause is diff --git a/docs/guides/README.md b/docs/guides/README.md index ed76da00..728fe177 100644 --- a/docs/guides/README.md +++ b/docs/guides/README.md @@ -32,7 +32,7 @@ This section condenses every guide above into one quick reference. Read it for t | write a provider ([guide](writing-providers.md)) | `#[cgp_impl]` with the header `impl Trait` (omit `for Context`, keep `self`/`Self`) | raw `#[cgp_provider]`/`#[cgp_new_provider]` in inside-out shape | | require a capability or an inner provider ([guide](declaring-dependencies.md)) | `#[uses(Trait)]` / `#[use_provider(P: Trait)]`, comma-separated in one attribute | hand-written `Self:`/`P: Trait` `where` bounds | | read a value from the context's own field ([guide](reading-context-fields.md)) | an `#[implicit]` argument | a getter trait declared only to read it | -| name an abstract type ([guide](importing-abstract-types.md)) | `#[use_type(Trait.Type)]` + the bare alias (and `{Type = Concrete}` to pin one) | a `: Trait` supertrait + qualified `Self::Type` | +| name an abstract type ([guide](importing-abstract-types.md)) | `#[use_type(Trait.Type)]` + the bare alias (`Trait.Type in Context` for a foreign type, `{Type = Concrete}` to pin one) | a `: Trait` supertrait + qualified `Self::Type`, or `where Context: Trait` + `Context::Type` | | add a non-type capability supertrait ([guide](capability-supertraits.md)) | `#[extend(Trait)]` | native `: Supertrait` inheritance syntax | | dispatch a generic-parameter component per type ([guide](dispatching-per-type.md)) | the `open` statement or a [namespace](namespaces-and-prefixes.md) | `#[derive_delegate]` + a `UseDelegate` nested table | diff --git a/docs/guides/debugging.md b/docs/guides/debugging.md index 2ebc073d..a1568095 100644 --- a/docs/guides/debugging.md +++ b/docs/guides/debugging.md @@ -20,6 +20,38 @@ One error shape is especially worth recognizing: **"the trait `X` is not impleme When a type in the error is elided as `...`, the full form is written to a file the compiler names in a final note (`the full name for the type has been written to '….long-type-….txt'`). Reading that file is how you recover *which* path or *which* context the error is really about — the elided middle is frequently the one segment that reveals the mistake, as when a dependency's context parameter turns out to be a `PathCons<..>` path rather than the real context. +## Grep for the suspected line instead of reading the whole log + +When you already suspect what went wrong, grep the captured error output for the one line that would confirm it rather than reading the cascade top to bottom. A CGP failure routinely runs to hundreds of lines — one mistake repeated at every dependent provider, each block naming generated types you never wrote — so reading all of it to recover a fact you could have searched for wastes both effort and context. Capture the build output to a file first, targeting the smallest failing unit so the cascade does not multiply across crates, then work the file with `grep -n` so every match carries a line number you can open directly: + +```bash +cargo check -p 2>&1 | tee /tmp/cgp-error.txt # or a single --test / example +``` + +This is the cheap alternative to handing the whole log to a sub-agent: grep when a targeted search answers your question, and [delegate](../skills/cgp/references/error-extraction.md) only when it will not. It pays off most when your suspicion is right — a field you think you forgot, a key you think you wired twice, a cycle you think you introduced — because then a single grep either confirms the cause and points at the fix, or rules it out and redirects you, for the price of a few lines. + +Grep the error headlines first, because that one search classifies everything. `grep -nE '^error' /tmp/cgp-error.txt` prints one line per error block — the code and the trait it names — which is the whole of [reading the error's shape](#read-the-errors-shape-before-its-contents) recovered without scrolling. From the headlines alone you know whether you face a dependency error (`E0277`/`E0599`), a coherence conflict (`E0119`), a cycle (`E0275`), an orphan violation (`E0210`), an unconstrained generic (`E0207`), or a name-resolution failure (`E0576`/`E0425`), and the [decoder below](#a-decoder-for-the-errors-you-will-actually-see) says what each means. Pair it with the tail of the file (`tail -n 3`), which shows the error count and any `the full name for the type has been written to '…long-type-….txt'` note telling you a cause is hiding behind an elided `...`. + +Then grep one class-specific pattern to confirm the cause, because each class has a signature line the headline points you to. Once the headline names the class, the second grep either finds the cause or proves it absent: + +| To confirm | Grep for | What the hit tells you | +|---|---|---| +| a surfaced dependency leaf (missing field or capability) | `grep -n 'help:'` | the concrete unmet bound; a `HasField>>` spells the field name letter-by-letter on that one line, and the paired "but trait `HasField<…>` *is* implemented" hint names the field the context *does* have | +| an unwired component | `grep -n 'does not contain any DelegateComponent entry'` | CGP's own diagnostic message, naming the component with no wiring | +| a duplicate key or generated name (`E0119`/`E0428`) | `grep -n 'conflicting implementation\|defined multiple times'` | the two carets are the two entries to reconcile | +| whether an `E0119` is a specific override or a blanket forwarding overlap | `grep -n 'downstream crates may implement'` | present → a duplicate key or a namespace override on a concrete key; absent on a fully-generic `DelegateComponent<_>` → two namespaces (or a namespace and a bare-key `for` loop) joined | +| a wiring or namespace-inheritance cycle (`E0275`) | `grep -n 'overflow evaluating'` | the recursing requirement names the loop; ignore the `recursion limit` help, which never applies to a true cycle | +| an orphan-rule violation (`E0210`/`E0117`) | `grep -n 'must be used as the type parameter'` | a registration into a foreign namespace with no local type | +| an unconstrained entry generic (`E0207`) | `grep -n 'is not constrained'` | the caret sits on the `` that must reach the key | +| an ill-formed lowered type (`E0277` `Sized`) | `grep -n 'size for values of type\|Sized'` | a field- or argument-type shorthand combination with no lowering rule (e.g. `Option<&[T]>`) | +| a `#[use_type]` name or context typo (`E0576`/`E0425`) | `grep -n 'cannot find associated type\|cannot find type'` | the caret sits on the misspelled name | + +The leaf is not always in a `help:` note: for an [unsatisfied ordinary trait bound](../errors/checks/ordinary-trait-bound.md) (`f64: Eq`) and an [unregistered namespace path](../errors/checks/unregistered-namespace-path.md) (`PathCons<..>: DefaultNamespace`) the concrete cause *is* the `^error` headline itself, so the first grep already carries it. A `PathCons>` key, like a field `Symbol`, spells its path segments out on one line, so reading that single hit decodes the unbound path. + +One shape defeats grep entirely, and recognizing it saves a fruitless search. If the headline grep returns only an `E0599` "method … exists … but its trait bounds were not satisfied" (or an `E0277` on a bare consumer trait) with no `CanUseComponent`/`IsProviderFor` block beneath it, you are looking at a [hidden dependency](../errors/hidden/unsatisfied-dependency.md): the root cause is *absent from the output*, not merely far down it, so no grep can find it. Do not keep searching — [promote the error with a check](#move-the-error-to-where-the-mistake-is-with-checks) and re-run, which turns it into a surfaced `E0277` whose leaf the greps above do find. + +Spawn a sub-agent instead of grepping when the search will not converge. Grep is the right tool when you have a hypothesis to test or a single fact to pull from a class you have already identified; hand the whole log to a sub-agent, per the [error-extraction skill](../skills/cgp/references/error-extraction.md), when the headline grep shows several unrelated classes tangled together, when the cause hides behind an elided `...` that forces you to cross-reference the `long-type-….txt` file and decode a deep `PathCons` spine, or when you have no hypothesis and the cascade is large. The dividing line is whether you know what you are looking for: a targeted question is a grep, an open-ended read is a delegation. + ## Move the error to where the mistake is with checks Because a lazy failure surfaces far from its cause, the most reliable first move is to force the check *at the wiring site* with [`check_components!`](../reference/macros/check_components.md). A standalone check trait asserts `CanUseComponent` for each component you name, so a missing dependency errors on the checked component's line — walking through `IsProviderFor` to name the real cause — instead of at some distant call site. Adding a check for the component you suspect turns a confusing downstream error into one anchored at the wiring you control. The [check-traits concept](../concepts/check-traits.md) explains why this works. @@ -49,11 +81,12 @@ For a large `delegate_components!` or `cgp_namespace!` table that fails as a who A handful of specific compiler errors recur in CGP code, and each maps to a small set of causes. Use this as a lookup once you have the error code and the trait it names. - **`E0599` "method exists but its trait bounds were not satisfied"** — the *hidden* form of an unsatisfied dependency: a consumer-trait method called on a context whose wiring will not resolve. It names the consumer and provider traits but **hides** the dependency that actually failed, because the compiler drops the nested bound when a blanket impl sits among the candidate impls. Do not scan it for a root cause — there is none in it; promote it with a [`check_components!`](../reference/macros/check_components.md) to surface one. See [Unsatisfied dependency (hidden)](../errors/hidden/unsatisfied-dependency.md). -- **`E0277` on `IsProviderFor<…>`, `CanUseComponent<…>`, or a consumer trait** — a dependency is unmet or a component is unwired. Either the context has no `DelegateComponent` entry for the component (wire it), or the chosen provider needs something the context does not supply (read the note chain to the innermost failing bound and satisfy it, or add a [`check_components!`](../reference/macros/check_components.md) to name it). A bound whose *key* is a `PathCons<..>` path — `PathCons<..>: DefaultNamespace` or `Ctx: DelegateComponent>` unsatisfied — means a namespace redirect landed on a path no entry binds; bind a provider there (a `#[default_impl]`, a namespace body entry, or a direct `@path:` line), per [Unregistered namespace path](../errors/checks/unregistered-namespace-path.md). A bound like `SomeType: IsProviderFor<…>` where `SomeType` looks like a `RedirectLookup` means the lookup instead resolved to a delegate that is not actually a provider for that component. The surfaced (checked) form names the real bound in a `help:` note — see [Check-trait failure](../errors/checks/check-trait-failure.md); a repeated wall of these over a single cause is a [verbose cascade](../errors/checks/verbose-cascade.md). -- **`E0119` conflicting implementation of `DelegateComponent<…>` (or a namespace trait)** — the same key is wired twice. Two `delegate_components!` entries for one key, two `cgp_namespace!` entries for one path, or — the subtle case — a context that joins a namespace and *also* wires a path the namespace itself registers, so the `namespace` blanket impl and the direct entry overlap. Remove one; to keep a context override, target a path the namespace routes to but does not itself terminate (see [`#[cgp_namespace]`](../reference/macros/cgp_namespace.md) Known issues). See [Conflicting wiring](../errors/wiring/conflicting-wiring.md). -- **`E0210`/`E0117` orphan-rule violation on a generated impl** — a `#[default_impl]` or hand-written provider impl is being written for a foreign trait and foreign types. A per-component `#[default_impl]` for a *prefixed* component expands to `impl Namespace for PathCons<..>`, which a downstream crate cannot write because the whole impl is foreign; register it from the namespace's own crate, or use an unprefixed component key the crate owns (see [`DefaultNamespace`](../reference/traits/default_namespace.md)). See [Orphan-rule violation](../errors/wiring/orphan-rule.md). +- **`E0277` on `IsProviderFor<…>`, `CanUseComponent<…>`, or a consumer trait** — a dependency is unmet or a component is unwired. Either the context has no `DelegateComponent` entry for the component (wire it), or the chosen provider needs something the context does not supply (read the note chain to the innermost failing bound and satisfy it, or add a [`check_components!`](../reference/macros/check_components.md) to name it). A bound whose *key* is a `PathCons<..>` path — `PathCons<..>: DefaultNamespace` or `Ctx: DelegateComponent>` unsatisfied — means a namespace redirect landed on a path no entry binds; bind a provider there (a `#[default_impl]`, a namespace body entry, or a direct `@path:` line), per [Unregistered namespace path](../errors/checks/unregistered-namespace-path.md). A bound like `SomeType: IsProviderFor<…>` where `SomeType` looks like a `RedirectLookup` means the lookup instead resolved to a delegate that is not actually a provider for that component. The surfaced (checked) form names the real bound in a `help:` note — see [Check-trait failure](../errors/checks/check-trait-failure.md); a repeated wall of these over a single cause is a [verbose cascade](../errors/checks/verbose-cascade.md). When the unmet leaf is an *ordinary* trait (`f64: Eq`, `T: Clone`) on an abstract type or impl generic rather than a `HasField` — its own primary `E0277` with a `help:` list of conforming standard types — the fix is to satisfy or relax that trait, not to wire a component; see [Unsatisfied ordinary trait bound](../errors/checks/ordinary-trait-bound.md). +- **`E0119` conflicting implementation of `DelegateComponent<…>` (or a namespace trait)** — two impls overlap for one key. The plain case is the same key *declared twice*: two `delegate_components!` entries for one key, two `cgp_namespace!` entries for one path, an `open` header colliding with a mapping — remove one (see [Conflicting wiring](../errors/wiring/conflicting-wiring.md)). Two namespace-specific cases have their own remedies: a *fully-generic* `DelegateComponent<_>`/`IsProviderFor<_, _, _>` conflict with no downstream note means two blanket forwardings overlap (joining two namespaces, or a namespace join plus a bare-key `for` loop) — inherit rather than join, or move the loop key into a path (see [Overlapping namespace forwarding](../errors/wiring/namespace-forwarding-conflict.md)); a conflict on a *concrete* key means an entry tries to override a path or key the namespace already claims (a context re-wiring a registered path, or a child namespace redefining an inherited entry) — override only a path the namespace leaves unbound (see [Namespace override conflict](../errors/wiring/namespace-override-conflict.md)). +- **`E0210`/`E0117` orphan-rule violation on a generated impl** — a namespace registration is being written for a foreign trait and foreign types. A `#[default_impl]` for a *prefixed* component expands to `impl Namespace for PathCons<..>` (all foreign), an unprefixed `#[default_impl(ForeignComponent in ForeignNamespace)]` to `impl Namespace for ForeignComponent`, and a `cgp_namespace!` block without `new` re-opening a foreign namespace to `impl ForeignNamespace for Key` — none of which a downstream crate can write. Register from the crate that owns the namespace, key on a local component the crate owns, use a namespace body entry, or inherit the namespace into a new local one (see [`DefaultNamespace`](../reference/traits/default_namespace.md)). See [Orphan-rule violation](../errors/wiring/orphan-rule.md). - **`E0275`/overflow evaluating a requirement** — a resolution cycle. The classic case is delegating a component to [`UseContext`](../reference/providers/use_context.md) when the context's only implementation of that component *is* that delegation, so the lookup chases its own tail; break it by wiring the component to a concrete provider. This form surfaces only when *forced through a check* (a plain method call hides it as the `E0599` above) — see [Wiring cycle](../errors/wiring/wiring-cycle.md). A second form is a circular namespace parent chain (`cgp_namespace! { new A: B }` with `new B: A`, or `new A: A`), which overflows *eagerly at the `cgp_namespace!` definitions* — see [Namespace inheritance cycle](../errors/wiring/namespace-inheritance-cycle.md). For either, ignore the `help: increase the recursion limit` suggestion: a true cycle never terminates, so no limit clears it. - **`E0207` unconstrained type parameter on a generated impl** — a generic parameter appears only in an associated-type position and so does not constrain the impl. This shows up when a *generic* provider is registered as a per-type default, since the provider's parameter lands only in the `Delegate` type; register a concrete provider instead. See [Unconstrained generic](../errors/wiring/unconstrained-generic.md). +- **`E0277` "the size for values of type `…` cannot be known at compilation time" on a getter or implicit-argument macro** — not a wiring error but a *lowering* one: a field- or argument-type shorthand combination the macro has no rule for (most often `Option<&[T]>`) was lowered literally into a bound naming an unsized type. Change the field type to a shape a single shorthand supports, or write the accessor by hand. See [Ill-formed generated type](../errors/lowering/ill-formed-generated-type.md). - **A dependency demanded of a `PathCons<..>` path rather than the context** — a bound like `PathCons<..>: HasErrorType` means a `Self`-keyed impl-side bound leaked onto an impl whose `Self` is a path key. This is a macro-level defect rather than a wiring mistake; capture it as a minimal reproduction and check the emitting macro's lowering. ## Related documentation diff --git a/docs/guides/declaring-dependencies.md b/docs/guides/declaring-dependencies.md index a90e88ad..3c1fb76a 100644 --- a/docs/guides/declaring-dependencies.md +++ b/docs/guides/declaring-dependencies.md @@ -33,6 +33,8 @@ Both attributes desugar to the same `where` predicates they replace. When a prov `#[uses(...)]` accepts any bound a `where` clause allows, including one with associated-type equality, though the simple `Trait` form is the idiomatic one. One case moves elsewhere: when a bound pins an *abstract type* — `Self: HasErrorType` — express it with the [`#[use_type]` equality form](importing-abstract-types.md) rather than spelling the equality in `#[uses]` or a hand-written `where`. Only equality on a trait that is not a `#[use_type]` import (`Iterator`) stays an explicit `where` clause, where it reads more clearly than crammed into an import-shaped attribute. +When a `#[uses]`-imported trait carries an abstract-type component as a *supertrait*, its associated type reaches the definition transitively — but prefer to also import that type explicitly with [`#[use_type]`](importing-abstract-types.md) rather than lean on the transitive `Self::Assoc`. If `CanCreateFoo` has `HasFooType` as a supertrait, write `#[uses(CanCreateFoo)]` *and* `#[use_type(HasFooType.Foo)]` so the signature names the bare `Foo`, with `#[uses]` declaring the capability dependency and `#[use_type]` declaring the type dependency — both visible, rather than the type riding in silently on the capability. See [importing abstract types](importing-abstract-types.md#re-import-a-type-that-arrives-through-a-supertrait). + ## Related guides - [Writing providers](writing-providers.md) — the `#[cgp_impl]` header these attributes attach to. diff --git a/docs/guides/importing-abstract-types.md b/docs/guides/importing-abstract-types.md index 48ad669e..2dc99d8b 100644 --- a/docs/guides/importing-abstract-types.md +++ b/docs/guides/importing-abstract-types.md @@ -29,10 +29,61 @@ One rule bounds the rewrite: it fires only on the bare identifier of an *importe When a definition imports types from several traits, combine them into one `#[use_type]` attribute by separating the trait paths with commas — `#[use_type(HasUserIdType.UserId, HasCurrencyType.Currency, HasErrorType.Error)]` — rather than stacking one attribute per trait; the combined form reads as a single import list. Several types from one trait use a braced list (`#[use_type(HasFooType.{Foo, Bar})]`). +## Import a foreign abstract type with `in Context` + +Prefer `#[use_type]` even when the abstract type lives on *another* type rather than on `Self` — a type named by a generic parameter. Add a trailing `in Context` clause: it rewrites the bare alias to `::Assoc` and adds `Context: Trait` as a bound, so you write neither the bound nor the qualified path by hand. This is the recommended form for a getter or method that reads a type off a parameter. The verbose form + +```rust +#[cgp_auto_getter] +pub trait HasLoggedInUser +where + App: HasUserIdType, +{ + fn logged_in_user(&self) -> &Option; +} +``` + +becomes + +```rust +#[cgp_auto_getter] +#[use_type(HasUserIdType.UserId in App)] +pub trait HasLoggedInUser { + fn logged_in_user(&self) -> &Option; +} +``` + +The `in App` clause supplies `App: HasUserIdType` on the generated trait, so the plain unbounded `` parameter is enough, and the signature names the bare `UserId` instead of `App::UserId`. The same clause works on `#[cgp_fn]` and `#[cgp_impl]`, and it composes: an `in Context` may itself point at another imported alias to chain through several hops. The written order of such chained imports does not matter — only a cycle, where two contexts resolve through each other, has no valid order and is rejected by the compiler. + ## Pinning an abstract type to a concrete one On a `#[cgp_impl]` or `#[cgp_fn]`, `#[use_type]` also *pins* an abstract type to a concrete one with the equality form `{Assoc = Type}`, which is the replacement for a hand-written `where Self: HasXType` clause. Writing `#[use_type(HasErrorType.{Error = AppError})]` emits `Self: HasErrorType` (and rewrites any bare `Error`), so a provider fixed to a concrete error type moves that pin out of its `where` clause and into the import. The right-hand side may name another imported alias to *unify* two abstract types — `#[use_type(HasPasswordType.Password, HasHashedPasswordType.{HashedPassword = Password})]` emits `Self: HasHashedPasswordType::Password>`. The equality form is rejected on `#[cgp_component]`, since a trait definition cannot carry the impl-side constraint it produces — this is the one place a pin stays in a hand-written `where` clause, and only for equality on a trait you would never `#[use_type]` from. +## Re-import a type that arrives through a supertrait + +Import an abstract type with `#[use_type]` even when it *already* reaches the definition transitively — as the supertrait of a trait you pulled in with [`#[uses]`](declaring-dependencies.md). Relying on the transitive path forces the qualified `Self::Assoc`, which only resolves when the supertrait is reachable and names the type unambiguously; a second `#[use_type]` gives you the bare alias directly and states the dependency where a reader can see it. When `CanCreateFoo` carries `HasFooType` as a supertrait, prefer + +```rust +#[cgp_fn] +#[uses(CanCreateFoo)] +#[use_type(HasFooType.Foo)] +fn bar(&self) -> Foo { + self.create_foo() +} +``` + +over leaning on the transitive supertrait and writing the qualified path: + +```rust +#[cgp_fn] +#[uses(CanCreateFoo)] +fn bar(&self) -> Self::Foo { + self.create_foo() +} +``` + +The extra `#[use_type(HasFooType.Foo)]` re-adds `Self: HasFooType` — harmless, since it is already implied — and rewrites the bare `Foo` throughout, so the signature and body read the same way they would if the type were imported directly. `#[uses]` declares the *capability* dependency and `#[use_type]` declares the *type* dependency; naming both is clearer than making the type ride in silently on the other. + When a capability supertrait has no associated type to import — a plain capability like `HasName` — add it with [`#[extend]`](capability-supertraits.md) rather than `#[use_type]`. Use `#[use_type]` when the signature names the trait's associated type; use `#[extend]` when it only calls the trait's methods. ## Related guides diff --git a/docs/implementation/README.md b/docs/implementation/README.md index 0eb9a15d..b07a06fd 100644 --- a/docs/implementation/README.md +++ b/docs/implementation/README.md @@ -68,7 +68,7 @@ One document per evaluation stack, grouped by the macro that owns it: - [delegate_component](asts/delegate_component.md), [check_components](asts/check_components.md), [namespace](asts/namespace.md). - [cgp_data](asts/cgp_data.md) — the shared extensible-data derive stack. - [product](asts/product.md), [sum](asts/sum.md), [path](asts/path.md), [symbol](asts/symbol.md) — the type-level construction stacks. -- [attributes](asts/attributes.md) — the modifier-attribute AST types (`#[uses]`, `#[use_type]`, `#[use_provider]`, `#[extend]`, `#[extend_where]`, `#[derive_delegate]`, `#[default_impl]`). +- [attributes/](asts/attributes/README.md) — the modifier-attribute AST stacks, one page per modifier: [`#[uses]`](asts/attributes/uses.md), [`#[use_type]`](asts/attributes/use_type.md), [`#[use_provider]`](asts/attributes/use_provider.md), [`#[extend]`](asts/attributes/extend.md), [`#[extend_where]`](asts/attributes/extend_where.md), [`#[derive_delegate]`](asts/attributes/derive_delegate.md), and [`#[default_impl]`](asts/attributes/default_impl.md). ### Functions — [functions/](functions/) diff --git a/docs/implementation/asts/attributes.md b/docs/implementation/asts/attributes.md deleted file mode 100644 index 13be1761..00000000 --- a/docs/implementation/asts/attributes.md +++ /dev/null @@ -1,90 +0,0 @@ -# The attribute-modifier AST stack - -The attribute modifiers — `#[uses]`, `#[use_type]`, `#[use_provider]`, `#[extend]`, `#[extend_where]`, `#[derive_delegate]`, and `#[default_impl]` — are not standalone macros. Each is an `#[…]` attribute that a host macro (`#[cgp_component]`, `#[cgp_impl]`, or `#[cgp_fn]`) strips off its input, parses into an AST type, and folds into the code it generates. This document covers each modifier's AST type, what it parses from, and what it injects into its host's output; for the user-facing syntax and expansion of each, read the reference documents under [reference/](../../reference/README.md) (in the `attributes/` subdirectory), and for how the hosts drive them see [entrypoints/cgp_component.md](../entrypoints/cgp_component.md), [entrypoints/cgp_impl.md](../entrypoints/cgp_impl.md), and [entrypoints/cgp_fn.md](../entrypoints/cgp_fn.md). - -The modifiers do not parse themselves out of the token stream on their own; a host collects them first. `#[cgp_component]` gathers them into a `CgpComponentAttributes`, `#[cgp_impl]` into a `CgpImplAttributes`, and `#[cgp_fn]` into a `FunctionAttributes`. Each collector walks the item's attribute list, matches the leading identifier (`uses`, `use_type`, …), parses that attribute's arguments into the corresponding AST type, and passes any unrecognized attribute through untouched onto the generated code. Which modifiers a host accepts differs — `#[derive_delegate]` and `#[prefix]` are only meaningful on a component, `#[default_impl]` only on a provider impl — so a given modifier appears in only the collectors of the hosts that consume it. - -## `#[uses]` - -`#[uses(TraitA, TraitB)]` imports `Self` trait bounds, reading like a `use` statement. It parses into `UsesAttributes`, which holds a `Vec` — one bound per imported capability. Each entry is a full `syn::TypeParamBound`, so any bound a `where` clause accepts is allowed, including an associated-type-equality binding (`HasErrorType`), a higher-ranked bound, or a lifetime bound; the plain `Trait` form is the idiomatic one. Its `to_type_param_bounds` collects the bounds, and the host appends them to the generated impl's `where` clause (on `Self`), where they become impl-side dependencies. On `#[cgp_fn]` the bounds are parsed straight into `FunctionAttributes` (a `Vec` field mirroring `#[extend]`); on `#[cgp_impl]` into the `UsesAttributes` held by `CgpImplAttributes`. The bounds land only on the impl, never on the consumer trait, which is what keeps the dependency hidden from callers. - -## `#[use_type]` - -`#[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: -::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. 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]` - -`#[use_provider(Inner: AreaCalculator)]` completes an inner provider's bound for a higher-order provider. It parses into a `UseProviderAttribute` — a provider type, a colon, and a `+`-separated list of provider-trait paths — collected into a `UseProviderAttributes`. The one thing it does is finish each bound by inserting the context type as the leading generic argument, so the user's `: AreaCalculator` becomes `AreaCalculator`, and move the completed bound into the impl's `where` clause on the provider parameter: - -```rust -// #[use_provider(Inner: AreaCalculator)] becomes the where-predicate: -Inner: AreaCalculator -``` - -The context type is inserted at index 0 of the trait's angle-bracketed arguments, so a bound that already carries parameters keeps them after the context. On `#[cgp_impl]` the collection is `CgpImplAttributes`; on `#[cgp_fn]`, `FunctionAttributes`. There is no call-site rewriting — the body still calls the provider explicitly with the associated-function form. - -## `#[extend]` - -`#[extend(Trait)]` adds *supertrait* bounds to a generated trait. On `#[cgp_fn]` it is parsed into the `extend` field of `FunctionAttributes` — a `Vec` — and its bounds are pushed onto both the generated trait's supertraits and the impl's `where` clause, because it is the only way to add a supertrait when a `#[cgp_fn]`'s `where` clauses are reserved for impl-side dependencies. On `#[cgp_component]` it is parsed by `CgpComponentAttributes` and its bounds are appended to the consumer trait's supertraits during `preprocess`, where it is the preferred way to add a non-type capability supertrait (an abstract-type supertrait should instead use `#[use_type]`, which adds the bound *and* rewrites the type). - -## `#[extend_where]` - -`#[extend_where(Bound)]` adds `where` predicates to a generated trait definition, and is `#[cgp_fn]`-only. It parses into the `extend_where` field of `FunctionAttributes` — a `Vec` — and its predicates are added to both the trait and the impl `where` clauses. Where `#[uses]` adds a bound to `Self` on the impl alone, `#[extend_where]` is the way to make a bound part of the generated *trait*, and it takes a full predicate — a bound on any type, not only `Self`, including associated-type-equality constraints. - -## `#[derive_delegate]` - -`#[derive_delegate(UseDelegate)]` (on a `#[cgp_component]` trait) generates a dispatcher provider impl so the component can be wired to a `UseDelegate` table. It parses into a `DeriveDelegateAttribute` — a wrapper identifier (`UseDelegate`) and its angle-bracketed key, which is either a single identifier or a non-empty parenthesized tuple — collected into a `DeriveDelegateAttributes`. Its `to_provider_impl` builds one impl of the provider trait for `Wrapper<__Components__>` that forwards each method to a delegate looked up through `DelegateComponent`. The impl carries two synthetic generics and two `where` bounds — the table lookup and the delegate's provider-trait bound — and forwards each trait method through the shared delegated-impl helpers: - -```rust -impl<__Context__, __Components__, __Delegate__> AreaCalculator<__Context__> - for UseDelegate<__Components__> -where - __Components__: DelegateComponent<(Shape), Delegate = __Delegate__>, - __Delegate__: AreaCalculator<__Context__>, -{ /* each method forwards to __Delegate__ */ } -``` - -The host (`#[cgp_component]`) collects it in `CgpComponentAttributes` and emits one such impl per `#[derive_delegate]` attribute alongside the component's standard provider impls. It is a legacy form for user code — `open` dispatch is preferred — but CGP's own error and handler families still define components with it. - -## `#[default_impl]` - -`#[default_impl(@test.ShowImplComponent.u32 in ExtendedNamespace)]` (on a `#[cgp_impl]` provider) registers the provider as a namespace's default for one path. It parses into a `DefaultImplAttribute` — a key type (a path or type), the `in` keyword, and the namespace path — collected into a `DefaultImplAttributes`. Its `to_item_impl` emits one impl of the namespace's lookup trait, keyed on the given path type, whose `Delegate` associated type is the provider being defined: - -```rust -// #[default_impl(@test.ShowImplComponent.u32 in ExtendedNamespace)] on provider ShowU32: -impl<__Components__> ExtendedNamespace<__Components__> -for PathCons>> -{ - type Delegate = ShowU32; -} -``` - -The namespace path gains a trailing `__Components__` type argument and the impl generics gain a matching `__Components__` parameter, so the default is generic over any table the namespace is queried through. The host (`#[cgp_impl]`) collects it in `CgpImplAttributes` and emits one such impl per attribute after the provider impl, using the provider's own generics and provider type. - -**The provider's `where` clause is deliberately dropped from this impl.** `to_item_impl` receives the provider impl's generics *after* `#[implicit]`/`#[uses]`/`#[use_type]`/`#[use_provider]` have pushed their `Self`-keyed impl-side bounds into it — a provider with `#[use_type(HasErrorType.Error)]`, for instance, arrives carrying `where Self: HasErrorType`. Those bounds belong on the provider's own impl and its `IsProviderFor`, never on this registration impl, whose only job is `type Delegate = Provider`. The registration impl's `Self` is the path key (`PathCons<..>`), so a retained `Self: HasErrorType` would demand `PathCons<..>: HasErrorType` — a bound that never holds — and silently break every context that joins the namespace. `to_item_impl` therefore clears `generics.where_clause` before splitting, keeping only the parameters that name the key and provider plus the `__Components__` table. (A provider whose *type* is generic, and whose parameter appears only in the `Delegate` associated type, would leave that parameter unconstrained, so a per-component default is written for a concrete provider.) - -## Tests - -The behavioral and snapshot tests that exercise each modifier are listed per attribute below; test and snapshot pointers for a construct live only in these implementation documents. - -- **`#[uses]`** — [impl_side_dependencies/fn_uses.rs](../../../crates/tests/cgp-tests/tests/impl_side_dependencies/fn_uses.rs) pins the `#[cgp_fn]` form and [impl_side_dependencies/impl_uses.rs](../../../crates/tests/cgp-tests/tests/impl_side_dependencies/impl_uses.rs) the `#[cgp_impl]` form; [generic_components/fn_impl_generics.rs](../../../crates/tests/cgp-tests/tests/generic_components/fn_impl_generics.rs) exercises it alongside generic parameters. The associated-type-equality bound (`HasErrorType`) that `#[uses]` also accepts is pinned on the `#[cgp_fn]` form in [impl_side_dependencies/fn_uses_associated_type.rs](../../../crates/tests/cgp-tests/tests/impl_side_dependencies/fn_uses_associated_type.rs) and exercised end-to-end on the `#[cgp_impl]` form in [impl_side_dependencies/impl_uses_associated_type.rs](../../../crates/tests/cgp-tests/tests/impl_side_dependencies/impl_uses_associated_type.rs). -- **`#[use_type]`** — [abstract_types/use_type_component.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_component.rs) covers the `#[cgp_component]` supertrait form and [use_type_foreign.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_foreign.rs) the `@` foreign form on a component; [abstract_types/use_type_fn_alias.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_alias.rs), [use_type_fn_equality.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_equality.rs), and [use_type_fn_foreign.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign.rs) cover the alias, equality, and foreign-context (`@`) forms; [use_type_fn_equality_cross_trait.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_equality_cross_trait.rs), [use_type_fn_foreign_equality.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign_equality.rs), and [use_type_fn_foreign_equality_cross_trait.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign_equality_cross_trait.rs) cover cross-spec and nested-foreign equality; [use_type_generic_param.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_generic_param.rs) covers an alias that collides with a generic parameter, [use_type_path_qualified.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_path_qualified.rs) the path-qualified trait form the `.` separator enables, and [implicit_arguments/cgp_fn_multi_and_use_type.rs](../../../crates/tests/cgp-tests/tests/implicit_arguments/cgp_fn_multi_and_use_type.rs) the generic-argument trait form (`HasFooType.Foo`). Rejections are pinned in [parser_rejections/use_type.rs](../../../crates/tests/cgp-macro-tests/tests/parser_rejections/use_type.rs): a `=` equality on a component, and a duplicate identifier or alias across specs, within one braced list, and on a component. -- **`#[use_provider]`** — [higher_order_providers/use_provider_fn.rs](../../../crates/tests/cgp-tests/tests/higher_order_providers/use_provider_fn.rs) pins the `#[cgp_fn]` form and [higher_order_providers/use_provider_impl.rs](../../../crates/tests/cgp-tests/tests/higher_order_providers/use_provider_impl.rs) the `#[cgp_impl]` form; [higher_order_providers/scaled_area.rs](../../../crates/tests/cgp-tests/tests/higher_order_providers/scaled_area.rs) wires a full higher-order provider through it. -- **`#[extend]`** — [impl_side_dependencies/fn_extend.rs](../../../crates/tests/cgp-tests/tests/impl_side_dependencies/fn_extend.rs) pins the `#[cgp_fn]` supertrait form; [abstract_types/extend_component.rs](../../../crates/tests/cgp-tests/tests/abstract_types/extend_component.rs) and [abstract_types/use_type_fn_extend.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_extend.rs) exercise it on a component and alongside `#[use_type]`; [getters/abstract_type_extend.rs](../../../crates/tests/cgp-tests/tests/getters/abstract_type_extend.rs) uses it with a getter. -- **`#[extend_where]`** — [abstract_types/use_type_fn_nested_foreign.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_nested_foreign.rs) exercises it alongside `#[use_type]` on a `#[cgp_fn]`. -- **`#[derive_delegate]`** — [dispatching/use_delegate_getter.rs](../../../crates/tests/cgp-tests/tests/dispatching/use_delegate_getter.rs) wires a component defined with `#[derive_delegate]` through a `UseDelegate` table. -- **`#[default_impl]`** — [namespaces/default_impls.rs](../../../crates/tests/cgp-tests/tests/namespaces/default_impls.rs) pins the emitted namespace-default impl (`snapshot_cgp_impl!`), and [namespaces/default_impls_wiring.rs](../../../crates/tests/cgp-tests/tests/namespaces/default_impls_wiring.rs) checks a context picks up the default. [namespaces/default_impl_use_type.rs](../../../crates/tests/cgp-tests/tests/namespaces/default_impl_use_type.rs) pins that the registration impl carries no `where` clause when the provider has a `#[use_type]` dependency, and resolves such a provider through a context that joins the namespace; the cross-crate orphan restriction on a prefixed component's default is pinned in [acceptable/cgp_namespace/default_impl_foreign_prefix_path.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_prefix_path.rs). - -## Source - -- The modifiers live in [cgp-macro-core/src/types/attributes/](../../../crates/macros/cgp-macro-core/src/types/attributes/): `uses.rs` (`UsesAttributes`), the `use_type/` submodule (`UseTypeAttribute`, per-type entries in `ident.rs`, the two-phase transform in `attributes.rs`, and predicate derivation in `type_predicates.rs`), the `use_provider/` submodule (`UseProviderAttribute` and its bound completion), the `derive_delegate/` submodule (`DeriveDelegateAttribute::to_provider_impl`), and the `default_impl/` submodule (`DefaultImplAttribute::to_item_impl`). -- `#[extend]`/`#[extend_where]` are fields of `FunctionAttributes` in `function.rs`. -- The host collectors are `CgpComponentAttributes` in `cgp_component_attributes.rs`, `CgpImplAttributes` in `cgp_impl_attributes.rs`, and `FunctionAttributes` in `function.rs`. -- The abstract-type substitution is the `SubstituteAbstractType` visitor in [cgp-macro-core/src/visitors/substitute_abstract_type.rs](../../../crates/macros/cgp-macro-core/src/visitors/substitute_abstract_type.rs), and the `#[derive_delegate]` forwarding bodies come from the [delegated-impl helpers](../functions/derive/delegated_impls.md). diff --git a/docs/implementation/asts/attributes/README.md b/docs/implementation/asts/attributes/README.md new file mode 100644 index 00000000..bc338905 --- /dev/null +++ b/docs/implementation/asts/attributes/README.md @@ -0,0 +1,46 @@ +# The attribute-modifier AST stacks + +The attribute modifiers — `#[uses]`, `#[use_type]`, `#[use_provider]`, `#[extend]`, `#[extend_where]`, `#[derive_delegate]`, and `#[default_impl]` — are not standalone macros; each is an `#[…]` attribute that a host macro strips off its input, parses into an AST type, and folds into the code it generates. This directory documents one modifier per page: each page covers that modifier's AST types, what it parses from, and what it injects into its host's output. This overview covers what the modifiers share — how a host collects them and which host accepts which — so the per-modifier pages can stay focused on their own types. For the user-facing syntax and expansion of each, read the reference documents in the [reference `attributes/` subdirectory](../../../reference/attributes/uses.md); for how the hosts drive them, see [entrypoints/cgp_component.md](../../entrypoints/cgp_component.md), [entrypoints/cgp_impl.md](../../entrypoints/cgp_impl.md), and [entrypoints/cgp_fn.md](../../entrypoints/cgp_fn.md). + +## The pages + +Each modifier has its own page in this directory: + +- [`#[uses]`](uses.md) — import `Self` trait bounds onto a provider impl, reading like a `use` statement. +- [`#[use_type]`](use_type.md) — import an abstract associated type: rewrite the bare alias everywhere and add the owning trait as a bound. +- [`#[use_provider]`](use_provider.md) — complete an inner provider's bound for a higher-order provider. +- [`#[extend]`](extend.md) — add *supertrait* bounds to a generated trait. +- [`#[extend_where]`](extend_where.md) — add `where` predicates to a generated trait definition. +- [`#[derive_delegate]`](derive_delegate.md) — generate a `UseDelegate` dispatcher provider impl for a component. +- [`#[default_impl]`](default_impl.md) — register a provider as a namespace's per-path default. + +## How a host collects a modifier + +The modifiers do not parse themselves out of the token stream on their own; a host collects them first. Each host macro owns a collector type that walks the item's attribute list once, matches the leading identifier (`uses`, `use_type`, …) of every attribute, parses that attribute's arguments into the corresponding AST type, and passes any unrecognized attribute through untouched onto the generated code. There are three collectors, one per host: + +- `CgpComponentAttributes` (in `cgp_component_attributes.rs`) collects the modifiers `#[cgp_component]` accepts, during its `preprocess` stage. +- `CgpImplAttributes` (in `cgp_impl_attributes.rs`) collects the modifiers `#[cgp_impl]` accepts, during `ItemCgpImpl::lower`. +- `FunctionAttributes` (in `function.rs`) collects the modifiers `#[cgp_fn]` accepts (and the getter macros reuse), during `preprocess`. + +An unrecognized attribute is never an error: a collector that does not match an attribute's leading identifier pushes it back onto a `raw_attributes` list (or straight back onto the item, for the component collector), which the host re-attaches to the generated code. This is what lets `#[async_trait]`, `#[allow(...)]`, and any other foreign attribute ride through a host macro untouched. + +## Which host accepts which modifier + +Which modifiers a host accepts differs, because a modifier is only meaningful on the construct that can consume it — `#[derive_delegate]` needs a component's provider trait to dispatch, `#[default_impl]` needs a provider to register, and `#[extend_where]` needs a generated trait whose own `where` clause it can extend. A modifier therefore appears only in the collectors of the hosts that consume it: + +| Modifier | `#[cgp_component]` | `#[cgp_impl]` | `#[cgp_fn]` | +|---|:---:|:---:|:---:| +| `#[uses]` | | ✓ | ✓ | +| `#[use_type]` | ✓ | ✓ | ✓ | +| `#[use_provider]` | | ✓ | ✓ | +| `#[extend]` | ✓ | | ✓ | +| `#[extend_where]` | | | ✓ | +| `#[derive_delegate]` | ✓ | | | +| `#[default_impl]` | | ✓ | | + +Two further modifiers the same collectors handle are documented elsewhere rather than here, because they belong to another construct's story. `CgpComponentAttributes` also collects `#[prefix(@path in Namespace)]`, which registers a component into a namespace and is documented with the [namespace machinery](../namespace.md). `FunctionAttributes` also collects `#[impl_generics(Param: Bound)]`, which adds a bounded generic parameter to a `#[cgp_fn]`'s impl alone and is documented with [`#[cgp_fn]`](../../entrypoints/cgp_fn.md). + +## Source + +- The modifiers live in [cgp-macro-core/src/types/attributes/](../../../../crates/macros/cgp-macro-core/src/types/attributes/), one module or submodule per modifier, re-exported from `mod.rs`. +- The host collectors are `CgpComponentAttributes` in [cgp_component_attributes.rs](../../../../crates/macros/cgp-macro-core/src/types/attributes/cgp_component_attributes.rs), `CgpImplAttributes` in [cgp_impl_attributes.rs](../../../../crates/macros/cgp-macro-core/src/types/attributes/cgp_impl_attributes.rs), and `FunctionAttributes` in [function.rs](../../../../crates/macros/cgp-macro-core/src/types/attributes/function.rs). diff --git a/docs/implementation/asts/attributes/default_impl.md b/docs/implementation/asts/attributes/default_impl.md new file mode 100644 index 00000000..209bc98f --- /dev/null +++ b/docs/implementation/asts/attributes/default_impl.md @@ -0,0 +1,48 @@ +# `#[default_impl]` — the AST stack + +`#[default_impl(@test.ShowImplComponent.u32 in ExtendedNamespace)]` on a `#[cgp_impl]` provider registers that provider as a namespace's default for one path. It is a modifier attribute collected by the impl host; this page covers its AST types and the registration impl it builds, and the shared collection mechanism lives in the [attribute-modifier overview](README.md). For the user-facing syntax and the namespace machinery it plugs into, read the reference document [`DefaultNamespace`](../../../reference/traits/default_namespace.md). + +## `DefaultImplAttribute` + +The attribute parses into a `DefaultImplAttribute` — a `key_type` (a `UniPathOrType`, so the key may be a path or a type), the `in` keyword, and a `namespace` path (a `PathWithTypeArgs`). Parsing reads the key type, the `in` token, and the namespace in order. + +## `DefaultImplAttributes` + +`DefaultImplAttributes` is the collection wrapper — a `Vec` — that `CgpImplAttributes` fills, one entry per `#[default_impl]` attribute on the provider block. Its `to_item_impls(provider_generics, provider_type)` maps each entry through `to_item_impl`, and the host (`#[cgp_impl]`) emits the resulting impls after the provider impl, using the provider's own generics and provider type. + +## `to_item_impl` — the registration impl + +`DefaultImplAttribute::to_item_impl(provider_generics, provider_type)` emits one impl of the namespace's lookup trait, keyed on the given path type, whose `Delegate` associated type is the provider being defined: + +```rust +// #[default_impl(@test.ShowImplComponent.u32 in ExtendedNamespace)] on provider ShowU32: +impl<__Components__> ExtendedNamespace<__Components__> +for PathCons>> +{ + type Delegate = ShowU32; +} +``` + +The namespace path gains a trailing `__Components__` type argument and the impl generics gain a matching `__Components__` parameter, so the default is generic over any table the namespace is queried through. The impl is built from quasi-quoted tokens and then re-spanned onto the user-written key with [`override_item_span`](../../entrypoints/delegate_components.md#error-spans), so a coherence conflict (`E0119`) between two default impls for the same key is reported on the key inside `#[default_impl(Key in …)]` rather than on the whole `#[cgp_impl]` attribute; only the boundary moves, so the interior tokens stay navigable in an IDE. + +## The dropped `where` clause + +**The provider's `where` clause is deliberately dropped from this impl**, and this is the subtle correctness point of `to_item_impl`. It receives the provider impl's generics *after* `#[implicit]`/`#[uses]`/`#[use_type]`/`#[use_provider]` have pushed their `Self`-keyed impl-side bounds into it — a provider with `#[use_type(HasErrorType.Error)]`, for instance, arrives carrying `where Self: HasErrorType`. Those bounds belong on the provider's own impl and its `IsProviderFor`, never on this registration impl, whose only job is `type Delegate = Provider`. The registration impl's `Self` is the path key (`PathCons<..>`), so a retained `Self: HasErrorType` would demand `PathCons<..>: HasErrorType` — a bound that never holds — and silently break every context that joins the namespace. `to_item_impl` therefore clears `generics.where_clause` before splitting, keeping only the parameters that name the key and provider plus the `__Components__` table. + +The one consequence of dropping the `where` clause is a limitation on generic providers: a provider whose *type* is generic, and whose parameter appears only in the `Delegate` associated type, would leave that parameter unconstrained once the clause is gone, so a per-component default is written for a concrete provider rather than a generic one. + +## Tests + +The behavioral and snapshot tests exercise the emitted impl, the wiring it enables, the `where`-clause drop, and the cross-crate orphan restriction: + +- [namespaces/default_impls.rs](../../../../crates/tests/cgp-tests/tests/namespaces/default_impls.rs) pins the emitted namespace-default impl (`snapshot_cgp_impl!`), and [namespaces/default_impls_wiring.rs](../../../../crates/tests/cgp-tests/tests/namespaces/default_impls_wiring.rs) checks a context picks up the default. +- [namespaces/default_impl_use_type.rs](../../../../crates/tests/cgp-tests/tests/namespaces/default_impl_use_type.rs) pins that the registration impl carries no `where` clause when the provider has a `#[use_type]` dependency, and resolves such a provider through a context that joins the namespace. +- The cross-crate orphan restriction on a default is pinned in [acceptable/cgp_namespace/default_impl_foreign_prefix_path.rs](../../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_prefix_path.rs) (a *prefixed* component's foreign path key) and [acceptable/cgp_namespace/default_impl_foreign_component.rs](../../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_component.rs) (a foreign *unprefixed* component marker key), both the [orphan-rule violation](../../../errors/wiring/orphan-rule.md) class. + +The duplicate-key conflict `#[cgp_impl]` defers to the compiler is covered on the host's own page — see [Failure modes in entrypoints/cgp_impl.md](../../entrypoints/cgp_impl.md#failure-modes). + +## Source + +- The `default_impl/` submodule in [cgp-macro-core/src/types/attributes/default_impl/](../../../../crates/macros/cgp-macro-core/src/types/attributes/default_impl/): `attribute.rs` holds `DefaultImplAttribute`, its parser, and `to_item_impl`; `attributes.rs` holds the `DefaultImplAttributes` collection and `to_item_impls`. +- Boundary re-spanning is [`override_item_span`](../../../../crates/macros/cgp-macro-core/src/functions/override_span.rs). +- The host that drives it: [entrypoints/cgp_impl.md](../../entrypoints/cgp_impl.md). diff --git a/docs/implementation/asts/attributes/derive_delegate.md b/docs/implementation/asts/attributes/derive_delegate.md new file mode 100644 index 00000000..14c431a3 --- /dev/null +++ b/docs/implementation/asts/attributes/derive_delegate.md @@ -0,0 +1,44 @@ +# `#[derive_delegate]` — the AST stack + +`#[derive_delegate(UseDelegate)]` on a `#[cgp_component]` trait generates a dispatcher provider impl, so the component can be wired to a `UseDelegate` table that dispatches on a generic parameter. It is a modifier attribute collected by the component host; this page covers its AST types and the impl it builds, and the shared collection mechanism lives in the [attribute-modifier overview](README.md). For the user-facing syntax and expansion, read the reference document [reference/attributes/derive_delegate.md](../../../reference/attributes/derive_delegate.md). + +## `DeriveDelegateAttribute` + +The attribute parses into a `DeriveDelegateAttribute` — a `wrapper` identifier (`UseDelegate`) and its angle-bracketed key, held as a `Punctuated` of `params`. The parser reads the wrapper identifier, a `<`, then either a single identifier or a parenthesized tuple of identifiers, then a `>`. An empty parenthesized tuple is rejected with a spanned "expect non-empty tuple list of identifiers in use_delegate_spec" error, so `UseDelegate<()>` cannot slip through as a keyless dispatcher. + +## `DeriveDelegateAttributes` + +`DeriveDelegateAttributes` is the thin collection wrapper — a `Vec` — that `CgpComponentAttributes` fills, one entry per `#[derive_delegate]` attribute on the trait. The host emits one dispatcher impl per entry alongside the component's standard provider impls, during `to_items`. + +## `to_provider_impl` — the generated impl + +`DeriveDelegateAttribute::to_provider_impl(provider_trait)` builds one impl of the provider trait for `Wrapper<__Components__>` that forwards each method to a delegate looked up through `DelegateComponent`. It clones the provider trait's own generics and appends two synthetic parameters — `__Components__` (the table type) and `__Delegate__` (the resolved delegate) — then adds two `where` bounds: the table lookup that resolves the key to a delegate, and the delegate's own provider-trait bound. Each trait method is forwarded through the shared [delegated-impl helpers](../../functions/derive/delegated_impls.md), so the dispatcher's bodies read as `<__Delegate__>::method(context, …)`: + +```rust +impl<__Context__, __Components__, __Delegate__> AreaCalculator<__Context__> + for UseDelegate<__Components__> +where + __Components__: DelegateComponent<(Shape), Delegate = __Delegate__>, + __Delegate__: AreaCalculator<__Context__>, +{ /* each method forwards to __Delegate__ */ } +``` + +The key in the `DelegateComponent<(…)>` lookup is the parenthesized `params` the attribute parsed, so a single-identifier key becomes `(Shape)` and a tuple key `(A, B)`. The impl keeps the component's own generics ahead of the two synthetic parameters, and reuses the provider trait's type generics (via `split_for_impl`) for both the delegate bound and the forwarded projections. + +## Behavior and corner cases + +**`#[derive_delegate]` is a legacy form for user code, but not dead code.** The `open` dispatch statement is preferred for new components, yet CGP's own error and handler families still *define* components with `#[derive_delegate]`, so the dispatcher impl this attribute generates remains in active use across the library. + +**The synthetic parameters use the reserved double-underscore form.** `__Components__` and `__Delegate__` are constructed with `Span::call_site()` and the double-underscore convention so they cannot clash with a user's own type parameters or with the component's generics that precede them. + +## Tests + +- [dispatching/use_delegate_getter.rs](../../../../crates/tests/cgp-tests/tests/dispatching/use_delegate_getter.rs) wires a component defined with `#[derive_delegate]` through a `UseDelegate` table. + +The `UseDelegate` impl a `#[derive_delegate]` attribute adds to a bare component has no dedicated expansion snapshot yet; it is exercised through the error and handler families instead (noted as a missing snapshot in [entrypoints/cgp_component.md](../../entrypoints/cgp_component.md)). + +## Source + +- The `derive_delegate/` submodule in [cgp-macro-core/src/types/attributes/derive_delegate/](../../../../crates/macros/cgp-macro-core/src/types/attributes/derive_delegate/): `attribute.rs` holds `DeriveDelegateAttribute`, its parser, and `to_provider_impl`; `attributes.rs` holds the `DeriveDelegateAttributes` collection. +- The forwarding method bodies come from the [delegated-impl helpers](../../functions/derive/delegated_impls.md). +- The host that drives it: [entrypoints/cgp_component.md](../../entrypoints/cgp_component.md). diff --git a/docs/implementation/asts/attributes/extend.md b/docs/implementation/asts/attributes/extend.md new file mode 100644 index 00000000..5277d750 --- /dev/null +++ b/docs/implementation/asts/attributes/extend.md @@ -0,0 +1,29 @@ +# `#[extend]` — the AST stack + +`#[extend(Trait)]` adds *supertrait* bounds to a generated trait, widening the trait's public interface rather than adding a hidden impl-side dependency. It is a modifier attribute collected by a host macro; this page covers what it parses into and what it injects, and the shared collection mechanism lives in the [attribute-modifier overview](README.md). For the user-facing syntax and expansion, read the reference document [reference/attributes/extend.md](../../../reference/attributes/extend.md). + +## What it parses into + +`#[extend]` has no dedicated AST type: it parses directly into a `Vec` field on its host collector, populated by `Punctuated::::parse_terminated`. On `#[cgp_fn]` that field is `FunctionAttributes::extend`; on `#[cgp_component]` it is `CgpComponentAttributes::extend`. Each bound is a full `syn::TypeParamBound`, so the same wide grammar `#[uses]` accepts parses here. + +## What the hosts inject + +`#[extend]` is accepted on `#[cgp_component]` and `#[cgp_fn]`, and the two hosts treat it differently because a `#[cgp_component]` trait can already declare supertraits natively while a `#[cgp_fn]` trait cannot: + +- On **`#[cgp_component]`**, `preprocess` appends the bounds to the consumer trait's supertraits (`item_trait.supertraits.extend(attributes.extend.clone())`) before the later stages transform the trait. It is the preferred way to add a *non-type* capability supertrait; an abstract-type supertrait should instead use [`#[use_type]`](use_type.md), which adds the bound *and* rewrites the type. +- On **`#[cgp_fn]`**, the bounds are pushed onto *both* the generated trait's supertraits and the impl's `where` clause. This dual placement exists because it is the only way to add a supertrait to a `#[cgp_fn]` trait — a `#[cgp_fn]`'s own `where` clauses are reserved for impl-side dependencies, so there is no other channel through which a supertrait can reach the generated trait. + +The contrast with [`#[uses]`](uses.md) is the reason both exist: `#[uses]` lands its bound on the impl's `Self` alone, hidden from callers, while `#[extend]` makes the bound a supertrait that every caller sees. And the contrast with [`#[extend_where]`](extend_where.md) is placement: `#[extend]` adds a *supertrait* (a bound on the trait's own `Self`), while `#[extend_where]` adds a full `where` predicate that may bound any type. + +## Tests + +The behavioral tests exercise both hosts and the interaction with getters and `#[use_type]`: + +- [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) exercises it on a component, and [abstract_types/use_type_fn_extend.rs](../../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_extend.rs) alongside `#[use_type]`. +- [getters/abstract_type_extend.rs](../../../../crates/tests/cgp-tests/tests/getters/abstract_type_extend.rs) uses it with a getter. + +## Source + +- The `extend` field is on `FunctionAttributes` in [function.rs](../../../../crates/macros/cgp-macro-core/src/types/attributes/function.rs) and on `CgpComponentAttributes` in [cgp_component_attributes.rs](../../../../crates/macros/cgp-macro-core/src/types/attributes/cgp_component_attributes.rs). +- The hosts that drive it: [entrypoints/cgp_component.md](../../entrypoints/cgp_component.md) and [entrypoints/cgp_fn.md](../../entrypoints/cgp_fn.md). diff --git a/docs/implementation/asts/attributes/extend_where.md b/docs/implementation/asts/attributes/extend_where.md new file mode 100644 index 00000000..a80e81b8 --- /dev/null +++ b/docs/implementation/asts/attributes/extend_where.md @@ -0,0 +1,20 @@ +# `#[extend_where]` — the AST stack + +`#[extend_where(Bound)]` adds `where` predicates to a generated trait definition, and is the only modifier that can make an arbitrary bound part of the generated *trait* rather than only its impl. It is `#[cgp_fn]`-only, and it is a modifier attribute collected by the function host; this page covers what it parses into and what it injects, and the shared collection mechanism lives in the [attribute-modifier overview](README.md). For the user-facing syntax and expansion, read the reference document [reference/attributes/extend_where.md](../../../reference/attributes/extend_where.md). + +## What it parses into + +`#[extend_where]` has no dedicated AST type: it parses directly into the `extend_where` field of `FunctionAttributes` — a `Vec` — populated by `Punctuated::::parse_terminated`. Because each entry is a full [`syn::WherePredicate`](https://docs.rs/syn/latest/syn/enum.WherePredicate.html) rather than a `TypeParamBound`, it can bound *any* type, not only `Self`, and can carry associated-type-equality constraints — expressiveness that `#[uses]` and `#[extend]` (which parse `TypeParamBound`s) do not have. + +## What the host injects + +`preprocess` adds the predicates to *both* the generated trait's own `where` clause and the impl's `where` clause. Adding them to the trait is the point of the attribute — it is what distinguishes `#[extend_where]` from [`#[uses]`](uses.md), which adds a bound to `Self` on the impl alone. Where `#[uses]` hides a dependency behind the impl, `#[extend_where]` makes a bound a visible part of the trait interface, and its full-predicate grammar is what lets it express a bound on a type other than `Self`. + +## Tests + +- [abstract_types/use_type_fn_nested_foreign.rs](../../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_nested_foreign.rs) exercises `#[extend_where]` alongside `#[use_type]` on a `#[cgp_fn]`, where it adds a `Scalar: Copy` bound (rewritten to the two-hop path) to the generated trait's `where` clause. + +## Source + +- The `extend_where` field is on `FunctionAttributes` in [function.rs](../../../../crates/macros/cgp-macro-core/src/types/attributes/function.rs). +- The host that drives it: [entrypoints/cgp_fn.md](../../entrypoints/cgp_fn.md). diff --git a/docs/implementation/asts/attributes/use_provider.md b/docs/implementation/asts/attributes/use_provider.md new file mode 100644 index 00000000..4282a543 --- /dev/null +++ b/docs/implementation/asts/attributes/use_provider.md @@ -0,0 +1,35 @@ +# `#[use_provider]` — the AST stack + +`#[use_provider(Inner: AreaCalculator)]` completes an inner provider's bound for a higher-order provider: the one thing it does is finish the bound by inserting the context as its leading type argument, so the user's `: AreaCalculator` becomes `AreaCalculator`, and move the completed bound onto the impl's `where` clause. It is a modifier attribute collected by a host macro; this page covers its AST types and what it injects, and the shared collection mechanism lives in the [attribute-modifier overview](README.md). For the user-facing syntax and expansion, read the reference document [reference/attributes/use_provider.md](../../../reference/attributes/use_provider.md). + +## `UseProviderAttribute` + +The attribute parses into a `UseProviderAttribute` per entry: a `context_type` (always `Self`), a `provider_type` (the inner provider parameter, e.g. `Inner`), a colon, and a `+`-separated list of provider-trait paths as `provider_trait_bounds` (each a `PathWithTypeArgs`). Parsing is straightforward — the context is fixed to `Self`, then the provider type, the colon, and the terminated `+`-list of bounds. + +The completion happens in two methods. `to_type_param_bounds(context_type)` walks each provider-trait bound, clones it, and **inserts the context at index 0 of the bound's angle-bracketed arguments**, so `AreaCalculator` becomes `AreaCalculator` and a bound that already carries parameters keeps them after the context. Position 0 sits ahead of any lifetime argument, which would be invalid Rust on its own; the method re-parses each completed bound through `parse_internal!`, and that `syn` round-trip re-emits lifetimes first, normalizing the order (see [Generic-parameter insertion and lifetime ordering](../../README.md#generic-parameter-insertion-and-lifetime-ordering)). `to_provider_bounds(context_type)` then wraps the completed bounds into a single `provider_type: bounds` `where` predicate: + +```rust +// #[use_provider(Inner: AreaCalculator)] becomes the where-predicate: +Inner: AreaCalculator +``` + +## `UseProviderAttributes` + +`UseProviderAttributes` holds the `Vec` a host collected and applies them through its `AddTypeParamBounds` impl. `add_type_param_bounds(self_type, generics)` returns early when there are no entries, otherwise pushes each entry's `to_provider_bounds(self_type)` predicate onto the impl generics' `where` clause. The `self_type` passed in is the impl's context type, which is what fills the inserted leading argument. + +## What the hosts inject + +`#[use_provider]` is accepted on `#[cgp_impl]` (collected into `CgpImplAttributes`) and `#[cgp_fn]` (collected into `FunctionAttributes`), and on both it contributes only the completed `where` predicate on the provider parameter. There is no call-site rewriting: the body still calls the inner provider explicitly through the associated-function form (`Inner::area(self)`), so the attribute's whole job is the bound, not the invocation. + +## Tests + +The behavioral tests exercise both hosts and a full higher-order 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. +- [higher_order_providers/use_provider_impl.rs](../../../../crates/tests/cgp-tests/tests/higher_order_providers/use_provider_impl.rs) pins 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. + +## Source + +- The `use_provider/` submodule in [cgp-macro-core/src/types/attributes/use_provider/](../../../../crates/macros/cgp-macro-core/src/types/attributes/use_provider/): `attribute.rs` holds `UseProviderAttribute` and the bound completion, `attributes.rs` holds `UseProviderAttributes` and its `AddTypeParamBounds` impl. +- The hosts that drive it: [entrypoints/cgp_impl.md](../../entrypoints/cgp_impl.md) and [entrypoints/cgp_fn.md](../../entrypoints/cgp_fn.md). diff --git a/docs/implementation/asts/attributes/use_type.md b/docs/implementation/asts/attributes/use_type.md new file mode 100644 index 00000000..965984b7 --- /dev/null +++ b/docs/implementation/asts/attributes/use_type.md @@ -0,0 +1,87 @@ +# `#[use_type]` — the AST stack + +`#[use_type(HasErrorType.Error)]` imports an abstract associated type: it rewrites the bare alias (`Error`) everywhere in the host's signatures into its fully-qualified `::Error` form, and adds the owning trait as a bound so those paths are well-formed. It is the richest of the attribute modifiers, with a small stack of AST types and a three-step transform driven off them; this page covers that stack and the shared collection mechanism lives in the [attribute-modifier overview](README.md). For the user-facing syntax and expansion, read the reference document [reference/attributes/use_type.md](../../../reference/attributes/use_type.md). + +## The stack at a glance + +An import parses into a `UseTypeAttribute` per spec, each carrying one or more `UseTypeIdent` entries, and the specs a host collects are held together in a `UseTypeAttributes`. Application then runs in three steps off `UseTypeAttributes`: **ground** each spec's context, **substitute** every bare alias in one traversal with the `SubstituteAbstractTypes` visitor, and **add the bounds** to the trait or impl. The type-equality (`= T`) pins are derived separately by `derive_use_type_predicates`. The sections below follow the data through those types in order. + +## `UseTypeIdent` + +`UseTypeIdent` is one imported associated type within a spec. It captures three pieces: the `type_ident` (the associated type's name, e.g. `Error`), an optional `as_alias` (the `as NewName` rename), and an optional `equals` (the `= ConcreteType` pin). Its `alias_ident()` returns the alias when one is written and the type identifier otherwise — this is the name that appears bare in the host's signatures and that the substitution matches against. Parsing reads the identifier, then optionally `as `, then optionally `= `, so the grammar is `Ident (as Ident)? (= Type)?`. + +## `UseTypeAttribute` + +`UseTypeAttribute` is one import spec — a trait path, the types imported from it, and the context they are projected against. It holds a `context_type` (the type whose associated type is imported, defaulting to `Self` or set by a trailing `in Context`), the `trait_path` (a `PathWithTypeArgs`, so the owning trait may be a full path or carry generic arguments such as `HasFooType`), and a `Vec` of the types imported from that trait. + +Its parser is where the `.`-versus-`::` separation is decided. It parses the trait path, consumes a `.`, and reads either a single `UseTypeIdent` or a brace-delimited comma list of them (`HasTypes.{A, B as C}`); it then reads an optional `in Context` suffix, parsing `Context` as a `PathWithTypeArgs`, with no suffix defaulting the context to `Self`. The `.` — not `::` — is what separates the trait from the associated type, so a trait that is itself a path keeps its `::` inside `trait_path` while the `.` unambiguously marks where the associated type begins; the `in` keyword, reserved in Rust, marks the context clause just as cleanly since it can never appear inside a type or path: + +```rust +// #[use_type(foo::bar::HasScalarType.Scalar in Context)] +// └────trait_path────┘ └assoc┘ └─ctx─┘ +``` + +`UseTypeAttribute` also carries `replace_ident`, the per-spec lookup the substitution visitor calls: given an identifier, it returns the fully-qualified replacement identifier if the identifier matches one of the spec's `alias_ident()`s, and — importantly — **stamps the user's original span onto the replacement**. Preserving the span is what makes a caret on a mistyped imported type point at the identifier the user wrote rather than at the whole macro block. + +## `UseTypeAttributes` and the three-step transform + +`UseTypeAttributes` holds the `Vec` a host collected and owns the transform that applies them. It exposes two entry points — `transform_item_trait` for a generated trait and `transform_item_impl` for a generated impl — and both run the same three steps, differing only in the bounds they add at the end. Each first calls `forbid_duplicate_aliases` and returns early when there are no specs. + +**Step one — grounding.** `grounded_specs` resolves each spec's context type up front so the later steps agree on one fully-qualified context. An `in Context` whose `Context` is itself imported by another spec — as in `HasTypes.Types, HasScalarType.Scalar in Types` — is rewritten from the bare alias `Types` into `::Types`. Contexts that name a real generic parameter or `Self` are left alone. The pass iterates to a fixpoint, running the substitution visitor over each spec's `context_type` and stopping when a pass makes no change; because each pass grounds one more level, `attributes.len()` passes cover any acyclic chain (so `HasA.A, HasB.B in A, HasC.C in B` grounds `B` to `<::A as HasB>::B`), and a cyclic reference simply stops making progress rather than looping — it surfaces later as an ordinary unresolved-type error. + +**Step two — substitution.** A single `SubstituteAbstractTypes` traversal, holding *every* grounded spec at once, rewrites each bare use of an alias into its fully-qualified associated type: + +```rust +// #[use_type(HasErrorType.Error)] turns a bare `Error` into: +::Error +``` + +Grounding the contexts up front is what lets one pass suffice: the replacement a spec emits already contains no bare alias, so the visitor never revisits its own output, and because aliases are unique (guaranteed by `forbid_duplicate_aliases`) the order among specs is irrelevant. The visitor's matching rules are covered under [SubstituteAbstractTypes](#substituteabstracttypes) below. + +**Step three — adding the bounds.** This is where the two entry points diverge: + +- `transform_item_trait` pushes each `Self`-context spec's trait path onto the consumer trait's *supertraits*, so the abstract type is available to every signature. For a foreign `in Context` spec it instead adds a plain `Context: Trait` predicate to the trait's `where` clause, so the substituted `::Assoc` signatures are well-formed without the author declaring the bound. The type-equality (`= T`) form is an impl-side pin and is deliberately *not* added to a trait here. +- `transform_item_impl` derives the impl-side `where` predicates through `derive_use_type_predicates` and extends the impl's `where` clause with them. These carry the `= T` equality pins as associated-type bindings; the pins are impl-side only and never reach the trait. + +## `derive_use_type_predicates` and the equality pins + +`derive_use_type_predicates` (in `type_predicates.rs`) turns a set of grounded specs into the impl-side `where` predicates they contribute — one `context_type: trait_path` bound per spec, carrying any `= T` pins as associated-type bindings inside the trait path (`Context: Trait`). It reads each spec's already-grounded `context_type` directly rather than re-resolving aliases, which is why grounding must run first. + +The pins can also **unify two imported abstract types**. `find_type_equality` handles the case where an equality's right-hand side names *another* imported alias: given `HasPasswordType.Password, HasHashedPasswordType.{HashedPassword = Password}`, it recognizes that `Password` is itself an imported alias and rewrites the pin's target into the other spec's fully-qualified projection (`::Password`), so the two abstract types are constrained equal. When the right-hand side is an ordinary concrete type it is used as written. + +Two more functions in this module round out the impl-side logic. `forbid_duplicate_aliases` rejects any two imports that resolve to the same identifier or alias: it flattens every `UseTypeIdent` across all specs into one list and compares every pair by `alias_ident()`, so the check catches a collision across separate specs *and* within a single braced list, uniformly for components, impls, and functions. A shared alias would make the substitution silently pick the first match and drop the rest, so it must be an error. + +## `SubstituteAbstractTypes` + +`SubstituteAbstractTypes` (in [visitors/substitute_abstract_type.rs](../../../../crates/macros/cgp-macro-core/src/visitors/substitute_abstract_type.rs)) is the `VisitMut` that performs the bare-alias rewrite in a single traversal. Holding every spec at once — rather than running one visitor per spec — is what lets one pass over the item handle all imports regardless of the order they were written. It rewrites a type only when the type is a bare, single-segment, argument-free path (`qself: None`, no leading colon, one segment, `PathArguments::None`) whose identifier matches a spec's `alias_ident()`; it then replaces the type with `::replacement_ident` and records `is_changed`. The strict match guard is deliberate: a path that already carries a qualifier, arguments, or more than one segment is not a bare alias and is left untouched, so a genuine `Self::Error` or a generic `Foo` is not disturbed. `is_changed` is what the grounding fixpoint reads to decide whether a further pass would be a no-op. + +## Behavior and corner cases + +**A `=` equality is rejected outright on `#[cgp_component]`.** `CgpComponentAttributes::parse` scans each imported `UseTypeIdent` for an `equals` and returns a spanned "Type equality constraints cannot be used in component trait definition" error, because a component *definition* cannot pin an abstract type to a concrete one — the pin belongs on an impl. The impl and function collectors accept the equality form. + +**A foreign `in Context` bound reaches the trait.** On a component or function trait, `transform_item_trait` adds the `Context: Trait` predicate to the trait's `where` clause for a foreign spec. Without this the constraint would be silently dropped, leaving a signature that only compiles when the author happens to supply the bound elsewhere; adding it is what makes the substituted `::Assoc` paths well-formed by construction. + +**Nested-import order does not matter, but a cycle has no order.** Grounding runs a fixpoint over *all* specs at once, so a spec's `in Context` may name an alias imported by any other spec regardless of where it sits in the list — `HasC.C in B, HasB.B in A, HasA.A` grounds exactly like the front-to-back `HasA.A, HasB.B in A, HasC.C in B`. The one arrangement with no valid order is a cycle, where two contexts resolve through each other (`HasA.A in B, HasB.B in A`). The fixpoint stops after `attributes.len()` passes rather than looping, so the cyclic aliases are never grounded and stay bare in the emitted types; the compiler then reports `E0425` "cannot find type" at the offending `in` alias. CGP could in principle detect the cycle locally and reject it at macro time, but currently lowers it faithfully and defers to the compiler. + +## Tests + +The behavioral tests span every host and every form the attribute accepts: + +- [abstract_types/use_type_component.rs](../../../../crates/tests/cgp-tests/tests/abstract_types/use_type_component.rs) covers the `#[cgp_component]` supertrait form; [use_type_foreign.rs](../../../../crates/tests/cgp-tests/tests/abstract_types/use_type_foreign.rs) the `in Context` foreign form on a component (with an *unbounded* generic parameter, guarding that the foreign bound reaches the trait); and [use_type_auto_getter.rs](../../../../crates/tests/cgp-tests/tests/abstract_types/use_type_auto_getter.rs) that a getter macro (`#[cgp_auto_getter]`) accepts `#[use_type]` through the same collector. +- [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 (`in`) forms on `#[cgp_fn]` — the last also using an unbounded generic parameter. +- [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), [use_type_fn_foreign_equality_cross_trait.rs](../../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign_equality_cross_trait.rs), and [use_type_fn_nested_foreign.rs](../../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_nested_foreign.rs) cover cross-spec and nested-foreign equality (the last combining a nested-foreign import with `#[extend_where]`); [use_type_fn_deep_foreign.rs](../../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_deep_foreign.rs) pins a three-hop foreign chain front-to-back, and [use_type_fn_reverse_order.rs](../../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_reverse_order.rs) writes the same chain back-to-front and asserts a value flows through it at runtime — together they exercise the transitive context grounding and pin its order-independence. +- [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`). + +The rejection cases are pinned in [parser_rejections/use_type.rs](../../../../crates/tests/cgp-macro-tests/tests/parser_rejections/use_type.rs): a `=` equality on a component, and a duplicate identifier or alias across specs, within one braced list, and on a component. + +Four acceptable compile-fail fixtures pin the post-codegen failures the attribute defers to the compiler: + +- [acceptable/cgp_component/use_type_foreign_unsatisfied.rs](../../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_component/use_type_foreign_unsatisfied.rs) (a foreign `in Types` bound *enforced* on the trait rather than dropped) and [acceptable/cgp_fn/use_type_nested_unsatisfied.rs](../../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_nested_unsatisfied.rs) (the same at a nested two-hop depth) are both the [check-trait-failure](../../../errors/checks/check-trait-failure.md) class. +- [acceptable/cgp_fn/use_type_unknown_assoc.rs](../../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_unknown_assoc.rs) (a misnamed imported associated type lowered into an unresolvable path, whose caret confirms the substitution preserves the user's identifier span) is the [unresolved-imported-type](../../../errors/lowering/unresolved-imported-type.md) class. +- [acceptable/cgp_fn/use_type_cyclic_context.rs](../../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_fn/use_type_cyclic_context.rs) (two `in Context` clauses that reference each other, so grounding never resolves either context and leaves the bare aliases in type position) surfaces as `E0425` "cannot find type" with the caret on the unresolved `in` alias — the sibling of the unresolved-imported-type class, but on the *context* rather than the associated-type name. + +## Source + +- The `use_type/` submodule in [cgp-macro-core/src/types/attributes/use_type/](../../../../crates/macros/cgp-macro-core/src/types/attributes/use_type/): `attribute.rs` (`UseTypeAttribute` and its parser), `ident.rs` (`UseTypeIdent`), `attributes.rs` (`UseTypeAttributes`, `grounded_specs`, and the two `transform_item_*` entry points), and `type_predicates.rs` (`derive_use_type_predicates`, `forbid_duplicate_aliases`, and the equality-unification helpers). +- The substitution visitor is `SubstituteAbstractTypes` in [cgp-macro-core/src/visitors/substitute_abstract_type.rs](../../../../crates/macros/cgp-macro-core/src/visitors/substitute_abstract_type.rs). +- The hosts that drive it: [entrypoints/cgp_component.md](../../entrypoints/cgp_component.md), [entrypoints/cgp_impl.md](../../entrypoints/cgp_impl.md), and [entrypoints/cgp_fn.md](../../entrypoints/cgp_fn.md). diff --git a/docs/implementation/asts/attributes/uses.md b/docs/implementation/asts/attributes/uses.md new file mode 100644 index 00000000..7df65fd0 --- /dev/null +++ b/docs/implementation/asts/attributes/uses.md @@ -0,0 +1,34 @@ +# `#[uses]` — the AST stack + +`#[uses(TraitA, TraitB)]` imports `Self` trait bounds onto a provider's generated impl, reading like a `use` statement that lists the capabilities the provider depends on. It is a modifier attribute collected by a host macro rather than a standalone macro; this page covers its AST type and what it injects, and the shared collection mechanism lives in the [attribute-modifier overview](README.md). For the user-facing syntax and expansion, read the reference document [reference/attributes/uses.md](../../../reference/attributes/uses.md). + +## `UsesAttributes` + +The attribute parses into `UsesAttributes`, which holds a single `Vec` — one bound per imported capability. Each entry is a full [`syn::TypeParamBound`](https://docs.rs/syn/latest/syn/enum.TypeParamBound.html), so the parser accepts any bound a `where` clause accepts, not only the idiomatic `Trait` form: an associated-type-equality binding (`HasErrorType`), a higher-ranked bound (`for<'a> Trait<'a>`), or a lifetime bound all parse. The plain `Trait` form is the one to prefer, and the equality form is better expressed through [`#[use_type]`](use_type.md) when the trait is an abstract-type component; the wider grammar is simply what a `TypeParamBound` permits. + +The type is deliberately thin — it carries the bounds and hands them back through its `ToTypeParamBounds` impl, whose `to_type_param_bounds` clones the imports into a `Punctuated`. The host is what appends them; `UsesAttributes` only holds and yields them. + +## What the hosts inject + +`#[uses]` is accepted on `#[cgp_impl]` and `#[cgp_fn]`, and both land the bounds on the generated impl's `where` clause — on `Self` — never on the consumer trait, which is what keeps the dependency hidden from callers. The two hosts store the parsed bounds slightly differently, but the effect is the same: + +- On **`#[cgp_impl]`**, the bounds are parsed into the `UsesAttributes` held by `CgpImplAttributes` (its `uses.imports` field). `ItemCgpImpl::lower` adds them to the provider impl's `where` clause as `Self`-keyed predicates, alongside the bounds contributed by `#[use_type]` and `#[use_provider]`. +- On **`#[cgp_fn]`**, the bounds are parsed straight into a `Vec` field on `FunctionAttributes` (a field that mirrors `#[extend]`'s), and `preprocess` pushes each as a `Self: Trait` predicate onto the impl only. + +The contrast with [`#[extend]`](extend.md) is the crux and worth holding in mind: `#[uses]` adds an impl-side `Self` bound that the trait interface does not expose, whereas `#[extend]` makes its bound a *supertrait* of the generated trait, visible to every caller. `#[uses]` is therefore the way to declare an impl-side dependency, and `#[extend]` the way to widen the interface. + +## Tests + +The behavioral tests exercise `#[uses]` on both hosts and alongside generics: + +- [impl_side_dependencies/fn_uses.rs](../../../../crates/tests/cgp-tests/tests/impl_side_dependencies/fn_uses.rs) pins the `#[cgp_fn]` form — a `Self` trait bound imported as an impl-side dependency. +- [impl_side_dependencies/impl_uses.rs](../../../../crates/tests/cgp-tests/tests/impl_side_dependencies/impl_uses.rs) pins 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. +- [impl_side_dependencies/fn_uses_associated_type.rs](../../../../crates/tests/cgp-tests/tests/impl_side_dependencies/fn_uses_associated_type.rs) pins the associated-type-equality bound (`HasErrorType`) that `#[uses]` also accepts, on the `#[cgp_fn]` form. +- [impl_side_dependencies/impl_uses_associated_type.rs](../../../../crates/tests/cgp-tests/tests/impl_side_dependencies/impl_uses_associated_type.rs) exercises the same equality bound end-to-end on the `#[cgp_impl]` form. + +## Source + +- `UsesAttributes` and its `ToTypeParamBounds` impl are in [cgp-macro-core/src/types/attributes/uses.rs](../../../../crates/macros/cgp-macro-core/src/types/attributes/uses.rs). +- The `#[cgp_impl]` collector is `CgpImplAttributes` in [cgp_impl_attributes.rs](../../../../crates/macros/cgp-macro-core/src/types/attributes/cgp_impl_attributes.rs); the `#[cgp_fn]` collector is `FunctionAttributes` in [function.rs](../../../../crates/macros/cgp-macro-core/src/types/attributes/function.rs). +- The hosts that drive it: [entrypoints/cgp_impl.md](../../entrypoints/cgp_impl.md) and [entrypoints/cgp_fn.md](../../entrypoints/cgp_fn.md). diff --git a/docs/implementation/entrypoints/cgp_fn.md b/docs/implementation/entrypoints/cgp_fn.md index 490cfe3d..06d3038e 100644 --- a/docs/implementation/entrypoints/cgp_fn.md +++ b/docs/implementation/entrypoints/cgp_fn.md @@ -89,9 +89,7 @@ Every `snapshot_cgp_fn!` invocation across the suite is indexed here, since thes - [impl_side_dependencies/fn_extend.rs](../../../crates/tests/cgp-tests/tests/impl_side_dependencies/fn_extend.rs) — `#[extend(...)]` adding a supertrait bound to the generated trait. - [impl_side_dependencies/fn_uses.rs](../../../crates/tests/cgp-tests/tests/impl_side_dependencies/fn_uses.rs) — `#[uses(...)]` importing a `Self` trait bound as an impl-side dependency. - [higher_order_providers/use_provider_fn.rs](../../../crates/tests/cgp-tests/tests/higher_order_providers/use_provider_fn.rs) — `#[use_provider]` on a `#[cgp_fn]`, borrowing another provider's behavior. -- [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), [use_type_fn_extend.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_extend.rs), [use_type_fn_foreign.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign.rs), [use_type_fn_foreign_equality.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign_equality.rs), [use_type_fn_nested_foreign.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_nested_foreign.rs), [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_cross_trait.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign_equality_cross_trait.rs) — the `#[use_type]` variants (local alias, type-equality bound, `#[extend]` form, foreign generic parameter, and their combinations). - -One variant has no snapshot yet: the `#[extend_where(...)]` attribute is exercised by no `snapshot_cgp_fn!` invocation, so its effect on the trait's own `where` clause is unpinned. +- [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), [use_type_fn_extend.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_extend.rs), [use_type_fn_foreign.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign.rs), [use_type_fn_foreign_equality.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign_equality.rs), [use_type_fn_nested_foreign.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_nested_foreign.rs), [use_type_fn_deep_foreign.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_deep_foreign.rs), [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_cross_trait.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_foreign_equality_cross_trait.rs) — the `#[use_type]` variants (local alias, type-equality bound, `#[extend]` form, foreign generic parameter, a three-hop foreign chain, and their combinations). The `#[extend_where(...)]` attribute's effect on the trait's own `where` clause is pinned by [use_type_fn_nested_foreign.rs](../../../crates/tests/cgp-tests/tests/abstract_types/use_type_fn_nested_foreign.rs), where it adds a `Scalar: Copy` bound (rewritten to the two-hop path) alongside the `#[use_type]` imports. ## Tests diff --git a/docs/implementation/entrypoints/cgp_namespace.md b/docs/implementation/entrypoints/cgp_namespace.md index 3ca6aa27..fe67026d 100644 --- a/docs/implementation/entrypoints/cgp_namespace.md +++ b/docs/implementation/entrypoints/cgp_namespace.md @@ -77,9 +77,9 @@ Each generated namespace impl is re-spanned onto the entry that produced it, so Some namespace input is accepted by the macro but fails to compile downstream, deferred to the compiler because it lacks the whole-program view the check needs. Each is intended behavior, not a bug, and its full anatomy lives in the [error catalog](../../errors/README.md); the [Error spans](#error-spans) section below covers how each caret is aimed at the offending entry. -A **duplicate key** (the same key mapped twice in one `cgp_namespace!` block), **overriding a path the joined namespace already registers**, and a **bare-key `for` loop alongside a `namespace` join** all produce `E0119` — the [conflicting wiring](../../errors/wiring/conflicting-wiring.md) error class. The override case is the subtle one: a `namespace N;` header emits a blanket `impl DelegateComponent for Ctx where Key: N` that covers every path `N` resolves, so a direct `@path: Provider` entry for a path `N` also registers overlaps with it. To keep such an override, target a path the namespace routes *to* but does not itself terminate, so the context supplies the leaf without the two impls overlapping. A `for in Table { Key: Value }` loop that wires the bound key *bare* rather than inside a path emits a second blanket `DelegateComponent` impl that overlaps the `namespace` header's blanket impl — which is why a loop key must sit inside a path. +Three distinct namespace mistakes all surface as `E0119`, and the catalog splits them by usage rather than by the shared error code. A **duplicate key** — the same key mapped twice in one `cgp_namespace!` block — is a plain [conflicting wiring](../../errors/wiring/conflicting-wiring.md). **Overriding a key a namespace already claims** — a context wiring a path the joined namespace registers, or a child namespace redefining a key it inherits — is the [namespace override conflict](../../errors/wiring/namespace-override-conflict.md): a `namespace N;` header emits a blanket `impl DelegateComponent for Ctx where Key: N` covering every path `N` resolves, and the inheritance impl covers every key the parent binds, so a specific entry for a claimed key overlaps one of them; to override, target a path the namespace routes *to* but does not terminate. **Emitting two blanket forwardings** — joining two namespaces, or a bare-key `for in Table { Key: Value }` loop alongside a `namespace` join — is the [overlapping namespace forwarding](../../errors/wiring/namespace-forwarding-conflict.md): each covers every key, so the two overlap, which is why a context joins one namespace and a loop key must sit inside a path. -**Registering a default for a prefixed component into a namespace the crate does not own** fails the orphan rule (`E0210`) — the [orphan-rule violation](../../errors/wiring/orphan-rule.md) error class. `#[default_impl(@path in Namespace)]` on a prefixed component expands to `impl Namespace<_> for PathCons<..>`, an impl of a foreign trait for a fully foreign type, so a per-component default keyed on a prefix path is confined to the namespace's own crate; the orphan-safe alternative is a *local* component key. +**Registering into a namespace the crate does not own, keyed on a type it does not own**, fails the orphan rule (`E0210`) — the [orphan-rule violation](../../errors/wiring/orphan-rule.md) error class. This covers a `#[default_impl]` on a *prefixed* component (`impl Namespace<_> for PathCons<..>`, offending parameter `__Components__`), a `#[default_impl]` keyed on a foreign *unprefixed* component marker (`impl Namespace<_> for GreeterComponent`), and a `cgp_namespace!` block *without* `new` re-opening a foreign namespace (`impl ForeignNamespace<_> for Key`, offending parameter `__Table__`, caret on the whole block) — each an impl of a foreign trait for a foreign type. The orphan-safe alternatives are owning either end: register from the namespace's crate, key on a local component, use a namespace body entry, or inherit the namespace into a new local one. **Joining a namespace that routes a component to a path no entry binds** produces an `E0277` when the wiring is checked — the [unregistered namespace path](../../errors/checks/unregistered-namespace-path.md) error class. A `#[prefix]` registers the *routing* (the namespace resolves the component to a `RedirectLookup` along the path) but not a provider at the path's leaf; if nothing (a `#[default_impl]`, a body entry, or a direct `@path:` line) ever binds one, the `RedirectLookup` finds no `DelegateComponent` entry and the lookup fails. This is a *lookup* failure, distinct from an unsatisfied *dependency*. @@ -107,7 +107,7 @@ The behavioral tests confirm the generated namespaces wire correctly: - [namespaces/default_impls_wiring.rs](../../../crates/tests/cgp-tests/tests/namespaces/default_impls_wiring.rs) checks that a `DefaultNamespace`-based namespace supplies defaults a context can override. - [namespaces/multi_param_namespace.rs](../../../crates/tests/cgp-tests/tests/namespaces/multi_param_namespace.rs) and [namespaces/prefix_default_namespace.rs](../../../crates/tests/cgp-tests/tests/namespaces/prefix_default_namespace.rs) exercise parameterized namespaces and the `#[prefix(...)]` join that attaches a component to one. - [namespaces/for_where_clause.rs](../../../crates/tests/cgp-tests/tests/namespaces/for_where_clause.rs) pins the `for <..> in .. where ..` loop's `where` clause landing on each generated impl (a `snapshot_delegate_components!`, since `delegate_components!` owns the `for`-loop form). -- [namespaces/default_impl_use_type.rs](../../../crates/tests/cgp-tests/tests/namespaces/default_impl_use_type.rs) registers a provider with a `#[use_type]` abstract-type dependency into a namespace via `#[default_impl]` and resolves it through a context that joins the namespace. Its `snapshot_cgp_impl!` pins that the namespace-registration impl carries no `where` clause — the provider's impl-side bounds must not leak onto the registration impl's `Self` (the path key), or the abstract-type dependency would be demanded of the path rather than the context. See [asts/attributes.md](../asts/attributes.md) for the mechanism. +- [namespaces/default_impl_use_type.rs](../../../crates/tests/cgp-tests/tests/namespaces/default_impl_use_type.rs) registers a provider with a `#[use_type]` abstract-type dependency into a namespace via `#[default_impl]` and resolves it through a context that joins the namespace. Its `snapshot_cgp_impl!` pins that the namespace-registration impl carries no `where` clause — the provider's impl-side bounds must not leak onto the registration impl's `Self` (the path key), or the abstract-type dependency would be demanded of the path rather than the context. See [asts/attributes/default_impl.md](../asts/attributes/default_impl.md) for the mechanism. The cross-crate coverage in `cgp-test-crate-b` confirms the orphan-safe direction of the default-impl coherence rule: @@ -120,9 +120,13 @@ The rejection cases in `cgp-macro-tests` pin the attribute rejection: The compile-fail fixtures in `cgp-compile-fail-tests` pin accepted expansions that fail to compile — **acceptable** failures deferred to the compiler, documented in the [error catalog](../../errors/README.md) (the [Failure modes](#failure-modes) section links each to its class): - [acceptable/cgp_namespace/duplicate_path_key.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/duplicate_path_key.rs) — two identical `@`-path prefix-rewrite entries conflict (`E0119`); its `.stderr` pins the [error span](#error-spans) landing on the duplicated path leaf rather than the whole block, even though the key lowers to a synthesized `PathCons<..>` type. -- [acceptable/cgp_namespace/override_registered_path.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/override_registered_path.rs) — a context joins a namespace that registers a path (via `#[default_impl]`) and also wires that path directly, so the `namespace` blanket impl and the direct entry both implement `DelegateComponent` for the path (`E0119`). -- [acceptable/cgp_namespace/default_impl_foreign_prefix_path.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_prefix_path.rs) — a downstream crate registers a default for an upstream *prefixed* component into the upstream namespace, whose `impl Namespace<_> for PathCons<..>` is wholly foreign and violates the orphan rule (`E0210`); depends on `cgp-test-crate-a` for the upstream component and namespace. -- [acceptable/cgp_namespace/for_loop_bare_key.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/for_loop_bare_key.rs) — a bare-key `for` loop alongside a `namespace` join, whose two blanket `DelegateComponent`/`IsProviderFor` impls overlap (`E0119`, fully-generic carets, no downstream note); the blanket-versus-blanket shape of [conflicting wiring](../../errors/wiring/conflicting-wiring.md). +- [acceptable/cgp_namespace/override_registered_path.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/override_registered_path.rs) — a context joins a namespace that registers a path (via `#[default_impl]`) and also wires that path directly, so the `namespace` blanket impl and the direct entry both implement `DelegateComponent` for the path (`E0119`, with the `downstream crates may implement` note); the context-level shape of the [namespace override conflict](../../errors/wiring/namespace-override-conflict.md) class. +- [acceptable/cgp_namespace/inherited_override_conflict.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/inherited_override_conflict.rs) — a child namespace that redefines a key its parent binds, so the inheritance blanket impl and the child's specific entry overlap (a *single* `E0119` on `ChildNs<_>` for the component marker, no note); the namespace-level shape of the [namespace override conflict](../../errors/wiring/namespace-override-conflict.md) class. +- [acceptable/cgp_namespace/for_loop_bare_key.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/for_loop_bare_key.rs) — a bare-key `for` loop alongside a `namespace` join, whose two blanket `DelegateComponent`/`IsProviderFor` impls overlap (`E0119`, fully-generic carets, no downstream note); the [overlapping namespace forwarding](../../errors/wiring/namespace-forwarding-conflict.md) class. +- [acceptable/cgp_namespace/two_namespaces_joined.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/two_namespaces_joined.rs) — two `namespace` joins on one context, each emitting a blanket forwarding impl over every key, which overlap (`E0119`, fully-generic carets); the same [overlapping namespace forwarding](../../errors/wiring/namespace-forwarding-conflict.md) class. +- [acceptable/cgp_namespace/default_impl_foreign_prefix_path.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_prefix_path.rs) — a downstream crate registers a default for an upstream *prefixed* component into the upstream namespace, whose `impl Namespace<_> for PathCons<..>` is wholly foreign and violates the orphan rule (`E0210` on `__Components__`); the [orphan-rule violation](../../errors/wiring/orphan-rule.md) class. Depends on `cgp-test-crate-a` for the upstream component and namespace. +- [acceptable/cgp_namespace/default_impl_foreign_component.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/default_impl_foreign_component.rs) — the same orphan keyed on a foreign *unprefixed* component marker rather than a path (`E0210` on `__Components__`); shows the restriction is not specific to prefixes. Same [orphan-rule violation](../../errors/wiring/orphan-rule.md) class. +- [acceptable/cgp_namespace/reopen_foreign_namespace.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/reopen_foreign_namespace.rs) — a `cgp_namespace!` block without `new` re-opening a foreign namespace, whose entry impl is foreign (`E0210` on `__Table__`, caret on the whole block); same [orphan-rule violation](../../errors/wiring/orphan-rule.md) class. - [acceptable/cgp_namespace/unregistered_prefix_path.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/unregistered_prefix_path.rs) — a `#[prefix]`-ed component joined through its namespace with no provider bound at its path, so a `check_components!` reports the redirect target's `DefaultNamespace`/`DelegateComponent` lookup as unsatisfied (`E0277`); the [unregistered namespace path](../../errors/checks/unregistered-namespace-path.md) class. - [acceptable/cgp_namespace/inheritance_cycle.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/cgp_namespace/inheritance_cycle.rs) — two mutually-inheriting namespaces whose inheritance impls' `where` clauses loop, overflowing (`E0275`) eagerly at both `cgp_namespace!` definitions; the [namespace inheritance cycle](../../errors/wiring/namespace-inheritance-cycle.md) class. diff --git a/docs/implementation/entrypoints/check_components.md b/docs/implementation/entrypoints/check_components.md index 706e0173..c247e7a0 100644 --- a/docs/implementation/entrypoints/check_components.md +++ b/docs/implementation/entrypoints/check_components.md @@ -76,6 +76,7 @@ The behavioral coverage for `check_components!` is the compile-time assertion it - The files listed under Snapshots are compile-only tests, so a successful build is the passing check. Each pins both the expansion (via the snapshot) and the fact that the asserted wiring resolves. - [parser_rejections/check_components.rs](../../../crates/tests/cgp-macro-tests/tests/parser_rejections/check_components.rs) — the table-level attribute rejections: an empty `#[check_providers()]`, a repeated `#[check_providers]`, a repeated `#[check_trait]`, and an unknown attribute each fail to parse. - [cgp-compile-fail-tests acceptable/check_components/missing_dependency.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/missing_dependency.rs) pins that an unsatisfied impl-side dependency is reported with its `E0277` caret on the checked component inside the block — a regression test for the `override_span` re-span (see [Failure modes](#failure-modes)). Its error anatomy is the [check-trait failure](../../errors/checks/check-trait-failure.md) class. +- [cgp-compile-fail-tests acceptable/check_components/ordinary_bound_unsatisfied.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/ordinary_bound_unsatisfied.rs) and [acceptable/check_components/generic_context_ordinary_bound.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/check_components/generic_context_ordinary_bound.rs) pin a check surfacing an *ordinary* trait bound (`Scalar: Eq`, unmet by a wired `f64`) as the primary `E0277`, from a concrete context and from a generic ` Wrapper` table checked at `Wrapper` respectively. Their error anatomy is the [unsatisfied ordinary trait bound](../../errors/checks/ordinary-trait-bound.md) class. ## Source diff --git a/docs/implementation/entrypoints/delegate_components.md b/docs/implementation/entrypoints/delegate_components.md index 0e0bcf0e..d4a4d0d0 100644 --- a/docs/implementation/entrypoints/delegate_components.md +++ b/docs/implementation/entrypoints/delegate_components.md @@ -133,6 +133,7 @@ The compile-fail fixtures in `cgp-compile-fail-tests` pin the expansions that fa - [acceptable/delegate_components/duplicate_open_key.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/duplicate_open_key.rs) — an `open` header colliding with an explicit mapping for the same component; its `.stderr` pins the [error span](#error-spans) of the `open`-header entry, whose span comes from the opened component (a source distinct from the plain key path). - [acceptable/delegate_components/overlapping_generic.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/overlapping_generic.rs) — a generic ` Wrapper` entry overlaps a specific `Wrapper` entry at the same key (`E0119`). - [acceptable/delegate_components/missing_dependency.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/missing_dependency.rs) — a lazily-wired provider whose `Self: HasName` dependency the context does not satisfy; the unmet bound surfaces at the call site (`E0599`). Its anatomy is the [hidden unsatisfied-dependency](../../errors/hidden/unsatisfied-dependency.md) error class. +- [acceptable/delegate_components/ordinary_bound_unsatisfied.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/ordinary_bound_unsatisfied.rs) — the same hidden `E0599` where the suppressed dependency is an *ordinary* trait bound (`Scalar: Eq`, unmet by a wired `f64`) rather than a `HasField`, called rather than checked. Same [hidden unsatisfied-dependency](../../errors/hidden/unsatisfied-dependency.md) class; its surfaced counterpart is the [unsatisfied ordinary trait bound](../../errors/checks/ordinary-trait-bound.md) class. - [acceptable/delegate_components/unconstrained_generic.rs](../../../crates/tests/cgp-compile-fail-tests/tests/acceptable/delegate_components/unconstrained_generic.rs) — a per-entry generic that appears only in the value (` GreeterComponent: GreetWith`) lowers to an impl with an unconstrained `T` (`E0207`), which the compiler rejects as it would a hand-written impl. ## Source diff --git a/docs/reference/attributes/derive_delegate.md b/docs/reference/attributes/derive_delegate.md index ffd7c69b..1993af20 100644 --- a/docs/reference/attributes/derive_delegate.md +++ b/docs/reference/attributes/derive_delegate.md @@ -151,4 +151,4 @@ Now `MyApp` implements `CanCalculateArea` through `RectangleArea` and - Dispatcher impl: built by the same file's `to_provider_impl`, which appends `__Components__` and `__Delegate__` generics, emits the `DelegateComponent<(params), Delegate = __Delegate__>` and `__Delegate__: ProviderTrait` bounds, and forwards each method through `trait_items_to_delegated_impl_items`. - Collection and emission: the attribute is collected in `types/attributes/cgp_component_attributes.rs` and emitted by `to_use_delegate_impls` in [crates/macros/cgp-macro-core/src/types/cgp_component/evaluated/item.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_component/evaluated/item.rs). - `UseDelegate` provider (and a worked single-key expansion): [crates/core/cgp-component/src/providers/use_delegate.rs](../../../crates/core/cgp-component/src/providers/use_delegate.rs); the multi-attribute form with a custom `UseInputDelegate` is used in [crates/extra/cgp-handler/src/components/computer.rs](../../../crates/extra/cgp-handler/src/components/computer.rs). -- Implementation document (the dispatcher impl it generates and the index of tests and snapshots): [implementation/asts/attributes.md](../../implementation/asts/attributes.md). +- Implementation document (the dispatcher impl it generates and the index of tests and snapshots): [implementation/asts/attributes/derive_delegate.md](../../implementation/asts/attributes/derive_delegate.md). diff --git a/docs/reference/attributes/extend.md b/docs/reference/attributes/extend.md index bb5fddc7..93bb36aa 100644 --- a/docs/reference/attributes/extend.md +++ b/docs/reference/attributes/extend.md @@ -124,4 +124,4 @@ Because `HasScalarType` is a supertrait of `RectangleArea`, the abstract `Self:: - Parsing: `#[extend(...)]` is parsed in [crates/macros/cgp-macro-core/src/types/attributes/function.rs](../../../crates/macros/cgp-macro-core/src/types/attributes/function.rs) (the `extend` field of `FunctionAttributes`). - For `#[cgp_fn]`: the bounds are added to the trait's supertraits and to the impl `where` clause in [crates/macros/cgp-macro-core/src/types/cgp_fn/preprocessed.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_fn/preprocessed.rs). - For `#[cgp_component]`: the attribute is parsed by `CgpComponentAttributes::parse` and its bounds appended to the consumer trait's supertraits in [crates/macros/cgp-macro-core/src/types/attributes/cgp_component_attributes.rs](../../../crates/macros/cgp-macro-core/src/types/attributes/cgp_component_attributes.rs). -- Implementation document (what the attribute injects into each host and the index of tests and snapshots): [implementation/asts/attributes.md](../../implementation/asts/attributes.md). +- Implementation document (what the attribute injects into each host and the index of tests and snapshots): [implementation/asts/attributes/extend.md](../../implementation/asts/attributes/extend.md). diff --git a/docs/reference/attributes/extend_where.md b/docs/reference/attributes/extend_where.md index a42bf4fb..af97b7c9 100644 --- a/docs/reference/attributes/extend_where.md +++ b/docs/reference/attributes/extend_where.md @@ -64,4 +64,4 @@ The `Scalar: Mul` bound, written in the function body, stays as - Parsing: `#[extend_where(...)]` is parsed in [crates/macros/cgp-macro-core/src/types/attributes/function.rs](../../../crates/macros/cgp-macro-core/src/types/attributes/function.rs) (the `extend_where` field of `FunctionAttributes`). - Injection: its predicates are added to both the trait and impl `where` clauses in [crates/macros/cgp-macro-core/src/types/cgp_fn/preprocessed.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_fn/preprocessed.rs). -- Implementation document (what the attribute injects into its host and the index of tests and snapshots): [implementation/asts/attributes.md](../../implementation/asts/attributes.md). +- Implementation document (what the attribute injects into its host and the index of tests and snapshots): [implementation/asts/attributes/extend_where.md](../../implementation/asts/attributes/extend_where.md). diff --git a/docs/reference/attributes/use_provider.md b/docs/reference/attributes/use_provider.md index 4ddf0328..9999a851 100644 --- a/docs/reference/attributes/use_provider.md +++ b/docs/reference/attributes/use_provider.md @@ -130,4 +130,4 @@ A context can now wire `AreaCalculatorComponent` to `ScaledArea`, - Parsing: the outer form is parsed by `UseProviderAttribute` in [crates/macros/cgp-macro-core/src/types/attributes/use_provider/attribute.rs](../../../crates/macros/cgp-macro-core/src/types/attributes/use_provider/attribute.rs); its `to_type_param_bounds` inserts the context type at index 0 of the trait's generic arguments, and `to_provider_bounds` builds the `where` predicate. - Bound insertion: the bounds are appended to the impl by `add_type_param_bounds` in `attributes.rs`. - Collection and application: the attribute is collected for `#[cgp_impl]` in `types/attributes/cgp_impl_attributes.rs` and for `#[cgp_fn]` in `types/attributes/function.rs`, and applied in `types/cgp_impl/item.rs` and `types/cgp_fn/preprocessed.rs`. -- Implementation document (the internal AST type, the bound completion, and the index of tests and snapshots): [implementation/asts/attributes.md](../../implementation/asts/attributes.md). +- Implementation document (the internal AST type, the bound completion, and the index of tests and snapshots): [implementation/asts/attributes/use_provider.md](../../implementation/asts/attributes/use_provider.md). diff --git a/docs/reference/attributes/use_type.md b/docs/reference/attributes/use_type.md index 6e866dbf..3fe68f82 100644 --- a/docs/reference/attributes/use_type.md +++ b/docs/reference/attributes/use_type.md @@ -22,9 +22,9 @@ The part before the `.` is the trait and the identifier after it is the associat 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})]`. -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. +A trailing `in Context` clause changes the rewrite target from `Self` to a named type, which is how foreign abstract types are imported. The form `#[use_type(HasScalarType.Scalar in Types)]` treats `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. The `in` keyword is reserved in Rust, so it can never be confused with a trait, type, or associated-type name and reads as a clean delimiter after the associated-type list; the clause is consistent with the `in` used elsewhere in CGP wiring, such as `#[prefix(@Path in Namespace)]`. -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. +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. A braced list may itself carry a foreign context — `#[use_type(HasFooType.{Foo, Bar} in Ctx)]` projects every imported type in the group against `Ctx`, since the `in` clause scopes over the whole spec, not a single entry. When a definition imports types from several traits at once, prefer combining them into a single `#[use_type]` attribute by separating the trait paths with commas — `#[use_type(HasUserIdType.UserId, HasCurrencyType.Currency, HasErrorType.Error)]` — since one attribute reads as a single import list. Stacking multiple `#[use_type]` attributes is also accepted and behaves identically, as in `#[use_type(HasBarType.{Bar as Baz = Foo}, HasFooType.Foo)]` written across two lines, but reach for a second attribute only when a real reason calls for it rather than as the default. @@ -37,7 +37,7 @@ The grammar below covers the tokens inside `#[use_type(...)]` — the comma-sepa ```ebnf UseTypeArgs -> UseTypeSpec (`,` UseTypeSpec)* `,`? -UseTypeSpec -> (`@` ContextPath `.`)? TraitPath `.` TypeItems +UseTypeSpec -> TraitPath `.` TypeItems (`in` ContextPath)? ContextPath -> TypePath TraitPath -> TypePath @@ -48,11 +48,11 @@ TypeItems -> 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]`). +`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 the trait starts the associated-type list. An omitted `in ContextPath` clause defaults the rewrite target to `Self`; because `in` is a reserved keyword it can never appear inside `TypeItems`, so it marks the context clause unambiguously. 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 -`#[use_type]` runs before the rest of the macro and does two things: it substitutes every matching bare type identifier with the qualified associated type, and it adds the trait as a supertrait or bound. Consider this `#[cgp_fn]` using the single-import form: +`#[use_type]` runs before the rest of the macro in three steps: it first *grounds* each import's context (resolving an `in Context` that names another import into a fully-qualified path), then substitutes every matching bare type identifier with the qualified associated type in one pass, and finally adds the trait as a supertrait or bound. Consider this `#[cgp_fn]` using the single-import form: ```rust pub trait HasScalarType { @@ -116,12 +116,12 @@ 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 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`: +The supertrait is added only when the rewrite target is `Self`. With the foreign-type `in Context` form the target is a named type, so the bound cannot be a supertrait of `Self`; instead the macro adds a plain `Context: Trait` predicate wherever the substituted `::Assoc` paths appear. On `#[cgp_fn]` and `#[cgp_impl]` that predicate lands in the impl's `where` clause, and on `#[cgp_component]` and `#[cgp_fn]` it is *also* added to the generated trait's own `where` clause — because the trait's signatures now name `::Assoc` and would not be well-formed without it. This means a component using the `in` form no longer has to declare the parameter's bound by hand; writing `pub trait CanCalculateArea` is enough, and `Types: HasScalarType` is supplied for you. (The type-equality `= T` pin, by contrast, stays impl-side and is never added to the trait.) This `#[cgp_fn]` imports `Scalar` from a generic parameter `Types`: ```rust #[cgp_fn] -#[use_type(@Types.HasScalarType.Scalar)] -pub fn rectangle_area( +#[use_type(HasScalarType.Scalar in Types)] +pub fn rectangle_area( &self, #[implicit] width: Scalar, #[implicit] height: Scalar, @@ -134,14 +134,17 @@ where } ``` -Every `Scalar`, including the ones in the explicit `where` clause, expands to `::Scalar`, and the bound `Types: HasScalarType` is added to the impl's `where` clause rather than as a supertrait: +Every `Scalar`, including the ones in the explicit `where` clause, expands to `::Scalar`, and the bound `Types: HasScalarType` is added to *both* the generated trait's `where` clause and the impl's — so the plain, unbounded `` the author wrote is enough. It is a `where` bound rather than a supertrait because the target is a named type, not `Self`: ```rust -pub trait RectangleArea { +pub trait RectangleArea +where + Types: HasScalarType, +{ fn rectangle_area(&self) -> ::Scalar; } -impl RectangleArea for Context +impl RectangleArea for Context where ::Scalar: Mul::Scalar> + Copy, @@ -162,6 +165,8 @@ 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. This cross-spec resolution relies on aliases being unique, which the duplicate check described in the Syntax section guarantees. +Because grounding resolves every context against all specs at once and iterates to a fixpoint, the *order* in which nested imports are written does not matter: `#[use_type(HasC.C in B, HasB.B in A, HasA.A)]` grounds identically to the front-to-back `#[use_type(HasA.A, HasB.B in A, HasC.C in B)]`. The one arrangement without a valid order is a cycle — two `in Context` clauses that resolve through each other, as in `#[use_type(HasA.A in B, HasB.B in A)]`. Grounding stops rather than loops on a cycle, so the aliases are never resolved and the compiler reports `E0425` "cannot find type" at the offending `in` alias; correct the imports so the contexts form an acyclic chain. + ## Examples A realistic use threads one abstract `Scalar` type through a component and a provider, with neither writing `Self::` by hand. The component and the type trait come first: @@ -202,8 +207,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), 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. +- 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 trait as a `PathWithTypeArgs`, the associated-type list after the `.`, and an optional `in Context` (also a `PathWithTypeArgs`). Per-type entries (`as` alias and `=` equality) are in `ident.rs`. +- Three-step transform (ground contexts, substitute, add bounds): lives in `attributes.rs`. `grounded_specs` resolves each `in Context` that names another import into a fully-qualified path; `transform_item_trait` then substitutes and adds bounds to a trait (a supertrait for a `Self` import, a `where` bound for a foreign `in` import — this is `#[cgp_component]` and `#[cgp_fn]`), and `transform_item_impl` does the same for an impl's `where` clause. Both call `forbid_duplicate_aliases` first to reject a shared identifier or alias, and the impl-side type-equality predicates are derived in `type_predicates.rs`. +- Identifier substitution: the `SubstituteAbstractTypes` `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 holds every grounded spec at once and rewrites single-segment, argument-free type paths in a single traversal. - `= ...` 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). +- Implementation document (the internal AST types, the two-phase transform, and the index of tests and snapshots): [implementation/asts/attributes/use_type.md](../../implementation/asts/attributes/use_type.md). diff --git a/docs/reference/attributes/uses.md b/docs/reference/attributes/uses.md index aa24cdfd..e102164e 100644 --- a/docs/reference/attributes/uses.md +++ b/docs/reference/attributes/uses.md @@ -108,4 +108,4 @@ The `#[uses(CanCalculateArea)]` attribute adds `Self: CanCalculateArea` to the g - Parsing: `#[uses(...)]` parsing lives in [crates/macros/cgp-macro-core/src/types/attributes/uses.rs](../../../crates/macros/cgp-macro-core/src/types/attributes/uses.rs) (the `UsesAttributes` type and its `to_type_param_bounds`). - Dispatch: the attribute dispatch is in [crates/macros/cgp-macro-core/src/types/attributes/function.rs](../../../crates/macros/cgp-macro-core/src/types/attributes/function.rs) for `#[cgp_fn]` and [crates/macros/cgp-macro-core/src/types/attributes/cgp_impl_attributes.rs](../../../crates/macros/cgp-macro-core/src/types/attributes/cgp_impl_attributes.rs) for `#[cgp_impl]`. - Injection: the bounds are added to the impl `where` clause in [crates/macros/cgp-macro-core/src/types/cgp_fn/preprocessed.rs](../../../crates/macros/cgp-macro-core/src/types/cgp_fn/preprocessed.rs). -- Implementation document (the internal AST type, what the attribute injects into each host, and the index of tests and snapshots): [implementation/asts/attributes.md](../../implementation/asts/attributes.md). +- Implementation document (the internal AST type, what the attribute injects into each host, and the index of tests and snapshots): [implementation/asts/attributes/uses.md](../../implementation/asts/attributes/uses.md). diff --git a/docs/reference/macros/cgp_namespace.md b/docs/reference/macros/cgp_namespace.md index 8fe50627..b5c3a94c 100644 --- a/docs/reference/macros/cgp_namespace.md +++ b/docs/reference/macros/cgp_namespace.md @@ -212,7 +212,7 @@ Inheritance composes the same way at the namespace level: `ExtendedNamespace: De ## Known issues -A context that joins a namespace with `namespace N;` cannot also wire, directly on itself, a path that `N` already registers. The `namespace N;` header emits a blanket `impl DelegateComponent for Ctx where Key: N`, which already covers every path `N` resolves; a direct `@path: Provider` entry on the same context emits a second `DelegateComponent` impl for that path, and the compiler rejects the overlap with `E0119`. Overriding therefore works only on a path the namespace routes *to* but does not itself terminate: register the component's [`#[prefix]`](cgp_component.md) redirect in a base namespace the context inherits, and leave the leaf path unclaimed by the namespace so the context can supply it. A path the namespace registers with a `:` body entry or a `#[default_impl]` is not overridable on the context; change it in the namespace instead. The same overlap arises from a bare-key `for` loop — `for in Table { Key: Value }` emits a blanket `DelegateComponent` impl of its own — which is why a loop key must be embedded in a path (`@app.SomeComponent.Key: Value`). This is a whole-program coherence fact the macro cannot detect, so it is deferred to the compiler; its full anatomy is in the [conflicting wiring](../../errors/wiring/conflicting-wiring.md) error class. +A context that joins a namespace with `namespace N;` cannot also wire, directly on itself, a path that `N` already registers. The `namespace N;` header emits a blanket `impl DelegateComponent for Ctx where Key: N`, which already covers every path `N` resolves; a direct `@path: Provider` entry on the same context emits a second `DelegateComponent` impl for that path, and the compiler rejects the overlap with `E0119`. Overriding therefore works only on a path the namespace routes *to* but does not itself terminate: register the component's [`#[prefix]`](cgp_component.md) redirect in a base namespace the context inherits, and leave the leaf path unclaimed by the namespace so the context can supply it. A path the namespace registers with a `:` body entry or a `#[default_impl]` is not overridable on the context; change it in the namespace instead. The same restriction stops an inheriting namespace from redefining a key its parent binds. This is a whole-program coherence fact the macro cannot detect, so it is deferred to the compiler; its full anatomy is in the [namespace override conflict](../../errors/wiring/namespace-override-conflict.md) error class. A related overlap arises when a context emits *two* blanket forwardings — joining two namespaces, or a bare-key `for` loop (`for in Table { Key: Value }`) alongside a `namespace` join, which is why a loop key must be embedded in a path (`@app.SomeComponent.Key: Value`) — the [overlapping namespace forwarding](../../errors/wiring/namespace-forwarding-conflict.md) error class. Two further whole-program failures are deferred to the compiler the same way. If a component is routed into a joined namespace by a `#[prefix]` but no entry ever *binds* a provider at its path — no `#[default_impl]`, body entry, or direct wiring — the redirect lands on an empty table slot, and a `check_components!` reports the lookup as unsatisfied (`E0277`); this is the [unregistered namespace path](../../errors/checks/unregistered-namespace-path.md) error class. And a circular parent chain — `new A: B` with `new B: A`, or a self-inheriting `new A: A` — makes the inheritance blanket impl's `where` clause loop, which the compiler rejects eagerly at the `cgp_namespace!` definitions with an `E0275` overflow; this is the [namespace inheritance cycle](../../errors/wiring/namespace-inheritance-cycle.md) error class. diff --git a/docs/reference/providers/use_default.md b/docs/reference/providers/use_default.md index f30222f4..fb50ed1d 100644 --- a/docs/reference/providers/use_default.md +++ b/docs/reference/providers/use_default.md @@ -108,4 +108,4 @@ delegate_components! { ## Source - The struct is defined in [crates/core/cgp-component/src/providers/use_default.rs](../../../crates/core/cgp-component/src/providers/use_default.rs) and re-exported in [crates/core/cgp-component/src/providers/mod.rs](../../../crates/core/cgp-component/src/providers/mod.rs); the file contains only the bare struct, with no macro-generated impls. -- For how it is generated and the index of tests, see the implementation document [implementation/asts/attributes](../../implementation/asts/attributes.md). +- For how it is generated and the index of tests, see the implementation document [implementation/asts/attributes/default_impl.md](../../implementation/asts/attributes/default_impl.md). diff --git a/docs/reference/providers/use_delegate.md b/docs/reference/providers/use_delegate.md index cc6ea9a7..a5b2e46f 100644 --- a/docs/reference/providers/use_delegate.md +++ b/docs/reference/providers/use_delegate.md @@ -98,4 +98,4 @@ The wiring reads in two layers. `MyApp` delegates `AreaCalculatorComponent` to ` - The struct is defined in [crates/core/cgp-component/src/providers/use_delegate.rs](../../../crates/core/cgp-component/src/providers/use_delegate.rs). - The `UseDelegate` provider impl is generated from the `#[derive_delegate]` directive parsed in [crates/macros/cgp-macro-core/src/types/attributes/](../../../crates/macros/cgp-macro-core/src/types/attributes/) and emitted by the `#[cgp_component]` pipeline in [crates/macros/cgp-macro-core/src/types/cgp_component/](../../../crates/macros/cgp-macro-core/src/types/cgp_component/). - The nested-table wiring is handled by `delegate_components!` in [crates/macros/cgp-macro-core/src/types/delegate_component/](../../../crates/macros/cgp-macro-core/src/types/delegate_component/). -- For how it is generated and the index of tests, see the implementation document [implementation/asts/attributes](../../implementation/asts/attributes.md). +- For how it is generated and the index of tests, see the implementation document [implementation/asts/attributes/derive_delegate.md](../../implementation/asts/attributes/derive_delegate.md). diff --git a/docs/skills/cgp/SKILL.md b/docs/skills/cgp/SKILL.md index e5e4c023..fc9732dd 100644 --- a/docs/skills/cgp/SKILL.md +++ b/docs/skills/cgp/SKILL.md @@ -872,7 +872,7 @@ The remaining sub-skills each own one construct family: - **[references/components.md](references/components.md)** — `#[cgp_component]` and the full expansion (consumer/provider traits, the two blanket impls, the `…Component` marker), why `IsProviderFor` exists, and the three provider-writing macros (`#[cgp_impl]`, `#[cgp_provider]`, `#[cgp_new_provider]`). *Without it* you will misjudge what `self`/`Self` mean inside a provider and how the blanket impls route a call. Load it before writing any component or provider. - **[references/wiring.md](references/wiring.md)** — `DelegateComponent`, every `delegate_components!` form (arrays, `new`, generic tables), `open` per-type dispatch, direct consumer-trait impls, `UseContext` and its circular-dependency trap, the legacy `UseDelegate` tables, and the other providers you see in tables (`WithProvider` and its `WithField`/`WithType`/`WithContext` aliases, `UseDefault`). *Without it* you will not know when a hand-written impl collides with the table, why `UseContext` overflows, or what a `WithField<…>` entry means. Load it before wiring any context. - **[references/checking.md](references/checking.md)** — why wiring is lazy, how check traits and `CanUseComponent` force readable errors, every `check_components!` / `delegate_and_check_components!` option (`#[check_trait]`, `#[check_providers]`, `#[check_params]`, `#[skip_check]`), and a debugging playbook. *Without it* you cannot localize a broken wiring or read the error it throws. Load it whenever a wiring fails to compile. -- **[references/error-extraction.md](references/error-extraction.md)** — how to reduce a long CGP compile error to a compact, root-cause-first summary, the hidden-versus-surfaced distinction that decides whether the root cause is even present in the output, and how to delegate the reading to a sub-agent so a wall of generated-type errors does not consume your context. *Without it* you will read a cascade inline, chase a cause a hidden error does not contain, or hand back raw output instead of the few facts that matter. Load it whenever a CGP error is long enough that a sub-agent should read it — in an ordinary debugging session, not only when authoring the error catalog. +- **[references/error-extraction.md](references/error-extraction.md)** — how to reduce a long CGP compile error to a compact, root-cause-first summary, the hidden-versus-surfaced distinction that decides whether the root cause is even present in the output, how to confirm a suspected cause by grepping the error output for one signature line instead of reading it all, and how to delegate the reading to a sub-agent so a wall of generated-type errors does not consume your context. *Without it* you will read a cascade inline, chase a cause a hidden error does not contain, or hand back raw output instead of the few facts that matter. Load it whenever a CGP error is long enough that a sub-agent should read it — in an ordinary debugging session, not only when authoring the error catalog. - **[references/functions-and-getters.md](references/functions-and-getters.md)** — `HasField`/`#[derive(HasField)]`, `#[cgp_fn]`, `#[implicit]` and its access rules, `#[uses]`/`#[extend]`/`#[extend_where]`/`#[impl_generics]`, the getters `#[cgp_auto_getter]`/`#[cgp_getter]`/`UseField`/`WithField`, and `ChainGetters` for nested-context fields. *Without it* you will reach for a getter trait where an implicit argument is idiomatic, or misapply the `.clone()`/`.as_str()`/`&mut` field-access rules. Load it for the ergonomic day-to-day surface. - **[references/abstract-types.md](references/abstract-types.md)** — `#[cgp_type]`, the built-in `HasType`/`TypeProvider`, wiring with `UseType` (and `UseDelegatedType` for table-chosen types), importing types with the `#[use_type]` attribute (distinct from the `UseType` provider), and the `WithType`/`WithDelegatedType` adapters. *Without it* you will confuse the provider and the attribute and write `Self::` paths by hand. Load it for any associated-type abstraction. - **[references/higher-order-providers.md](references/higher-order-providers.md)** — providers parameterized by other providers, the stray `` on the inner bound, `#[use_provider]`, `UseContext` defaults, generic-parameter components, and cross-context dependencies. *Without it* you will call the inner provider as a method instead of `Provider::method(self)` and misplace the context slot. Load it before composing providers. diff --git a/docs/skills/cgp/references/abstract-types.md b/docs/skills/cgp/references/abstract-types.md index 1a8e0b5f..603800cb 100644 --- a/docs/skills/cgp/references/abstract-types.md +++ b/docs/skills/cgp/references/abstract-types.md @@ -147,7 +147,7 @@ where The substitution is purely textual at the type level — it matches single-segment, argument-free type paths whose identifier equals the imported name — so a bare `Scalar` in the return type, an implicit-argument annotation, or a `let` binding inside the body is all rewritten the same way. Beyond saving keystrokes, the always-qualified rewrite removes the ambiguity the bare form cannot express, which is why this is the default way to import abstract types in all three macros. -The attribute has a few richer forms worth knowing. A leading `@` changes the rewrite target from `Self` to a named type, which imports a *foreign* abstract type from a generic parameter: `#[use_type(@Types.HasScalarType.Scalar)]` rewrites `Scalar` to `::Scalar` and adds `Types: HasScalarType` as a `where` bound rather than a supertrait. A braced list imports several types from one trait, each optionally renamed with `as` or constrained with `=`: `#[use_type(HasScalarType.{Scalar = f64})]` both imports `Scalar` and emits `Self: HasScalarType`, pinning it. This equality form is the modern replacement for a hand-written `where Self: HasScalarType` clause on a `#[cgp_impl]` or `#[cgp_fn]` — prefer moving such a pin into the attribute rather than leaving it as an explicit `where`. The right-hand side of `=` may name another imported alias to *unify* two abstract types: `#[use_type(HasPasswordType.Password, HasHashedPasswordType.{HashedPassword = Password})]` emits `Self: HasHashedPasswordType::Password>`, tying the two together. To import types from *several* traits, separate the trait paths with commas inside one attribute — `#[use_type(HasUserIdType.UserId, HasCurrencyType.Currency, HasErrorType.Error)]` — and prefer that combined form over stacking one `#[use_type]` attribute per trait, since it reads as a single import list; stacked attributes behave identically but are for when a real reason calls for it. The `= ...` equality form is rejected on `#[cgp_component]`, since a trait definition cannot carry the impl-side equality constraint it produces; it belongs on `#[cgp_fn]` and `#[cgp_impl]`. Two imports may not resolve to the same identifier or alias — across specs or within one braced list — since the substitution could then match only one; a collision is a compile error on every host macro. +The attribute has a few richer forms worth knowing. A trailing `in Context` clause changes the rewrite target from `Self` to a named type, which imports a *foreign* abstract type from a generic parameter: `#[use_type(HasScalarType.Scalar in Types)]` rewrites `Scalar` to `::Scalar` and adds `Types: HasScalarType` as a `where` bound rather than a supertrait. That bound lands on the generated impl *and*, on `#[cgp_fn]`/`#[cgp_component]`, on the generated trait itself, so a plain unbounded `` parameter is enough — you do not restate `Types: HasScalarType` by hand. The `in Context` clause may also point at another abstract type imported in the same attribute, chaining through several hops (`HasTypes.Types, HasScalarType.Scalar in Types` yields the two-hop `<::Types as HasScalarType>::Scalar`). A braced list imports several types from one trait, each optionally renamed with `as` or constrained with `=`: `#[use_type(HasScalarType.{Scalar = f64})]` both imports `Scalar` and emits `Self: HasScalarType`, pinning it. This equality form is the modern replacement for a hand-written `where Self: HasScalarType` clause on a `#[cgp_impl]` or `#[cgp_fn]` — prefer moving such a pin into the attribute rather than leaving it as an explicit `where`. The right-hand side of `=` may name another imported alias to *unify* two abstract types: `#[use_type(HasPasswordType.Password, HasHashedPasswordType.{HashedPassword = Password})]` emits `Self: HasHashedPasswordType::Password>`, tying the two together. To import types from *several* traits, separate the trait paths with commas inside one attribute — `#[use_type(HasUserIdType.UserId, HasCurrencyType.Currency, HasErrorType.Error)]` — and prefer that combined form over stacking one `#[use_type]` attribute per trait, since it reads as a single import list; stacked attributes behave identically but are for when a real reason calls for it. The `= ...` equality form is rejected on `#[cgp_component]`, since a trait definition cannot carry the impl-side equality constraint it produces; it belongs on `#[cgp_fn]` and `#[cgp_impl]`. Two imports may not resolve to the same identifier or alias — across specs or within one braced list — since the substitution could then match only one; a collision is a compile error on every host macro. ## Sharing one type across contexts diff --git a/docs/skills/cgp/references/error-extraction.md b/docs/skills/cgp/references/error-extraction.md index c7e51d22..840b589b 100644 --- a/docs/skills/cgp/references/error-extraction.md +++ b/docs/skills/cgp/references/error-extraction.md @@ -14,6 +14,14 @@ A **surfaced** error carries the root cause. It is an `E0277` note chain, typica A **hidden** error does not. It is an `E0599` "the method `greet` exists for struct `Person`, but its trait bounds were not satisfied", or an `E0277` that a consumer trait like `Person: CanGreet` is unsatisfied, and it names the consumer or provider trait and then stops — with no note descending to the missing field or dependency. This happens when broken wiring is exercised by *calling the consumer-trait method directly* rather than through a check: the compiler sees the consumer trait's blanket impl among the candidate impls, finds it inapplicable, and suppresses the nested bound that made it so. The cause is **absent**, not buried. Do not scan a hidden error for a root cause; there is none to find. The fix is to *promote* it into a surfaced error — add a `check_components!` for the failing component at the wiring site — and read that instead. +## The cheap first move: grep for the suspected line + +Before committing to either role, try to answer your question with a grep, because the main agent can often confirm or dismiss a hypothesis without reading the log itself or spawning a sub-agent to read it. This is the move to reach for whenever you have a specific suspicion — a field you think you forgot, a key you think you wired twice, a `UseContext` you think is a cycle — and the failing build is captured to a file. Grep is not a third role so much as the way to avoid needing Role 2: a targeted search costs a few lines of output where delegating costs a whole sub-agent turn, so a hypothesis you can phrase as a pattern is one you should grep for rather than delegate. + +Grep the error headlines first, because that one search does the classifying. `grep -nE '^error' /tmp/cgp-error.txt` prints one line per error block — the code and the trait each names — which decides the [shape](#the-two-shapes-to-recognize) and, with the [macro-grammar decoder](macro-grammar.md), the class. A second grep for the class's signature then confirms the cause: `grep -n 'help:'` for a surfaced dependency leaf (a `HasField>` spells the field name letter-by-letter on one line), `conflicting implementation` for a duplicate key, `overflow evaluating` for a cycle, `does not contain any DelegateComponent entry` for an unwired component, `is not constrained` for an unconstrained generic, and so on. The [debugging guide's grep table](https://github.com/contextgeneric/cgp/blob/main/docs/guides/debugging.md#grep-for-the-suspected-line-instead-of-reading-the-whole-log) lists the signature line for every class. + +Two limits decide when grep gives way to a full read or a delegation. A **hidden** error cannot be grepped: if the only headline is an `E0599` "method … exists … but its trait bounds were not satisfied", the cause is absent from the output, so promote it with a `check_components!` and re-run rather than searching for a leaf that is not there. And when the headline grep shows several unrelated classes at once, or the cause hides behind an elided `...` whose full form sits in a `long-type-….txt` file, the search will not converge — that is the point to escalate to [Role 2](#role-2--delegating-the-extraction-to-a-sub-agent) and let a sub-agent absorb the whole log. The rule is the same throughout: a targeted question is a grep, an open-ended read is a delegation. + ## Role 1 — extracting an error yourself You are in this role whenever you are the one reading the raw output — as a sub-agent handed the job, or as the main agent facing an error short enough to read inline. Your product is the [compact summary](#reduce-to-the-compact-summary) below, never the raw dump. diff --git a/docs/skills/cgp/references/functions-and-getters.md b/docs/skills/cgp/references/functions-and-getters.md index 26777466..2c33b032 100644 --- a/docs/skills/cgp/references/functions-and-getters.md +++ b/docs/skills/cgp/references/functions-and-getters.md @@ -213,6 +213,18 @@ pub trait HasName { A context gains the getter just by deriving `HasField` with a matching field — `person.name()` resolves through the blanket impl with no wiring. The cost of that simplicity is rigidity: the field name *must* equal the method name, and there is no way to swap the implementation. When you need either, `#[cgp_getter]` provides it — but that is an advanced tool, not a routine next step. +When the getter's return type names an abstract type that lives on a *foreign* type — a generic parameter of the trait rather than `Self` — prefer importing it with [`#[use_type]`](abstract-types.md)'s `in Context` clause over a hand-written `where` bound and a qualified path. Write + +```rust +#[cgp_auto_getter] +#[use_type(HasUserIdType.UserId in App)] +pub trait HasLoggedInUser { + fn logged_in_user(&self) -> &Option; +} +``` + +rather than declaring `where App: HasUserIdType` and returning `&Option`. The `in App` clause supplies `App: HasUserIdType` on the generated trait and rewrites the bare `UserId` to `::UserId`, so the plain `` parameter and the bare alias are enough — the same benefit `#[use_type]` gives on `Self`, extended to a parameter. + ## Wireable getters: `#[cgp_getter]` and `UseField` `#[cgp_getter]` defines a getter as a full CGP [component](components.md) instead of a blanket impl, so the field name can differ from the method name and the getter can be swapped per context through [wiring](wiring.md). It is a specialized, advanced tool: reserve it for when a context genuinely needs full control over which field a getter reads from, and prefer an implicit argument or `#[cgp_auto_getter]` for the ordinary case of a same-named field. It accepts the same getter-method forms as `#[cgp_auto_getter]`, but because it is an extension of `#[cgp_component]` it needs a provider trait name. The default derives one from the trait name by stripping a leading `Has` and appending `Getter`, so `HasName` yields the provider `NameGetter` and the component marker `NameGetterComponent`; pass an argument like `#[cgp_getter(GetName)]` to override it. diff --git a/docs/skills/cgp/references/macro-grammar.md b/docs/skills/cgp/references/macro-grammar.md index b9967a51..dcc0ae52 100644 --- a/docs/skills/cgp/references/macro-grammar.md +++ b/docs/skills/cgp/references/macro-grammar.md @@ -132,7 +132,7 @@ This attribute has enough structure to warrant a grammar of its own: ```ebnf UseTypeArgs -> UseTypeSpec ( `,` UseTypeSpec )* `,`? -UseTypeSpec -> ( `@` ContextPath `.` )? TraitPath `.` TypeItems +UseTypeSpec -> TraitPath `.` TypeItems ( `in` ContextPath )? ContextPath -> TypePath TraitPath -> TypePath @@ -143,7 +143,7 @@ TypeItems -> UseTypeIdent UseTypeIdent -> IDENTIFIER ( `as` IDENTIFIER )? ( `=` Type )? ``` -The `.` (not `::`) after the trait path starts the associated-type list — a trait path keeps its own `::` segments and may carry generics. Omitting the `@ContextPath .` prefix defaults the rewrite target to `Self`; a `@Types.` prefix rewrites against a named generic parameter and adds `Types: Trait` as a `where` bound rather than a supertrait. In each `UseTypeIdent`, `as` gives a local alias to write in signatures, and `= Type` pins the type with an equality bound (accepted on `#[cgp_fn]`/`#[cgp_impl]`, **rejected on `#[cgp_component]`**, whose trait definition cannot carry the impl-side equality). The macro rewrites every bare mention of the imported identifier (or alias) to the fully-qualified `::Type`. Host: `#[cgp_fn]`, `#[cgp_impl]`, `#[cgp_component]`. See [abstract-types](abstract-types.md). +The `.` (not `::`) after the trait path starts the associated-type list — a trait path keeps its own `::` segments and may carry generics. Omitting the `in ContextPath` suffix defaults the rewrite target to `Self`; an `in Types` suffix rewrites against a named generic parameter and adds `Types: Trait` as a `where` bound rather than a supertrait (`in` is a reserved keyword, so it can never be mistaken for part of a type, and reads like the `in` in `#[prefix(@Path in Namespace)]`). In each `UseTypeIdent`, `as` gives a local alias to write in signatures, and `= Type` pins the type with an equality bound (accepted on `#[cgp_fn]`/`#[cgp_impl]`, **rejected on `#[cgp_component]`**, whose trait definition cannot carry the impl-side equality). The macro rewrites every bare mention of the imported identifier (or alias) to the fully-qualified `::Type`. Host: `#[cgp_fn]`, `#[cgp_impl]`, `#[cgp_component]`. See [abstract-types](abstract-types.md). --- diff --git a/docs/skills/cgp/references/modern-idioms.md b/docs/skills/cgp/references/modern-idioms.md index 7e10474a..e18edb20 100644 --- a/docs/skills/cgp/references/modern-idioms.md +++ b/docs/skills/cgp/references/modern-idioms.md @@ -144,6 +144,49 @@ One rule bounds the rewrite: it fires only on the bare identifier of an *importe When a definition imports types from several traits, combine them into one `#[use_type]` attribute with the trait paths comma-separated — `#[use_type(HasUserIdType.UserId, HasCurrencyType.Currency, HasErrorType.Error)]` — rather than stacking one attribute per trait; the combined form reads as a single import list. Several types from one trait use the braced list (`#[use_type(HasFooType.{Foo, Bar})]`). Stacking repeated `#[use_type]` attributes behaves identically, but reach for it only when a real reason calls for it. +Prefer `#[use_type]` even when the type lives on a **foreign** type rather than `Self` — a type named by a generic parameter — using the trailing `in Context` clause. It rewrites the bare alias to `::Assoc` *and* supplies `Context: Trait`, so the hand-written + +```rust +#[cgp_auto_getter] +pub trait HasLoggedInUser +where + App: HasUserIdType, +{ + fn logged_in_user(&self) -> &Option; +} +``` + +becomes + +```rust +#[cgp_auto_getter] +#[use_type(HasUserIdType.UserId in App)] +pub trait HasLoggedInUser { + fn logged_in_user(&self) -> &Option; +} +``` + +with `App: HasUserIdType` supplied for you, so the plain `` parameter and the bare `UserId` are enough. + +Import a type with `#[use_type]` even when it *already* arrives transitively — as the supertrait of a trait pulled in by `#[uses]`. When `CanCreateFoo` has `HasFooType` as a supertrait, prefer re-importing over leaning on the transitive `Self::Foo`: + +```rust +#[cgp_fn] +#[uses(CanCreateFoo)] +#[use_type(HasFooType.Foo)] // re-import: bare `Foo`, explicit `HasFooType` dep +fn bar(&self) -> Foo { self.create_foo(42) } +``` + +instead of + +```rust +#[cgp_fn] +#[uses(CanCreateFoo)] +fn bar(&self) -> Self::Foo { self.create_foo(42) } // relies on the reachable supertrait +``` + +The re-import re-adds a harmless (already-implied) `Self: HasFooType` and rewrites the bare `Foo`, so `#[uses]` states the *capability* dependency and `#[use_type]` states the *type* dependency, both visible rather than one riding in silently. + `#[use_type]` also *pins* an abstract type with the equality form `{Assoc = Type}`, which is the modern replacement for a hand-written `where Self: HasXType` clause — reach for it whenever a provider constrains an abstract type to a concrete one, rather than leaving the equality as an explicit `where`. On a `#[cgp_impl]`, the legacy ```rust