Skip to content

Fix five macro defects and close test gaps found in the knowledge-base review - #273

Merged
soareschen merged 5 commits into
mainfrom
ai-updates-20260928
Oct 4, 2026
Merged

soareschen merged 5 commits into
mainfrom
ai-updates-20260928

Conversation

@soareschen

@soareschen soareschen commented Oct 4, 2026 •

Copy link
Copy Markdown
Collaborator

This PR fixes five defects in CGP's macros and adds the tests that a review of the knowledge base
found missing. Each fix replaces a confusing downstream compiler error, or a failure to compile
valid code, with either working code or a spanned error at the macro input. Every fix comes with a
regression test. The rest of the branch pins behavior that already worked but had no test, so a
later change cannot alter it silently.

The branch holds five commits on top of main (367f066), touching 64 files with 1,587 insertions
and 173 deletions. Most of the line count is new tests. The source changes are confined to
cgp-macro-core, cgp-extra-macro-lib, and one rustdoc comment in cgp-macro.

Defect fixes

Each fix below states the failure it removes and the behavior that replaces it. Most of the old
failures surfaced as compiler errors inside generated code rather than on the input the user wrote,
which made them hard to trace back to their cause.

#[cgp_component] rejects a name: parameter the trait does not declare

A keyed #[cgp_component] can give the component marker generic parameters, as in
name: ShapeComponent<T>. The marker struct is declared with those parameters, but the generated
impls name it using the trait's generics. A parameter that appears only on the name therefore
reached impl positions where nothing bound it, and compilation failed with
E0425: cannot find type `T` in this scope plus follow-on errors inside generated code.

ItemCgpComponent::preprocess now checks every lifetime, type, and const parameter of the name
against the trait's generics before generating anything. An undeclared parameter produces an error
spanned on that parameter:

the component name's parameter `T` is not a generic parameter of the trait `CanShape`

A const parameter in the name used to fail with an internal parse error,
failed to parse internal tokens to type `syn::generics::TypeParamBound` . It now gets a
user-facing error. On a trait that lacks the parameter, the error is the one above. On a trait that
declares it, the error is const generic parameters are not supported on CGP component traits,
spanned on the trait's parameter.

#[check_providers] works on a generic table

check_components! with #[check_providers(...)] generated a check trait whose supertrait named
the context directly: IsProviderFor<__Component__, Wrapper<T>, __Params__>. For a generic table
such as <T> Wrapper<T>, the T appeared in the trait header, where it is undeclared, and the
check failed with E0425 and an E0207 on the impl.

The check trait now takes the context as a parameter:

trait CheckWrapperProviders<__Component__, __Context__, __Params__: ?Sized>:
    IsProviderFor<__Component__, __Context__, __Params__> {}

impl<T> CheckWrapperProviders<NameGetterComponent, Wrapper<T>, ()>
for UseField<Symbol!("name")> {}

The table's generics stay on the impls that declare them. The plain check_components! form, which
asserts CanUseComponent on the context, is unchanged.

delegate_components! resolves a component used at an unsized parameter

The forwarding IsProviderFor impl that delegate_components! emits for each entry declared
__Params__ without ?Sized, although the trait itself declares it ?Sized. A component used at
an unsized argument, such as HasReference<'a, str>, has the params tuple (Life<'a>, str), which
is unsized. Such a component passed check_components!, which consults the provider's own impl.
But every call through the context failed, because the provider blanket impl goes through the
table's forwarding impl:

error[E0599]: the method `get_reference` exists for struct `Borrowed<'_>`, but its trait bounds were not satisfied
  = note: `str: Sized` which is required by `Borrowed<'_>: HasReference<'_, str>`

The forwarding impl now declares __Params__: ?Sized. This relaxes a bound, so it accepts strictly
more code. The new bound appears in every delegate_components! expansion snapshot, so it accounts
for the small edits under crates/tests/cgp-tests/tests/namespaces/ and basic_delegation/, in the
delegate_and_check_* files under checking/, and in dispatching/use_delegate_getter.rs. Some of
those edits are rustfmt rewrapping the longer impl headers.

