Skip to content

Namespace macro refactoring#241

Merged
soareschen merged 10 commits into
mainfrom
namespace-redesign
Jun 28, 2026
Merged

Namespace macro refactoring#241
soareschen merged 10 commits into
mainfrom
namespace-redesign

Conversation

@soareschen

@soareschen soareschen commented Jun 28, 2026

Copy link
Copy Markdown
Collaborator

AI Overview

This PR reworks how CGP's namespace and path machinery is configured and expanded, and pulls derive_delegate out of the #[cgp_component] argument block into a standalone attribute. The headline change is grouped paths — a syntax that lets one delegation entry expand into the cartesian product of several path elements. Alongside it are a cluster of supporting refactors: the derive_delegate configuration moves to its own attribute, namespace-struct generation is decoupled from parent-namespace inheritance, and several macro-internal references are now resolved through the crate's export table rather than bare identifiers. The user-facing surface is small; most of the work is in the proc-macro core.

High-level concepts

The central new idea is that a single path key can name a set of components and a set of parameters at once. Where you previously wrote one delegation line per component, you can now write @app.[FooComponent, BarComponent].[u64, String]: DummyImpl and have the macro expand it into all four combinations (Foo×u64, Foo×String, Bar×u64, Bar×String). This is purely an expansion-time convenience — each combination still produces an independent DelegateComponent and IsProviderFor impl, exactly as if you had written them out by hand. The grouping composes, so two bracketed groups chained with . multiply together.

The second concept is that derive_delegate is now an ordinary attribute rather than a key inside the #[cgp_component { ... }] brace block. Instead of #[cgp_component { provider: ErrorRaiser, derive_delegate: UseDelegate<SourceError> }], you now write two lines: #[cgp_component(ErrorRaiser)] followed by #[derive_delegate(UseDelegate<SourceError>)]. Multiple delegates that used to be a bracketed list become repeated attributes, one per line. This makes #[cgp_component] read like the other attribute-driven CGP macros, and it lets the same delegate-deriving logic be shared more uniformly across #[cgp_type] and #[cgp_getter].

The third concept is a cleaner separation inside namespace tables between defining a namespace and inheriting from a parent. Creating the backing __XComponents struct is now the job of one function keyed on the new keyword, while wiring a namespace to its parent is the job of another keyed on the presence of a parent clause. The two concerns no longer share a code path.

Structural changes

The path parser gains a dedicated type and a richer enum. A new PathElementWithGenerics type pairs a path element with its optional generic binder, and the PathHead enum is reshaped so that grouping is explicit: the old grouping variant is renamed Nested, and a new Group(elements, tail) variant carries a bracketed list plus the rest of the path. Both expansion routines — into_paths in the macro core and path_head_to_prefix in the macro lib — learn to walk the Group variant and emit the cartesian product. A new Path! proc-macro is exposed that parses a single path expression into its type form.

The derive_delegate plumbing is relocated, not just renamed. The parsing logic now lives in CgpComponentAttributes, which collects each #[derive_delegate(...)] occurrence into a vector; the old bracketed-list Parse impl and the derive_delegate key (with its duplicate-key guard) are deleted from the component-args types. The evaluation step reads the delegates from the parsed attributes instead of from the component args. Documentation, the test utilities README, and the affected component definitions across cgp-error, cgp-type, cgp-handler, and cgp-run are all migrated to the new form, and the handler components additionally gain explicit #[prefix(@cgp.extra.handler in DefaultNamespace)] annotations.

The namespace table is split along the seam described above. build_namespace_struct now emits the __XComponents struct whenever new is present, independent of any parent, and build_parent_namespace_impl returns only the inheritance impl. The DelegateComponent trait's parameter is renamed Name → Key to match the type-level-table framing, and macro-internal references to Life, CanUseComponent, and friends are now resolved through the export_constructs! table so generated code points at the canonical paths.

The test suite tracks these changes. A new group.rs snapshot test exercises grouped-path expansion end to end, the multi_param tests are simplified away from heavy snapshot wrapping toward direct macro use, and the namespace snapshots are updated to reflect that new namespaces now always emit their backing struct.

Impacts

The user-facing migration is the most direct impact: any code using the brace-form derive_delegate must move to the standalone #[derive_delegate(...)] attribute. The brace key is now rejected with unknown key derive_delegate, so the change is a hard break for that syntax rather than a deprecation. All in-tree call sites are migrated, but downstream crates that used the old form will need the same edit.

Grouped paths reduce delegation boilerplate wherever the same provider serves many component-and-parameter combinations. The expansion is mechanical and equivalent to the longhand, so it introduces no new runtime behavior — the benefit is entirely in how much wiring a developer has to write and read.

The namespace refactor changes generated output in one visible way: a new namespace now always emits its __XComponents struct, even without a parent clause, which is why several snapshots grew a pub struct __…Components; line. This is consistent and intended, but it is a codegen difference worth noting for anyone comparing expanded output across versions.

A few sharp edges accompany the new flexibility, and they are worth keeping in mind. Specifying a parent namespace without new is no longer caught by a dedicated error; it now falls through to generated code that references an undefined struct, so the failure shows up as a confusing "cannot find type" message instead of a clear diagnostic. Grouped paths that reuse the same generic binder name across levels, or that contain an empty [] group, are not validated either — the former yields a duplicate-generic compile error and the latter silently expands to nothing. The new Path! macro currently parses only the single-path form and has no group support or tests. None of these block the feature, but they are the places where a user is most likely to hit an unhelpful error.

@soareschen soareschen merged commit 31c18fa into main Jun 28, 2026
5 checks passed
@soareschen soareschen deleted the namespace-redesign branch June 28, 2026 14:09
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