Skip to content

Migrate remaining macro implementations and polish up cgp-macro-core#243

Merged
soareschen merged 29 commits into
mainfrom
migrate-macros-to-core
Jun 28, 2026
Merged

Migrate remaining macro implementations and polish up cgp-macro-core#243
soareschen merged 29 commits into
mainfrom
migrate-macros-to-core

Conversation

@soareschen

@soareschen soareschen commented Jun 28, 2026

Copy link
Copy Markdown
Collaborator

AI Summary

This PR consolidates how CGP's procedural macros are organized and how the code they generate is written, on the branch migrate-macros-to-core (29 commits). The headline change is that the real code-generation logic for several macros moves out of the thin entrypoint crate (cgp-macro-lib) and into the shared core crate (cgp-macro-core), where the rest of CGP's macro machinery already lives. Alongside that move, the PR retires the Greek-letter type aliases that CGP used to shorten compiler error messages, switches generated code to fully-qualified construct paths, and replaces a large body of hand-written runtime tests with golden snapshot tests.

None of these changes alter the public CGP surface that users write against. The macros you invoke — #[cgp_component], #[cgp_type], #[derive(HasField)], delegate_components!, and the rest — keep the same names and behavior. What changes is the internal structure of the macro implementation, the spelling of the types that appear in compiler output, and the way the generated code references CGP's own constructs.

High-Level Concepts

The work is best understood as four parallel threads: relocating macro logic, flattening the entrypoint crate, removing the Greek aliases, and rebuilding the test suite around snapshots. They are independent in intent but were carried out together because they all touch the boundary between cgp-macro-lib and cgp-macro-core.

Macro logic moves into cgp-macro-core. Previously, the generation logic for the data macros (#[derive(HasField)], #[derive(HasFields)], #[derive(CgpData)], the builder and extractor derives) and for #[cgp_type] lived inside cgp-macro-lib. This PR moves that logic into cgp-macro-core under new modules — types/cgp_data (with record, variant, and item submodules), types/cgp_type, types/product, and types/sum. The move is built around two new abstractions, ItemCgpType and ItemCgpRecord, that model a parsed macro input as a structured item that knows how to emit its own generated items. After the move, cgp-macro-lib keeps only a thin wrapper per macro that forwards to the core implementation.

The entrypoint crate is flattened. cgp-macro-lib used to nest every macro entry point under an entrypoints/ subdirectory and re-export them through a single pub use crate::entrypoints::*. This PR dissolves that subdirectory: each macro now lives in a top-level module (cgp_component, cgp_fn, delegate_components, and so on) re-exported directly from lib.rs. The crate's role narrows to being the proc-macro-facing veneer over cgp-macro-core.

Greek-letter aliases are removed. CGP historically defined its type-level constructs under Greek names — Cons as π, Nil as ε, Symbol as ψ, Chars as ζ, Either as σ, Void as θ, Index as δ, and others — so that deeply nested type-level lists and strings would print more compactly in compiler errors. This PR deletes those aliases and defines each construct directly under its real, readable name. The prelude stops re-exporting the Greek symbols, and the macros now emit the plain names. Error messages get longer but immediately legible, trading compactness for clarity.

Generated code becomes fully qualified. The macros now refer to CGP's own constructs through fully-qualified paths rather than relying on whichever names happen to be imported at the call site. To support this, the macro core's export_constructs! list is expanded substantially — adding Void, Either, Field, the full family of data traits (HasFields, FromFields, ExtractField, HasBuilder, and so on), and the UseType / TypeProvider / WithProvider providers — so every construct the generated code might reference has a canonical export to point at.

Structural Changes

The crate layout gains one new crate and reorganizes two existing macro crates. A new cgp-base-extra crate is added to the workspace (registered in both the root Cargo.toml members list and the workspace dependency table). It re-exports base_types, component, and the cgp-type constructs, and exposes a macro_prelude module that gathers the type-level exports macros need to resolve their fully-qualified references. The existing cgp-base crate is extended to re-export cgp-base-types as base_types and cgp-component as component, giving the generated code stable module paths to anchor on.

Within cgp-macro-core, the new types/cgp_data, types/cgp_type, types/product, and types/sum modules absorb the migrated logic, and types/mod.rs is updated to declare them. The Symbol parser in the macro core is reworked to parse a string literal directly (storing the value as a String plus a Span) instead of consuming an Ident, which makes Symbol!("...") parsing more robust. Within cgp-macro-lib, the entrypoints/ and type_component/ directories are deleted and their surviving contents promoted to top-level modules.

The test suite is restructured from runtime assertions into golden snapshots. The HasField and HasFields tests are removed from crates/tests/cgp-tests/src/tests/ and reborn under crates/tests/cgp-tests/tests/extensible_data_tests/, where they capture the macros' generated output inline via the insta snapshot crate. This is backed by three new snapshot macros in the test-util crate — snapshot_derive_has_field!, snapshot_derive_has_fields!, and snapshot_derive_cgp_data! — documented in an expanded cgp-macro-test-util README. The growth in test line counts (for example, the has_field/chain tests expand from roughly 300 to 600+ lines) reflects the golden output now being checked in alongside each test.

Impacts

The changes are overwhelmingly internal, but they ripple outward in a few visible ways. The list below frames each impact and who it affects; nothing here requires action from code that simply uses CGP through the documented macros.

  • Compiler error messages change appearance. Type-level lists and strings now print with their full names (Cons, Nil, Symbol, Chars, Either, Index) instead of Greek letters. Errors become more readable but visually longer. Anyone who had learned to recognize π/ε/ψ in diagnostics will see the spelled-out forms instead.
  • The Greek aliases are no longer importable. Code that explicitly referenced the alias names through the prelude (δ, ε, ζ, θ, π, σ, ψ, ω) will no longer find them. This is the one change that could break downstream code, though such direct use was always unusual — these were display conveniences, not the documented API.
  • A new cgp-base-extra crate joins the workspace. It is a small re-export and macro-prelude crate that underpins the fully-qualified code generation. Workspace builds will compile one additional crate.
  • Generated code is more self-contained. Because the macros now emit fully-qualified paths, the code they generate is less sensitive to what the user has imported at the call site, reducing a class of "name not in scope" errors in generated output.
  • Obsolete macro internals are removed. The dead cgp_preset, cgp_inherit, re_export_imports, replace_with, and type_component modules are deleted from cgp-macro-lib. These were not exposed as public proc macros on either branch, so their removal is dead-code cleanup with no user-facing effect.
  • Macro maintenance is centralized. With the data and type macro logic living in cgp-macro-core beside the rest of the machinery, future changes touch one crate rather than two, and cgp-macro-lib shrinks to a predictable set of thin forwarding wrappers.
  • Regression coverage shifts to snapshots. The macro output is now pinned by golden snapshots rather than runtime behavior alone. This catches unintended changes in generated code precisely, but it also means intentional changes to macro output will require refreshing the snapshots as part of the workflow.
  • Symbol! parsing is sturdier. Parsing a string literal directly removes the previous dependency on the input tokenizing as an identifier, which makes symbol construction more predictable for unusual field names.

@soareschen soareschen merged commit f4460fb into main Jun 28, 2026
5 checks passed
@soareschen soareschen deleted the migrate-macros-to-core branch June 28, 2026 23:07
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