Skip to content

Refactor and move delegate_and_check_components! macro implementation to cgp-macro-core#240

Merged
soareschen merged 19 commits into
mainfrom
refactor-delegate-and-check-components
Jun 23, 2026
Merged

Refactor and move delegate_and_check_components! macro implementation to cgp-macro-core#240
soareschen merged 19 commits into
mainfrom
refactor-delegate-and-check-components

Conversation

@soareschen

@soareschen soareschen commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

AI Summary

This PR rebuilds the delegate_and_check_components! macro so that it reuses the existing delegation machinery instead of maintaining its own parallel parser. The macro previously had a hand-written parser that understood only a narrow slice of the delegation syntax and then translated it into the real delegation types. That duplicate path is now gone. The macro parses an ordinary delegation table — the same one delegate_components! uses — and derives its compile-time checks from that single source of truth. The net effect is less duplicated code, broader syntax support, and clearer error messages, at the cost of some changes in the order of generated code.

Background: what the macro does

delegate_and_check_components! is the recommended way to wire components onto a context, because it both performs the wiring and verifies it in one step. It expands into a delegate_components!-style table (which builds the type-level lookup table via DelegateComponent) plus a check_components!-style block (which asserts at compile time that every wired component is actually implementable for the context, with all transitive dependencies satisfied). Understanding the PR means understanding that the macro has always produced these two halves; what changed is how it gets there.

The core change: one parser instead of two

The central structural change is that the macro now reuses the shared DelegateTable parser rather than its own bespoke one. Before this PR, the macro lived behind a dedicated 158-line parser in cgp-macro-lib/src/parse/delegate_and_check_components.rs that defined its own DelegateAndCheckSpec, DelegateAndCheckEntry, and DelegateAndCheckKey types. That parser recognized a restricted form of the delegation syntax, then rebuilt the standard DelegateKey and DelegateEntry values just so it could call impl_delegate_components. The whole file has been deleted, along with its registration in the parse module.

The macro now parses the same DelegateTable that delegate_components! already uses, then derives the checks from it. The entrypoint shrank from roughly 93 lines of manual translation down to 20 lines: parse the table, derive a check table from it, evaluate the delegation table the normal way, and emit both. Because the delegation half is now produced by the standard table evaluator (item.table.eval()) rather than a special-purpose helper, the macro automatically inherits every delegation feature the evaluator supports.

To make this work, the check-related attributes had to become genuine Rust attributes carried by the delegation types. Previously the macro's private parser peeked for # tokens and pulled #[check_params], #[skip_check], and #[check_trait] out by hand. Now the shared delegation key types — SingleDelegateKey, MultiDelegateKey, and PathDelegateKey — and the DelegateTable itself each carry an attributes: Vec<Attribute> field populated by Attribute::parse_outer during parsing. The check logic reads those attributes afterward rather than during a custom parse pass.

New supporting types

A new delegate_and_check_components module was added to the reusable core crate (cgp-macro-core), holding the logic that turns a parsed delegation table into check entries. This module did not exist before; the equivalent behavior was scattered through the deleted parser and the old entrypoint. It introduces four pieces that together translate "a delegation table with check attributes" into "a set of compile-time checks."

CheckParamsAttribute is an enum with Default, Skip, and Multi(params) variants that models the three states a key can be in: check it with no extra generic parameters, skip it entirely (#[skip_check]), or check it against an explicit list of parameter tuples (#[check_params(...)]). It includes a merge method so that an attribute written on a bracketed group of keys can be combined with an attribute on an individual key inside that group, and merge deliberately rejects the contradictory combination of #[skip_check] with #[check_params].

KeyWithCheckParams, ToKeysWithCheckParams, and ItemDelegateAndCheckComponents carry the data through the rest of the pipeline. KeyWithCheckParams pairs a component key type with its resolved CheckParamsAttribute and knows how to emit the corresponding check entries. ToKeysWithCheckParams is a trait implemented across the delegation key and mapping types that walks the parsed table and flattens it into a list of KeyWithCheckParams. ItemDelegateAndCheckComponents is the top-level wrapper the entrypoint parses into; it owns the delegation table, derives the check-trait name, and assembles the final CheckComponentsTable.

Validation of unsupported attributes

Because the shared delegation types now accept attributes everywhere, the PR adds a ValidateAttributes trait to police where attributes are actually allowed. The plain delegate_components! macro supports no attributes at all, so it now calls validate_attributes() and rejects any stray attribute with a clear "unsupported attribute" error rather than silently parsing and discarding it. Within delegate_and_check_components!, the same validation rejects attributes in positions that do not yet support checks — specifically redirect mappings and the statement forms (for / namespace / open), which still produce delegation impls but generate no check entries. The intent is that an attribute that cannot take effect is reported as an error instead of being quietly ignored.

Shared check-trait naming helper

A small piece of duplicated logic was extracted into a shared derive_check_trait_ident helper in the check-components module. Both check_components! and delegate_and_check_components! derive a default check-trait name from the context type, differing only in the prefix — __Check for the former and __CanUse for the latter. The helper takes the prefix as an argument so both call sites share one implementation. Alongside this, the CheckComponentsTable's where_clause field changed from a WhereClause to an Option<WhereClause>, removing the previous pattern of constructing an empty dummy where clause with a call-site span when none was present, and CheckEntries gained a Default derive so the new code can accumulate entries into an empty collection.

Impacts

The impacts below follow from the refactor; most are improvements, and two are behavioral changes worth calling out explicitly.

Reduced duplication and a single source of truth. Roughly 280 lines were removed against 466 added, but the removals delete an entire parallel parser while the additions are mostly reusable, well-factored types in the core crate. There is now one parser for delegation syntax, so the two macros can no longer drift apart.

Broader syntax support for delegate_and_check_components!. Because the macro now parses a full DelegateTable, it accepts everything that table supports — array/bracket key grouping, path and redirect mappings, statement forms, and nested table definitions — rather than the restricted subset the old custom parser understood. The delegation half of the output is produced by the standard evaluator, so its behavior matches delegate_components! exactly.

Checks are not generated for every construct. Redirect mappings and statement forms (for / namespace / open) still emit delegation impls but intentionally produce no check entries, and this limitation is now explicit in code comments and enforced by attribute validation. Attributes on these constructs are rejected rather than ignored, so a user cannot mistakenly believe a check is running when it is not.

Clearer, earlier error messages. Several conditions that were previously silent or vague are now explicit errors: an unsupported attribute on delegate_components!, more than one check attribute on a key, #[skip_check] arguments where none are allowed, and the contradictory #[skip_check] + #[check_params] combination. The macro fails loudly at the offending span instead of discarding input.

Generated-code ordering changed. Because the delegation half now runs through the standard table evaluator and the check entries are accumulated in a different traversal order, the order of the emitted impls and helper struct definitions changed. This is visible in two updated snapshot tests: in use_delegate/getter.rs, the FooTypes and FooGetters struct definitions now appear at the top of the expansion and the per-component impls are regrouped; in check_components.rs, the order of the generated __CanUseContext impls shifted. These changes are ordering-only and do not affect the meaning of the generated code, but any downstream tooling that depends on declaration order should be aware of them.

Code relocated into the core crate. The check-derivation logic moved from cgp-macro-lib into cgp-macro-core, consistent with keeping reusable macro logic in the core crate and leaving the thin entrypoints in the library crate.

@soareschen soareschen merged commit 4008714 into main Jun 23, 2026
5 checks passed
@soareschen soareschen deleted the refactor-delegate-and-check-components branch June 23, 2026 21:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant