Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,18 @@ impl DefaultImplAttribute {
.push(parse_internal!(__Components__));

let mut generics = provider_generics.clone();

// Drop the provider's impl-side dependencies. `provider_generics` is the
// provider impl's generics *after* `#[implicit]`/`#[uses]`/`#[use_type]`/
// `#[use_provider]` have pushed their `Self`-keyed bounds into its `where`
// clause (e.g. `Self: HasErrorType`). Those belong on the provider's own
// impl and its `IsProviderFor`, never on this registration impl, whose only
// job is `type Delegate = Provider`. Left in place they would bind the
// registration impl's `Self` — the path key `PathCons<..>` — so a
// dependency like `Self: HasErrorType` would demand `PathCons<..>:
// HasErrorType` and never resolve. The impl carries only the parameters
// that name the key and provider, plus the `__Components__` table.
generics.where_clause = None;
generics.params.push(parse_internal!(__Components__));

let (impl_generics, _, where_clause) = generics.split_for_impl();
Expand Down
1 change: 1 addition & 0 deletions crates/tests/cgp-compile-fail-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ readme = "./README.md"

[dependencies]
cgp = { workspace = true }
cgp-test-crate-a = { workspace = true }

[dev-dependencies]
trybuild = { version = "1.0.117" }
11 changes: 11 additions & 0 deletions crates/tests/cgp-compile-fail-tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,14 @@ when an intended change alters a diagnostic, regenerate the snapshots with
`TRYBUILD=overwrite cargo test -p cgp-compile-fail-tests`, then review the diff
before committing — an unexpected change to an `acceptable/` diagnostic, or a
`problematic/` fixture that stops failing, is a signal worth reading closely.

Regenerate with the pinned toolchain, because a snapshot can embed standard-library
source. When an error points into the standard library — `option_slice.stderr` is
the only current case — `rustc` prints the referenced source line (`pub enum
Option<T> {`) if the `rust-src` component is installed and omits it otherwise, while
`trybuild` normalizes the path to `$RUST/...` either way. The difference is therefore
invisible in the path and shows up only as an added or missing snippet line, which
reads as spurious non-determinism between machines. The pinned toolchain declares
`rust-src` in [rust-toolchain.toml](../../../rust-toolchain.toml) precisely so this
renders the same everywhere; blessing under a toolchain that lacks the component
would silently strip the snippet and reintroduce the mismatch on CI.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ error[E0277]: the size for values of type `[u8]` cannot be known at compilation
= help: the trait `Sized` is not implemented for `[u8]`
note: required by an implicit `Sized` bound in `Option`
--> $RUST/core/src/option.rs
|
| pub enum Option<T> {
| ^ required by the implicit `Sized` requirement on this type parameter in `Option`
= note: this error originates in the attribute macro `cgp_auto_getter` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0599]: the method `as_ref` exists for reference `&Option<[u8]>`, but its trait bounds were not satisfied
Expand All @@ -15,6 +18,11 @@ error[E0599]: the method `as_ref` exists for reference `&Option<[u8]>`, but its
17 | #[cgp_auto_getter]
| ^^^^^^^^^^^^^^^^^^ method cannot be called on `&Option<[u8]>` due to unsatisfied trait bounds
|
::: $RUST/core/src/option.rs
|
| pub enum Option<T> {
| ------------------ doesn't satisfy `Option<[u8]>: AsRef<_>`
|
= note: the following trait bounds were not satisfied:
`[u8]: Sized`
`Option<[u8]>: AsRef<_>`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//! Acceptable failure: `#[cgp_component(Greeter)]` derives a marker
//! `pub struct GreeterComponent;`, and this module also declares its own
//! `GreeterComponent`, so the name is defined twice (E0428). `#[cgp_component]`
//! expands without any view of the rest of the module, so it emits the derived
//! marker faithfully and lets the compiler report the clash — exactly as two
//! hand-written definitions would.
//!
//! This fixture pins the span of the *derived* `#[cgp_component]` marker. The
//! E0428 "previous definition of the type `GreeterComponent` here" note falls on
//! the `Greeter` provider name the user wrote inside `#[cgp_component(…)]`, not on
//! the whole attribute, because the derived marker struct ident is emitted with
//! the provider identifier's own span (see
//! cgp-macro-core/src/types/cgp_component/args/component_args.rs). A regression
//! that stamped the marker with `Span::call_site()` would move that note onto the
//! whole `#[cgp_component(..)]` attribute — the leak the span fix removed so that
//! cross-crate go-to-definition on the marker resolves to the provider name alone.
//!
//! See docs/implementation/entrypoints/cgp_component.md (Failure modes).

use cgp::prelude::*;

#[cgp_component(Greeter)]
pub trait CanGreet {
fn greet(&self);
}

// Collides with the `GreeterComponent` marker derived from `Greeter` above.
pub struct GreeterComponent;

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
error[E0428]: the name `GreeterComponent` is defined multiple times
--> tests/acceptable/cgp_component/duplicate_component_name.rs:28:1
|
22 | #[cgp_component(Greeter)]
| ------- previous definition of the type `GreeterComponent` here
...
28 | pub struct GreeterComponent;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `GreeterComponent` redefined here
|
= note: `GreeterComponent` must be defined only once in the type namespace of this module
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//! Acceptable failure: a downstream crate cannot register a default for a
//! *prefixed* upstream component into the upstream namespace with `#[default_impl]`.
//!
//! `cgp-test-crate-a`'s `Announcer` carries `#[prefix(@app in DefaultNamespace)]`,
//! so its namespace key is the path `@app.AnnouncerComponent`. Registering a
//! default at that path in crate-a's `AppNamespace` expands to
//! `impl AppNamespace<_> for PathCons<Symbol<"app">, PathCons<AnnouncerComponent, Nil>>`,
//! whose trait (`AppNamespace`) and every element of the `Self` type (`PathCons`
//! and `Symbol` from `cgp`, `AnnouncerComponent` from crate-a) are all foreign to
//! this crate — an orphan-rule violation (E0117). A per-component default keyed on
//! a *prefix path* can therefore only be written in the crate that owns the
//! namespace; a downstream crate must own the key, which for a prefixed component
//! it does not. (The orphan-safe counterpart — a *local* component key registered
//! into a foreign namespace — is exercised in `cgp-test-crate-b`.)
//!
//! This is why `#[default_impl]` couples an implementation to the namespace's
//! crate and why the guide recommends namespace *body* entries for wiring that
//! must live downstream of the namespace.
//!
//! See docs/implementation/entrypoints/cgp_namespace.md (Failure modes).

use cgp::prelude::*;
use cgp_test_crate_a::{Announcer, AnnouncerComponent, AppNamespace, HasName};

#[cgp_impl(new AnnounceQuietly)]
#[default_impl(@app.AnnouncerComponent in AppNamespace)]
impl Announcer
where
Self: HasName,
{
fn announce(&self) -> String {
format!("(psst, {})", self.name())
}
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
error[E0210]: type parameter `__Components__` must be used as the type parameter for some local type (e.g., `MyStruct<__Components__>`)
--> tests/acceptable/cgp_namespace/default_impl_foreign_prefix_path.rs:25:1
|
25 | #[cgp_impl(new AnnounceQuietly)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type parameter `__Components__` must be used as the type parameter for some local type
|
= note: implementing a foreign trait is only possible if at least one of the types for which it is implemented is local
= note: only traits defined in the current crate can be implemented for a type parameter
= note: this error originates in the attribute macro `cgp_impl` (in Nightly builds, run with -Z macro-backtrace for more info)
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//! Acceptable failure: a context that joins a namespace with `namespace N;`
//! cannot also wire, directly on itself, a path that `N` already registers.
//!
//! `GreetHello` registers the path `@app.GreeterComponent` into `AppNamespace`
//! with `#[default_impl]`, so `PathCons<app, GreeterComponent>` implements
//! `AppNamespace<_>`. The `namespace AppNamespace;` header then emits a blanket
//! `impl<Key> DelegateComponent<Key> for App where Key: AppNamespace<App>`, which
//! already covers that path. The extra `@app.GreeterComponent: GreetBye` entry
//! emits a second `DelegateComponent<PathCons<app, GreeterComponent>> for App`,
//! and the two overlap — E0119. CGP lowers both entries faithfully; only the whole
//! program reveals the overlap, so it defers to the compiler.
//!
//! The rule this pins: override a component the namespace routes by shadowing its
//! *marker* only when the namespace does not itself terminate the redirect path,
//! or wire the override on a path the namespace never registers. A namespace that
//! registers the leaf path leaves nothing for the context to override there.
//!
//! See docs/implementation/entrypoints/cgp_namespace.md (Failure modes).

use cgp::prelude::*;

#[cgp_component(Greeter)]
#[prefix(@app in DefaultNamespace)]
pub trait CanGreet {
fn greet(&self) -> String;
}

#[cgp_impl(new GreetHello)]
#[default_impl(@app.GreeterComponent in AppNamespace)]
impl Greeter {
fn greet(&self) -> String {
"Hello".to_owned()
}
}

#[cgp_impl(new GreetBye)]
impl Greeter {
fn greet(&self) -> String {
"Bye".to_owned()
}
}

cgp_namespace! {
new AppNamespace: DefaultNamespace {}
}

pub struct App;

delegate_components! {
App {
namespace AppNamespace;

@app.GreeterComponent: GreetBye,
}
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
error[E0119]: conflicting implementations of trait `IsProviderFor<PathCons<Symbol<3, cgp::prelude::Chars<'a', cgp::prelude::Chars<'p', cgp::prelude::Chars<'p', Nil>>>>, PathCons<GreeterComponent, Nil>>, _, _>` for type `App`
--> tests/acceptable/cgp_namespace/override_registered_path.rs:53:14
|
51 | namespace AppNamespace;
| ------------ first implementation here
52 |
53 | @app.GreeterComponent: GreetBye,
| ^^^^^^^^^^^^^^^^ conflicting implementation for `App`
|
= note: downstream crates may implement trait `cgp::prelude::IsProviderFor<cgp::prelude::PathCons<cgp::prelude::Symbol<3, cgp::prelude::Chars<'a', cgp::prelude::Chars<'p', cgp::prelude::Chars<'p', cgp::prelude::Nil>>>>, cgp::prelude::PathCons<GreeterComponent, cgp::prelude::Nil>>, _, _>` for type `GreetHello`
= note: downstream crates may implement trait `cgp::prelude::IsProviderFor<cgp::prelude::PathCons<cgp::prelude::Symbol<3, cgp::prelude::Chars<'a', cgp::prelude::Chars<'p', cgp::prelude::Chars<'p', cgp::prelude::Nil>>>>, cgp::prelude::PathCons<GreeterComponent, cgp::prelude::Nil>>, _, _>` for type `GreetBye`

error[E0119]: conflicting implementations of trait `DelegateComponent<PathCons<Symbol<3, cgp::prelude::Chars<'a', cgp::prelude::Chars<'p', cgp::prelude::Chars<'p', Nil>>>>, PathCons<GreeterComponent, Nil>>>` for type `App`
--> tests/acceptable/cgp_namespace/override_registered_path.rs:53:14
|
51 | namespace AppNamespace;
| ------------ first implementation here
52 |
53 | @app.GreeterComponent: GreetBye,
| ^^^^^^^^^^^^^^^^ conflicting implementation for `App`
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//! Acceptable failure: `GreetHello` carries the impl-side dependency
//! `Self: HasName`, but `Person` has no `name` field, so it cannot satisfy it.
//! `check_components!` exists precisely to surface this at the wiring site rather
//! than lazily at the call site (contrast
//! acceptable/delegate_components/missing_dependency.rs, which leaves the same
//! wiring unchecked and hits the error only when `greet` is called). The failure
//! is the check doing its job, not a macro defect.
//!
//! This fixture pins the `check_components!` error span. The unsatisfied-bound
//! caret falls on `GreeterComponent` inside the `check_components!` block, not on
//! the `Person` context type, because the check impl re-spans the shared context
//! token onto each listed component in turn with `override_span` (see
//! cgp-macro-core/src/types/check_components/table.rs). A regression that dropped
//! that re-span would report the error on the single `Person` token shared by
//! every checked component instead of on the component that actually fails.
//!
//! See docs/implementation/entrypoints/check_components.md (Failure modes).

use cgp::prelude::*;

#[cgp_component(Greeter)]
pub trait CanGreet {
fn greet(&self);
}

#[cgp_auto_getter]
pub trait HasName {
fn name(&self) -> &str;
}

#[cgp_impl(new GreetHello)]
impl Greeter
where
Self: HasName,
{
fn greet(&self) {
let _ = self.name();
}
}

#[derive(HasField)]
pub struct Person {
pub age: u8,
}

delegate_components! {
Person {
GreeterComponent: GreetHello,
}
}

// `Person` cannot satisfy `GreetHello`'s `Self: HasName`, so the check fails here.
check_components! {
Person {
GreeterComponent,
}
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
error[E0277]: the trait bound `Person: CanUseComponent<GreeterComponent>` is not satisfied
--> tests/acceptable/check_components/missing_dependency.rs:55:9
|
55 | GreeterComponent,
| ^^^^^^^^^^^^^^^^ unsatisfied trait bound
|
help: the trait `HasField<Symbol<4, cgp::prelude::Chars<'n', cgp::prelude::Chars<'a', cgp::prelude::Chars<'m', cgp::prelude::Chars<'e', Nil>>>>>>` is not implemented for `Person`
but trait `HasField<Symbol<3, cgp::prelude::Chars<'a', cgp::prelude::Chars<'g', cgp::prelude::Chars<'e', Nil>>>>>` is implemented for it
--> tests/acceptable/check_components/missing_dependency.rs:41:10
|
41 | #[derive(HasField)]
| ^^^^^^^^
note: required for `Person` to implement `HasName`
--> tests/acceptable/check_components/missing_dependency.rs:26:1
|
26 | #[cgp_auto_getter]
| ^^^^^^^^^^^^^^^^^^
27 | pub trait HasName {
| ^^^^^^^
note: required for `GreetHello` to implement `IsProviderFor<GreeterComponent, Person>`
--> tests/acceptable/check_components/missing_dependency.rs:31:1
|
31 | #[cgp_impl(new GreetHello)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
...
34 | Self: HasName,
| ------- unsatisfied trait bound introduced here
= note: required for `Person` to implement `CanUseComponent<GreeterComponent>`
note: required by a bound in `__CheckPerson`
--> tests/acceptable/check_components/missing_dependency.rs:53:1
|
53 | / check_components! {
54 | | Person {
55 | | GreeterComponent,
56 | | }
57 | | }
| |_^ required by this bound in `__CheckPerson`
= note: this error originates in the derive macro `HasField` which comes from the expansion of the macro `check_components` (in Nightly builds, run with -Z macro-backtrace for more info)
9 changes: 9 additions & 0 deletions crates/tests/cgp-test-crate-a/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,12 @@ where
format!("ANNOUNCEMENT from {}!", self.name())
}
}

// A shared namespace downstream crates populate and join. It inherits the
// built-in `DefaultNamespace`, so a context joining it also inherits the standard
// defaults. `cgp-test-crate-b` registers a *local* component into it with
// `#[default_impl]` — orphan-safe because the crate owns the component key even
// though it does not own this namespace.
cgp_namespace! {
new AppNamespace: DefaultNamespace {}
}
Loading
Loading