Table structs declared by delegate_components! keep const parameters

delegate_components! declares a struct in two places: for a new target, and for a nested inner
table such as UseDelegate<new CountTable<...> { ... }>. Neither could carry a const parameter:

  • new target: <const N: u64> new ConstantTable<N> { ... } names N in argument position,
    without its kind, so the declared struct got a type parameter N, and any entry that passed N
    as a const failed with E0747.
  • Inner table: TypeGenerics rejected const N: usize in the table's own generic list. The
    parser tries the nested-table form speculatively and falls back to a plain type, so the user saw
    expected `,` at the inner table's name. Writing a bare N instead declared a type parameter
    and failed with E0747, as for a new target. The wiring value was also built from the
    definition list (CountTable<const N: usize>) rather than the argument list (CountTable<N>),
    which would have broken once the parse succeeded.

DelegateTable now restores a parameter's const kind from the table's generic list before declaring
the struct. TypeGenerics now accepts bare lifetimes, bare type parameters, and const N: T
parameters, and it still rejects bounds, defaults, and where clauses. The inner table's value is
built through build_table_type, so it uses the argument form.

#[cgp_auto_dispatch] generates names that cannot collide or break

#[cgp_auto_dispatch] emits one private free function per method, and it named that function after
the method itself. A module that already held an item of the same name, such as a free function
area beside a trait method area, failed with E0428: the name `area` is defined multiple times
and further errors from the clash. A method named with a raw identifier, such as r#type, also
made the macro panic. The computer's name was built from the method name with its r# prefix,
giving ComputeR#type, which Ident::new rejects.

The helper is now named __compute_{method}__, which follows the reserved double-underscore
convention of the other generated identifiers. Both the helper and the Compute{Method} computer
are built from the unrawed method name, so r#type yields __compute_type__ and ComputeType.
One clash remains possible: two #[cgp_auto_dispatch] traits in one module with the same method
name. That limitation is recorded in the knowledge base and left for a separate change, since
fixing it would rename the Compute{Method} provider.

Test coverage added

The review found constructs whose behavior was documented but not pinned by any test. The new tests
fall into three groups.

Parser rejections in cgp-macro-tests pin inputs that these macros refuse. cgp-macro-tests gains a
dev-dependency on cgp-extra-macro-lib so it can drive the extra macros directly.

  • #[cgp_type]: a non-trait item, an empty trait, a method, two associated types, and a generic
    or where-bounded associated type.
  • #[cgp_computer]: a self receiver, and a one-argument Result<T> alias, which the syntactic
    Result detection cannot parse (a known limitation, now pinned).
  • #[cgp_producer]: a parameter, a self receiver, an async function, and a generic
    parameter.
  • #[cgp_auto_dispatch]: an associated type, a method without a receiver, and a generic method.
  • #[blanket_trait]: a method without a body, a constant without a value, and a macro item.
  • #[cgp_component]: a repeated key, an unknown key, a keyed form without provider, and an
    undeclared name: parameter, plus a positive case for a declared one.

Expansion snapshots pin generated code that had no snapshot:

  • component_assoc_const: a component trait with an associated const, projected through every
    forwarding impl.
  • component_async: a component with an async method under #[async_trait], with .await
    forwarding.
  • const_custom_context: #[blanket_trait(Ctx)] with an associated constant and a renamed context
    parameter.
  • delegate_and_check_skip: a #[skip_check] entry beside checked entries.
  • mut_ref_auto and mut_option_str_auto: #[cgp_auto_getter] returning &mut T and
    Option<&mut str>.

Behavioral tests cover the remaining gaps:

  • path_macro: the bare Path! macro's segment classification, by type equality.
  • optional_builder: finalize_optional returns the name of a missing field, and with several
    unset it reports the last in declaration order.

