Refactor and move delegate_and_check_components! macro implementation to cgp-macro-core#240
Merged
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 onedelegate_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 adelegate_components!-style table (which builds the type-level lookup table viaDelegateComponent) plus acheck_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
DelegateTableparser rather than its own bespoke one. Before this PR, the macro lived behind a dedicated 158-line parser incgp-macro-lib/src/parse/delegate_and_check_components.rsthat defined its ownDelegateAndCheckSpec,DelegateAndCheckEntry, andDelegateAndCheckKeytypes. That parser recognized a restricted form of the delegation syntax, then rebuilt the standardDelegateKeyandDelegateEntryvalues just so it could callimpl_delegate_components. The whole file has been deleted, along with its registration in theparsemodule.The macro now parses the same
DelegateTablethatdelegate_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, andPathDelegateKey— and theDelegateTableitself each carry anattributes: Vec<Attribute>field populated byAttribute::parse_outerduring parsing. The check logic reads those attributes afterward rather than during a custom parse pass.New supporting types
A new
delegate_and_check_componentsmodule 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."CheckParamsAttributeis an enum withDefault,Skip, andMulti(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 amergemethod so that an attribute written on a bracketed group of keys can be combined with an attribute on an individual key inside that group, andmergedeliberately rejects the contradictory combination of#[skip_check]with#[check_params].KeyWithCheckParams,ToKeysWithCheckParams, andItemDelegateAndCheckComponentscarry the data through the rest of the pipeline.KeyWithCheckParamspairs a component key type with its resolvedCheckParamsAttributeand knows how to emit the corresponding check entries.ToKeysWithCheckParamsis a trait implemented across the delegation key and mapping types that walks the parsed table and flattens it into a list ofKeyWithCheckParams.ItemDelegateAndCheckComponentsis the top-level wrapper the entrypoint parses into; it owns the delegation table, derives the check-trait name, and assembles the finalCheckComponentsTable.Validation of unsupported attributes
Because the shared delegation types now accept attributes everywhere, the PR adds a
ValidateAttributestrait to police where attributes are actually allowed. The plaindelegate_components!macro supports no attributes at all, so it now callsvalidate_attributes()and rejects any stray attribute with a clear "unsupported attribute" error rather than silently parsing and discarding it. Withindelegate_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_identhelper in the check-components module. Bothcheck_components!anddelegate_and_check_components!derive a default check-trait name from the context type, differing only in the prefix —__Checkfor the former and__CanUsefor the latter. The helper takes the prefix as an argument so both call sites share one implementation. Alongside this, theCheckComponentsTable'swhere_clausefield changed from aWhereClauseto anOption<WhereClause>, removing the previous pattern of constructing an empty dummywhereclause with a call-site span when none was present, andCheckEntriesgained aDefaultderive 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 fullDelegateTable, 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 matchesdelegate_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, theFooTypesandFooGettersstruct definitions now appear at the top of the expansion and the per-component impls are regrouped; incheck_components.rs, the order of the generated__CanUseContextimpls 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-libintocgp-macro-core, consistent with keeping reusable macro logic in the core crate and leaving the thin entrypoints in the library crate.