From b552454288733bc44053553219b1a2a3b1a727c7 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Sat, 26 Sep 2026 12:49:00 +0000 Subject: [PATCH] Qualify the cgp_auto_dispatch matcher call, and test open on a later parameter The generated dispatch method called the matcher as `Matcher::compute`, which is ambiguous with `E0034` in any module that also imports `CanCompute` or `CanComputeAsync`. Name the provider trait with inferred arguments instead, and add a test that imports both consumer traits. Also add a snapshot and behavioral test for `open` keys whose first segment is a per-entry generic, dispatching on the input alone. Co-Authored-By: Claude Opus 5.5 --- .../src/entrypoints/cgp_auto_dispatch.rs | 9 +- .../auto_dispatch_consumer_traits_in_scope.rs | 57 +++++++ .../tests/cgp-tests/tests/dispatching/mod.rs | 3 + .../tests/cgp-tests/tests/namespaces/mod.rs | 1 + .../tests/namespaces/open_later_param.rs | 150 ++++++++++++++++++ 5 files changed, 218 insertions(+), 2 deletions(-) create mode 100644 crates/tests/cgp-tests/tests/dispatching/auto_dispatch_consumer_traits_in_scope.rs create mode 100644 crates/tests/cgp-tests/tests/namespaces/open_later_param.rs diff --git a/crates/macros/cgp-extra-macro-lib/src/entrypoints/cgp_auto_dispatch.rs b/crates/macros/cgp-extra-macro-lib/src/entrypoints/cgp_auto_dispatch.rs index 84bd413a..072f3439 100644 --- a/crates/macros/cgp-extra-macro-lib/src/entrypoints/cgp_auto_dispatch.rs +++ b/crates/macros/cgp-extra-macro-lib/src/entrypoints/cgp_auto_dispatch.rs @@ -220,7 +220,10 @@ fn derive_blanket_impl(item_trait: &ItemTrait) -> syn::Result { let method_body = if signature.asyncness.is_some() { quote! { - #matcher::<#computer_ident>::compute_async( + // Name the provider trait so the call stays unambiguous when the + // consumer trait `CanComputeAsync` is also in scope. The `_` arguments + // are inferred, which avoids naming the HRTB-only `'__a__` lifetime. + <#matcher<#computer_ident> as AsyncComputer<_, _, _>>::compute_async( &(), ::core::marker::PhantomData::<()>, #args, @@ -228,7 +231,9 @@ fn derive_blanket_impl(item_trait: &ItemTrait) -> syn::Result { } } else { quote! { - #matcher::<#computer_ident>::compute( + // As above, qualified so that an imported `CanCompute` does not make + // the call ambiguous. + <#matcher<#computer_ident> as Computer<_, _, _>>::compute( &(), ::core::marker::PhantomData::<()>, #args, diff --git a/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_consumer_traits_in_scope.rs b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_consumer_traits_in_scope.rs new file mode 100644 index 00000000..f9442fa9 --- /dev/null +++ b/crates/tests/cgp-tests/tests/dispatching/auto_dispatch_consumer_traits_in_scope.rs @@ -0,0 +1,57 @@ +//! `#[cgp_auto_dispatch]` in a module that imports the computer consumer traits. +//! +//! The generated method calls the matcher through its provider trait, so importing +//! `CanCompute` and `CanComputeAsync` beside the prelude's `Computer` and +//! `AsyncComputer` leaves the call unambiguous for a synchronous method with an +//! argument and for an async `&self` method, both dispatched over `FooBar`. +//! +//! See cgp-knowledge-base/cgp/implementation/entrypoints/cgp_auto_dispatch.md. + +// Only their presence in scope matters: the generated calls must not resolve to them. +#[allow(unused_imports)] +use cgp::extra::handler::{CanCompute, CanComputeAsync}; +use cgp::prelude::*; +use futures::executor::block_on; + +use super::types::{Bar, Foo, FooBar}; + +#[cgp_auto_dispatch] +pub trait CanGreet { + fn greet(&self, name: &str) -> String; +} + +impl CanGreet for Foo { + fn greet(&self, name: &str) -> String { + format!("foo greets {name}") + } +} + +impl CanGreet for Bar { + fn greet(&self, name: &str) -> String { + format!("bar greets {name}") + } +} + +#[cgp_auto_dispatch] +#[async_trait] +pub trait CanCall { + async fn call(&self) -> &'static str; +} + +impl CanCall for Foo { + async fn call(&self) -> &'static str { + "foo" + } +} + +impl CanCall for Bar { + async fn call(&self) -> &'static str { + "bar" + } +} + +#[test] +fn test_consumer_traits_in_scope() { + assert_eq!(FooBar::Bar(Bar).greet("ann"), "bar greets ann"); + assert_eq!(block_on(FooBar::Foo(Foo).call()), "foo"); +} diff --git a/crates/tests/cgp-tests/tests/dispatching/mod.rs b/crates/tests/cgp-tests/tests/dispatching/mod.rs index f5d8072b..f9d3221c 100644 --- a/crates/tests/cgp-tests/tests/dispatching/mod.rs +++ b/crates/tests/cgp-tests/tests/dispatching/mod.rs @@ -32,6 +32,9 @@ pub mod auto_dispatch_async_self_mut_only; pub mod auto_dispatch_async_self_only; pub mod auto_dispatch_async_self_ref_only; +// `#[cgp_auto_dispatch]` beside imported `CanCompute`/`CanComputeAsync`. +pub mod auto_dispatch_consumer_traits_in_scope; + // The `UseDelegate` dispatch provider and the `UseDelegate`-table form of // `delegate_components!` (this concept owns those snapshots). pub mod use_delegate_getter; diff --git a/crates/tests/cgp-tests/tests/namespaces/mod.rs b/crates/tests/cgp-tests/tests/namespaces/mod.rs index 5b5fdc8f..ea9f6c4e 100644 --- a/crates/tests/cgp-tests/tests/namespaces/mod.rs +++ b/crates/tests/cgp-tests/tests/namespaces/mod.rs @@ -18,6 +18,7 @@ pub mod multi_param_namespace; pub mod multi_param_open; pub mod namespace_group; pub mod open_dispatch; +pub mod open_later_param; pub mod prefix_default_namespace; pub mod redirect_lookup; diff --git a/crates/tests/cgp-tests/tests/namespaces/open_later_param.rs b/crates/tests/cgp-tests/tests/namespaces/open_later_param.rs new file mode 100644 index 00000000..26c4931b --- /dev/null +++ b/crates/tests/cgp-tests/tests/namespaces/open_later_param.rs @@ -0,0 +1,150 @@ +//! `open` dispatch on a component's second type parameter alone. +//! +//! The redirect appends every type parameter to the lookup path, so a lookup of +//! `CanDescribe` follows `@DescriberComponent.Code.Input`. A key whose +//! first segment is a per-entry generic, `@DescriberComponent. Code.u64`, +//! matches every `Code` and dispatches on the input, which is the `open` form of a +//! `UseInputDelegate` table. The test calls one context with two codes per input +//! and checks that the input alone selects the provider. The `delegate_components!` +//! snapshot pins the generic-first-segment expansion; the component and providers +//! are incidental scaffolding. +//! +//! See cgp-knowledge-base/cgp/implementation/entrypoints/delegate_components.md. + +use core::marker::PhantomData; + +use cgp::prelude::*; +use cgp_macro_test_util::snapshot_delegate_components; + +// Incidental: a component with a code parameter and an input parameter. +#[cgp_component(Describer)] +pub trait CanDescribe { + fn describe(&self, code: PhantomData, input: Input) -> String; +} + +// Incidental: one provider per input type, each generic over the code. +#[cgp_impl(new DescribeNumber)] +impl Describer { + fn describe(&self, _code: PhantomData, input: u64) -> String { + format!("number {input}") + } +} + +#[cgp_impl(new DescribeText)] +impl Describer { + fn describe(&self, _code: PhantomData, input: String) -> String { + format!("text {input}") + } +} + +pub struct Short; + +pub struct Long; + +pub struct App; + +snapshot_delegate_components! { + delegate_components! { + App { + open DescriberComponent; + + @DescriberComponent. Code.u64: + DescribeNumber, + @DescriberComponent. Code.String: + DescribeText, + } + } + + expand_open_later_param(output) { + insta::assert_snapshot!(output, @" + impl DelegateComponent for App { + type Delegate = RedirectLookup>; + } + impl<__Context__, __Params__> IsProviderFor + for App + where + RedirectLookup< + App, + PathCons, + >: IsProviderFor, + {} + impl< + Code, + __Wildcard__, + > DelegateComponent< + PathCons>>, + > for App { + type Delegate = DescribeNumber; + } + impl< + Code, + __Wildcard__, + __Context__, + __Params__, + > IsProviderFor< + PathCons>>, + __Context__, + __Params__, + > for App + where + DescribeNumber: IsProviderFor< + PathCons>>, + __Context__, + __Params__, + >, + {} + impl< + Code, + __Wildcard__, + > DelegateComponent< + PathCons>>, + > for App { + type Delegate = DescribeText; + } + impl< + Code, + __Wildcard__, + __Context__, + __Params__, + > IsProviderFor< + PathCons>>, + __Context__, + __Params__, + > for App + where + DescribeText: IsProviderFor< + PathCons>>, + __Context__, + __Params__, + >, + {} + ") + } +} + +check_components! { + App { + DescriberComponent: [ + (Short, u64), + (Long, u64), + (Short, String), + (Long, String), + ], + } +} + +#[test] +fn test_open_later_param() { + let app = App; + + assert_eq!(app.describe(PhantomData::, 7), "number 7"); + assert_eq!(app.describe(PhantomData::, 7), "number 7"); + assert_eq!( + app.describe(PhantomData::, "hi".to_owned()), + "text hi" + ); + assert_eq!( + app.describe(PhantomData::, "hi".to_owned()), + "text hi" + ); +}