The regression tests for the defect fixes are check_providers_generic, unsized_parameter,
const_generic_tables, auto_dispatch_method_name_in_scope, auto_dispatch_raw_method_name, and
the new #[cgp_component] name-parameter cases.

Documentation

The #[blanket_trait] rustdoc example in cgp-macro/src/lib.rs used the pre-release
#[trait_alias] name and the removed Async trait. It now shows a current #[blanket_trait] and
the __Context__ parameter the macro actually emits.

The knowledge base is updated in companion commit da168be of cgp-knowledge-base. That commit removes the Known
issues these fixes close, documents the replacement behavior in the reference and implementation
documents, and indexes the new tests and snapshots.

Compatibility

None of the changes alters the public API of a runtime crate. The observable differences are in
generated code:

  • The #[check_providers] check trait gains a __Context__ parameter. The trait exists only to
    make the compile-time assertion, so code outside the check_components! block has no reason to
    name it.
  • __Params__: ?Sized on the forwarding impl only relaxes a bound.
  • The #[cgp_auto_dispatch] helper function is private and now has a reserved name. Code in the
    same module that called the old helper by the method's name would stop compiling, but that helper
    was never documented as callable.

Verification

cargo test --workspace passes: 540 tests, 0 failures. cargo clippy --workspace --all-targets
reports no warnings. The re-accepted snapshots differ from main only by the ?Sized bound, the
#[check_providers] trait parameter, and rustfmt rewrapping.

The name: parameter errors were also checked in a separate crate. An undeclared lifetime or const
parameter gets the spanned error described above, and a trait that declares a const parameter gets
the const-generic refusal. The new parser-rejection test covers the undeclared type parameter.

soareschen and others added 5 commits September 27, 2026 03:31
Pin that finalize_optional names a missing field, and that with several
unset it names the last one in declaration order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HEMw6zp1hjNDZyxaKsszMj
Parser rejections for #[cgp_type], #[cgp_computer], #[cgp_producer],
#[cgp_auto_dispatch], and #[blanket_trait], with cgp-macro-tests gaining a
dev-dependency on cgp-extra-macro-lib. New snapshots cover the mutable
&mut T and Option<&mut str> auto getters, a #[blanket_trait(Ctx)] with an
associated constant, and a #[skip_check] entry in
delegate_and_check_components!. A type-equality test covers the bare
Path! macro.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HEMw6zp1hjNDZyxaKsszMj
Snapshot a component trait with an associated const and one with an async
method under #[async_trait], pinning const projection and .await forwarding
in every generated impl. Pin the #[cgp_component] rejections of a repeated
key, an unknown key, and a keyed form with no provider.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HEMw6zp1hjNDZyxaKsszMj
The example used the pre-release #[trait_alias] name and the removed Async trait.
Show a current #[blanket_trait] with the __Context__ parameter the macro emits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HEMw6zp1hjNDZyxaKsszMj
- #[cgp_component] rejects a name: parameter the trait does not declare,
  with a spanned error instead of E0425 in the generated impls.
- #[check_providers] takes the context as a trait parameter, so a
  generic table's parameters stay on the impls that declare them.
- The forwarding IsProviderFor impl of delegate_components! declares
  __Params__: ?Sized, so a component used at an unsized parameter
  resolves through a table.
- A table struct delegate_components! declares keeps a const
  parameter's kind, for a new target and for a nested inner table.
- #[cgp_auto_dispatch] names its per-variant helper __compute_{method}__
  and builds both generated names from the unrawed method identifier.

Each fix has a regression test, and the affected snapshots are
re-accepted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@soareschen soareschen added the AI label Oct 4, 2026
@soareschen
soareschen merged commit 076438c into main Oct 4, 2026
3 checks passed
@soareschen
soareschen deleted the ai-updates-20260928 branch October 4, 2026 04:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant