Harden #[use_type] grounding, lift nested delegation tables, and close test gaps - #264
Merged
Merged
Conversation
`crates/tests/README.md` restated its own `AGENTS.md` at length — the two crates' jobs, where post-codegen failures live, the target layout, the snapshot ownership rules. It is now the map of what is here and how to run it, and defers the rules to `AGENTS.md`, which is where a reader is sent for them anyway. `cgp-macro-test-util/README.md` duplicated the knowledge base's `snapshot_macros.md` across fifteen near-identical per-macro sections, plus a guide to a migration that has long since happened. It keeps the macro table, one canonical invocation, the `insta` workflow, and the two notes that are genuinely its own, and points at the implementation document for how the macros are built and at `crates/tests/AGENTS.md` for when to snapshot at all — 592 lines to 112. The crate-level `README.md` for `cgp`, which is the crates.io front page, still called CGP "a new modular programming paradigm in Rust" — the positioning the knowledge base records as retired. It now opens with the current line and points at the knowledge base for the exhaustive semantics. In `AGENTS.md`, the knowledge-base links spelled their full paths as link text, which read as noise where a name would do; a code span split across two lines in `crates/tests/AGENTS.md` is closed on one, per the backtick rule. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two positions where an imported alias was left unresolved, both
inconsistencies rather than boundaries: the alias already resolved in
neighbouring positions, so the substitution was incomplete rather than
deliberately narrow.
An equality pin's right-hand side was matched by whole-type equality, so
`{HashedPassword = Password}` grounded but `{Transaction = Tx<Db>}` emitted a
bare `Db` that resolved to nothing. `find_type_equality` now runs
`SubstituteAbstractTypes` over the right-hand side, which grounds an alias
wherever it occurs inside it — nested in a generic argument or inside a
qualified path — and collapses the two cases into one rule. The pinned alias
is excluded from its own substitution, so a degenerate `{Foo = Foo}` stays an
unresolved-name error rather than becoming a vacuous bound.
An alias qualifying an expression path was also passed through, even though a
`let` annotation in the same body was rewritten. `SubstituteAbstractTypes`
gains `visit_expr_path_mut`, rewriting `Transaction::begin_from(pool)` into
the qualified-type form `<<Self as Trait>::Transaction>::begin_from(pool)`.
The boundary kept is arity: a bare single-segment path in expression position
names a value, which an abstract type can never be, so an alias sharing its
name with a unit struct still resolves to the struct.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EVFcwx5jehpRRFieG8L8Q1
The expression-path rewrite mutated the node and returned, skipping the recursion, so an alias in a *later* segment's generic arguments was left unsubstituted: `Transaction::tagged::<Db>(db)` emitted a bare `Db` and failed with `E0425`. Falling through to the recursion fixes it and cannot loop, because the rewritten node now carries a `qself` and both visit guards require `qself: None`. The asymmetry that hid this is worth noting: `visit_type_mut` uses the same return-after-mutate shape and is correct there, because its guard requires `PathArguments::None` so the node it replaces has no children to visit. The expression case accepts later segments that do carry arguments, so it needs the recursion the type case does not. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EVFcwx5jehpRRFieG8L8Q1
…, merge pins
Four defects, found by asking of each position whether the construct already
handled the neighbouring one.
**A grounding cycle is now rejected at macro time.** Two imports whose contexts
resolve through each other (`HasA.A in B, HasB.B in A`) have no valid grounding
order, and neither does the one-node `in A`. `forbid_grounding_cycles` builds the
spec dependency graph, searches it depth-first, and reports a back edge on the
token that closes the loop, naming the cycle (`` `B` -> `A` -> `B` ``).
The check has to run *before* grounding, because the obvious after-the-fact test
finds nothing. Grounding does not leave a cyclic alias bare: each pass substitutes
against the previous snapshot, in which the other context is itself ungrounded, so
every pass wraps one more layer and the alias ends up buried four levels down a
projection. Whether a context is "still a bare alias" is therefore the wrong
question; the spec graph states the problem directly.
**An alias in the imported trait's own generic arguments is now grounded.** It was
resolved in the context and in a pin's right-hand side but not here, which is two
of the three positions it can occupy — a missing case rather than a boundary. So
`HasDbType.Db, HasPoolType<Db>.Pool` now projects against
`HasPoolType<<Self as HasDbType>::Db>` instead of emitting a bare `Db` that
resolves to nothing.
Grounding deliberately still stops at the trait path's *head*. That is not the
missing third case: the head must name a trait and an alias names a type, so
grounding it would turn a clear `E0405` into an unparseable projection.
**A pin on a generic trait emitted invalid Rust.** `HasFooType<u8>.{Foo = u32}`
built `HasFooType<u8><Foo = u32>` and died inside the macro with a bare "failed to
parse internal tokens to type `syn::generics::WherePredicate`" naming no cause.
Pins now merge into the trait path's existing argument list, so the bound reads
`HasFooType<u8, Foo = u32>` — and `HasRefType<'a, Ref = &'a str>` where a lifetime
must keep leading the list.
A circular *pin* is deliberately left to the compiler, unlike a circular context.
It grounds in one pass and means something well defined — two abstract types are
equal, which a context may satisfy — so only the solver's ability to discharge it
fails, and that is not knowable at expansion time.
`bare_alias_ident` is factored out of the substitution visitor so the new
`CollectBareAliases` reads dependency edges by exactly the rule the substitution
rewrites by; a reference the check cannot see is precisely the input grounding
then fails on. `groundable_types` names the positions once, so the grounding pass,
the emitted bounds, and the cycle check cannot disagree about what grounding
reaches.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017xGdHGrJEtoZjSmFEhsChw
Grounding used one traversal to detect cycles and a second, a fixpoint, to
resolve. The fixpoint substituted each pass against a *snapshot* of the previous
pass, in which a dependency was still ungrounded — which is the mechanism that
built the spiral the cycle check exists to prevent. Rejecting cycles upstream made
that unreachable, but the robustness then came from a precondition in a
neighbouring function rather than from the code itself.
Both collapse into `ground_specs`, because they answer one question: grounding a
spec means resolving its dependencies first, so the order it needs is a
topological order, and a cycle is exactly the graph having no such order. One
depth-first walk grounds each spec in post-order and reports a back edge as the
cycle. Two properties are now structural instead of resting on an argument about
iteration counts: the walk visits each spec once, so it terminates; and a spec is
substituted only against fully-grounded specs, so a replacement can never carry a
bare alias. The shape is gone rather than guarded — there is no previous pass to
read a half-resolved spec out of.
Resolution keeps source order in its output, so the emitted supertraits and
predicates still read in the order the author wrote the imports.
`resolve` tests `!= Unvisited` rather than `== Done`. An in-progress spec cannot
arrive today, since the call site reports the cycle before recursing, but
descending on one would recurse forever; a future call site that skipped the guard
now fails as an ungrounded spec at the end of the walk — diagnosable — instead of
as a stack overflow inside the compiler.
`PathWithTypeArgs::to_bound_tokens` takes over merging associated-type bindings
into a trait path's own argument list. The caller previously had to know that
`ToTokens` renders those arguments as a *trailing* group, and so that appending
bindings after the whole path yields the invalid `Trait<A><Item = X>`. Keeping the
merge with the rendering it must agree with stops a caller from reintroducing that,
and collapses the pinned and unpinned cases into one path, since an empty binding
list renders the path unchanged.
`is_changed` is removed from the substitution visitor: it existed only to drive the
fixpoint and was left written but never read, which clippy misses on a `pub` field.
Tests cover what the refactor claims. Order-independence is pinned for a nested pin
whose right-hand side names an alias declared *after* it, and for specs split across
stacked `#[use_type]` attributes in both orders — the latter a documented promise
with nothing behind it, exercised on the case where it could plausibly break
(`{Foo = Vec<Bar>}` above `HasBarType.Bar`). All four arrangements emit the same
bound. `to_bound_tokens` gains unit tests in the file that already covers its type,
including that its output parses as a trait bound at all, which is the property the
two-group form violated and which no unit test pinned.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017xGdHGrJEtoZjSmFEhsChw
`DelegateTable::eval` collects every nested `Wrapper<new Inner { … }>` value and
emits its struct and `DelegateComponent` impls; two paths that reach the same
value did not, so the table was parsed, named in the entry's `Delegate`, and then
never emitted. Both failed with `E0425` on the dropped struct — a message that
reads like a typo rather than a lost table.
`NamespaceTable::eval` was the first. A namespace body parses into the same
`DelegateEntries` a delegation table does, so it accepts the value either way;
what it lacked was the extraction. Skipping it was an inconsistency rather than a
boundary — the neighbouring case was already handled — which is the test the
implementation documentation prescribes for exactly this judgement. `eval` now
runs the same walk, and `EvaluatedNamespaceTable` grows an `inner_structs` field
rendered after the trait, mirroring `EvaluatedDelegateTable`. The form earns its
keep here rather than merely working: the dispatch table lives in the namespace,
so every context that joins inherits it without restating it.
`DelegateEntries::extract_inner_tables` was the second, and it reaches
`delegate_components!` itself. It walked mappings but not statements, so a value
inside a `for` loop's body was dropped the same way. It now walks both, with
`ExtractInnerDelegateTables` implemented for the statement forms — `for`
delegating to its body's mappings, `namespace` and `open` carrying no value.
Writing such a value well-formed takes one specific shape, and it is worth
knowing before reaching for the form. The loop binds a provider variable the entry
must mention, or the generated impl leaves it unconstrained; but the loop's
variables are not in scope inside the lifted table's own impls, so mentioning it
only there fails on both counts at once. Declaring it as the inner table's own
generic — `new Inner<Provider>` — puts it in both places. The combination stays
redundant in practice, since a loop already yields one provider per key, so the
form is kept correct rather than recommended.
Seven tests close gaps the audit of this grammar turned up, all of them cases
where documentation made a claim nothing pinned.
`redirect_mapping` covers the `=>` operator written on a context, which appeared
nowhere in the suite. Its second snapshot is the `open` spelling of its first
entry, so the two goldens are identical modulo the context name — which pins the
`open C;` ≡ `C => @C,` equivalence by construction rather than asserting it.
`combined_forms` puts every body form in one block, the only place the
composition itself is checked. `namespace_nested_table` and
`for_loop_nested_table` guard the two halves of the fix above; both were confirmed
to fail without it.
Three rejection cases pin body-grammar diagnostics whose wording is misleading
enough to be worth guarding: a statement after a mapping and a braced path group
followed by more path, both reported as `expected ':'`, and a bounded generic list
on a nested table, reported as `expected ','` because the value parser falls back
to reading the whole value as a plain type.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four of these cover behaviour that was implemented and untested; the fifth pins a defect rather than a feature. `basic_delegation/self_local_assoc_type.rs` covers the exemption in `#[cgp_impl]`'s `Self` rewrite: a `Self::Assoc` naming an associated type the block itself declares is left alone, so the emitted provider impl keeps it, while every other `Self` in the same body still becomes the context. `implicit_arguments/cgp_fn_mref.rs` covers the `MRef<'_, T>` implicit argument — the one reference-shaped mode with no mutable mirror, which stays a shared `HasField` read even under a `&mut self` receiver. `parser_rejections/use_provider.rs` pins the one-provider-per-attribute rule, together with the counterpart that stops it being read too broadly: `+` continues one provider's bound list and is accepted, while a comma is not. `parser_rejections/delegate_and_check_components.rs` covers the check attributes the macro refuses, including a `#[skip_check]` merged with a `#[check_params]` across a list key and its element — a conflict that exists only after the merge, so neither attribute is wrong on its own. `higher_order_providers/lifetime_inner_provider.rs` snapshots current, wrong behaviour: a higher-order provider over a lifetime-carrying component gets no `IsProviderFor` counterpart on its inner bound, because the rewrite reads the bound's first generic argument as the context and finds a lifetime there. The stack compiles and runs; what is lost is the propagation that lets `#[check_providers]` localize a broken layer. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011BfN1sjgaBAA2sEVqVNeig
Add the missing coverage for the extensible-data derives, and capture the first invalid-expansion case. Three building-block derives had no test anywhere — `#[derive(BuildField)]`, `#[derive(ExtractField)]`, and `#[derive(CgpRecord)]` — even though two of them are the form the concept documentation shows. Give the first two a snapshot macro of their own, alongside one for `#[derive(FromVariant)]`, so that each slice's defining claim is checkable rather than asserted: the snapshot shows the derive emitting its own items and *not* the neighbouring ones. `CgpRecord` and `CgpVariant` get no macro, since they emit what `CgpData` emits on the same shape and a snapshot would only duplicate one. Add seven test files covering those three derives, the four enum variant shapes `#[derive(HasFields)]` accepts and no other derive does, the unit struct, and the fieldless record. The variant-shape file is the substantive one: `HasFields` nests each variant's own fields as a product, so a unit variant becomes `Nil`, a newtype variant passes its payload through, and the two multi-field shapes become `Index`- and `Symbol!`-keyed products — none of which was tested or documented. Open the `invalid_expansion` target with the case it was reserved for. The variant derives name their own associated types through an unqualified `Self::…` path, so seven variant names cannot be used: `Fields` and `FieldsRef` break `HasFields`, `Value` breaks `FromVariant`, and `Value`, `Remainder`, `Extractor`, `ExtractorRef`, and `ExtractorMut` break `ExtractField`. The fix is a fully qualified projection in the codegen; until then the emitted paths are pinned as string snapshots so the test compiles even though the code it describes would not. How readable the resulting error is depends on the derive, which is the part worth recording: the representation and constructor impls target the user's enum, so a note points at the real variant, while the extractor's target the generated companions and point back at the derive, naming nothing. The three `parser_rejections` files carry `cargo fmt` reflowing only; their content is unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018n2krDG9WF1HD9aA1aMASX
Cover two gaps found by auditing the trait surface while porting the reference's `traits/` group. `AppendProduct`, `ConcatProduct`, and `MapFields` had no test anywhere, despite all three being public and carrying worked examples in the documentation. They compute types rather than values, so the new tests are type equalities: each coerces the computed `Output`/`Mapped` to the type the operation is documented to produce, and compiles only if the two agree. The file covers both `Nil` identities of concat, the three markers over a product, and `MapFields` over the `Either`/`Void` sum spine as well as the product one — the latter being the reason `MapFields` is the only one of the three defined over both. `DefaultImpls2` had no user at all: nothing in the library emits or consumes it, and no test named it, which left it unclear whether the trait was reachable or merely declared. It is reachable, and needs no new construct to be — `#[default_impl]` accepts an arbitrary namespace path and appends only the table parameter, so a two-type key registers and an ordinary `for … in` loop consumes it. The test establishes both halves and drives the resulting wiring. Writing them also settled where these traits live, which is now stated on the public pages: none of the three product operations is in the prelude, nor is the `IsOptional` marker, nor `DefaultImpls1`/`DefaultImpls2`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018n2krDG9WF1HD9aA1aMASX
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 Overview
This branch expands
#[use_type]alias resolution, rejects grounding cycles, fixes missing nested table declarations, and exposescgp-basethroughcgp-core. It also adds tests for previously untested behaviour. The branch contains ten commits on top ofmain(add50de, which moved the documentation into the knowledge base), changing 70 files with 4,897 insertions and 751 deletions. Most additions are test code. The macro-core source changes are confined to theuse_typeattribute implementation, thedelegate_components!body grammar, and thecgp_namespace!evaluator.Behaviour changes
#[use_type]resolves aliases in more positions and rejects grounding cycles#[use_type]now resolves imported aliases in positions where it previously emitted an unresolved identifier. This resolution, called grounding, replaces an alias with its associated-type projection. The added positions are:HasDbType.Db, HasTransactionType.{Transaction = Tx<Db>}now replaces the nestedDbwith<Self as HasDbType>::Db. Previously, substitution required whole-type equality and handled only a bare alias such as{Hashed = Password}. The shared substitution visitor now walks the right-hand side. It excludes the pinned alias itself, so{Foo = Foo}remains an unresolved-name error rather than producing a vacuous bound.Transaction::begin_from(pool)andTransaction::LABELinside a#[cgp_fn]or#[cgp_impl]body now use the projected type. For example, the call becomes<<Self as HasTransactionType>::Transaction>::begin_from(pool). Previously, these paths passed through unchanged even when aletannotation in the same body was rewritten. Substitution applies only to paths with at least two segments, so a single-segmentMarkerin an expression still names the unit struct. The visitor also recurses into the replacement, resolving aliases in later generic arguments such asTransaction::tagged::<Db>(db).HasDbType.Db, HasPoolType<Db>.Poolnow projects againstHasPoolType<<Self as HasDbType>::Db>instead of emitting a bareDb. Grounding still stops at the head of the trait path, which must name a trait and cannot be an alias.Pins on generic traits now produce valid bounds.
HasFooType<u8>.{Foo = u32}previously rendered asHasFooType<u8><Foo = u32>and failed inside the macro with a "failed to parse internal tokens" error. Pins now merge into the trait's argument list, producingHasFooType<u8, Foo = u32>. Lifetime arguments retain their leading position, as inHasRefType<'a, Ref = &'a str>.Grounding cycles now fail during macro expansion with a diagnostic that names the loop and marks the token that closes it. Rejected examples include
HasA.A in B, HasB.B in A, the self-referenceHasA.A in A, and a cycle through a trait argument,HasPoolType<Pool>.Pool. For example, a diagnostic identifies`B` -> `A` -> `B`. Previously, iterative grounding wrapped the alias one level deeper on each pass and left the compiler to reportE0425four projections down. Circular pins such as{Foo = Bar}, {Bar = Foo}remain the compiler's responsibility because they express a well-defined constraint that a context may satisfy.Grounding now follows alias dependencies in a single depth-first traversal.
ground_specs, in the newuse_type/grounding.rs, builds a dependency graph from the positions that support grounding and processes each specification after its dependencies. An edge back to a specification still being processed identifies a cycle. Each substitution uses fully grounded specifications, so replacements cannot contain bare aliases. The traversal visits each specification once and terminates.Alias resolution is independent of import order, stacked
#[use_type]attributes, and imports that share a context. Tests now cover each case. Emitted supertraits and predicates retain source order, so readers see them in the order the author wrote the imports.Nested tables are emitted from namespace and loop bodies
Nested tables such as
Wrapper<new Inner { … }>are now declared when written inside acgp_namespace!body or afor … inloop body. Both paths previously parsed the inner table and referenced it in the entry'sDelegatewithout declaring its struct. The resultingE0425pointed to a user-written name and made the missing declaration look like a typo.Namespace and loop evaluation now extract nested tables for emission.
NamespaceTable::evalruns the sameExtractInnerDelegateTablestraversal asDelegateTable::evaland renders the extracted structs after the namespace trait.DelegateEntries::extract_inner_tablesalso walks statements, withExtractInnerDelegateTablesimplemented for the statement forms.Namespace nesting lets every joining context inherit the dispatch table. Loop nesting is supported for correctness, but is not recommended: the loop already yields one provider per key. A nested table in a loop must declare the loop's provider variable as its own generic, written
new LoopInner<Provider>, for the generated implementations to be well formed.cgp-coreexposescgp-basethrough the facadecgp-corenow re-exportscgp_base as base, makingcgp::core::base::traits::StaticFormatandcgp::core::base::types::{Chars, Cons, Nil, PathCons, Symbol}available. The code now matches the paths already documented in the knowledge base and the/cgpskill.Related export changes affect the base crates:
cgp_base: Glob-re-exportscgp_base_types::*, exposing itstraitsandtypesmodules. This removes the formercgp_base::base_typesalias, which neither the workspace nor sibling checkouts referenced.cgp_base_extra::types: Now refers tocgp_base::types, the type-level primitives module, instead of thecgp_typecrate.HasTypeand related items no longer resolve through this path. They remain available atcgp::core::typesand incgp_base_extra::macro_prelude.Internal refactors
The
use_typeimplementation now shares the rules for identifying alias references and positions that support grounding.bare_alias_ident, invisitors/substitute_abstract_type.rs, identifies aliases in type positions. Both the substitution visitor and the new read-onlycollect_bare_aliasesvisitor use it. The latter collects dependencies for cycle detection.UseTypeAttribute::groundable_typesandgroundable_types_mutdefine the positions used by grounding, emitted bounds, and cycle detection so those operations agree.Bound rendering now merges associated-type bindings beside the code that renders the trait path.
PathWithTypeArgs::to_bound_tokensadds the bindings to the path's existing argument list. The refactor also removes the iterative algorithm'sis_changedfield fromSubstituteAbstractTypesand simplifiesfind_type_equality, which no longer needs the current specification or asyn::Resultreturn type.Tests added
Regression coverage
The new regression tests cover alias grounding, nested table emission, cycle diagnostics, and bound rendering:
abstract_types/:use_type_fn_equality_nested,use_type_fn_expr_path,use_type_fn_trait_arg_alias,use_type_fn_generic_trait_equality,use_type_fn_stacked_attributes, anduse_type_fn_shared_contextcover the changed resolution rules.abstract_types/:use_type_trait_arg_componentexercises grounding across a#[cgp_component]and#[cgp_impl]pair, including wiring and runtime dispatch.namespaces/:namespace_nested_tableandfor_loop_nested_tablewere both confirmed to fail before the fix.cgp-macro-tests:parser_rejections/use_type.rsadds five grounding-cycle rejection cases.cgp-macro-tests:ident_with_type_params/path_with_type_args.rsteststo_bound_tokens, including whether its output parses as aWherePredicate.Coverage for previously untested constructs
The branch also adds coverage for existing syntax, derives, and provider behaviour:
namespaces/redirect_mappingcovers the=>operator on a context. Itsopenequivalent produces the same snapshot except for the context name, testing the equivalence ofopen C;.namespaces/combined_formscombines every body form in one block.namespaces/default_impls2tests access toDefaultImpls2through ordinary#[default_impl]andfor … informs.extensible_records/product_opsadds the first tests forAppendProduct,ConcatProduct, andMapFields, expressed as type equalities.build_field_derive,extract_field_derive,from_variant_derive, andcgp_record_derivecover the individual derives.has_fields_enum_shapescovers the four variant shapes accepted by#[derive(HasFields)].record_emptyandstruct_unit_fieldcover fieldless shapes.basic_delegation/self_local_assoc_typetests theSelf::Assocexemption in#[cgp_impl].implicit_arguments/cgp_fn_mreftests theMRef<'_, T>implicit mode under both receivers.New snapshot macros support the derive tests:
snapshot_derive_build_field!,snapshot_derive_extract_field!, andsnapshot_derive_from_variant!. Each adds a 25-line entry point incgp-macro-test-util-liband a matching#[proc_macro]wrapper.Rejection cases
Parser rejection tests cover invalid attribute combinations and body forms whose diagnostics need regression coverage:
parser_rejections/use_provider.rstests the one-provider-per-attribute rule and its+counterpart.parser_rejections/delegate_and_check_components.rstests the check attributes that the fused macro rejects. Cases include#[skip_check]merged with#[check_params]across a list key and its element.parser_rejections/delegate_components.rsadds cases for a statement after a mapping, a braced path group followed by more path, and a bounded generic list on a nested table. Their current messages are misleading, so the tests record those diagnostics.Tests recording known defects
The branch records existing defects without fixing them. Both are documented under Known issues in the knowledge base:
invalid_expansion/reserved_variant_names.rsactivates the previously emptyinvalid_expansiontarget. Variant derives use unqualifiedSelf::…paths for associated types, causing ambiguous-associated-item errors when an enum variant is namedValue,Remainder,Extractor,ExtractorRef,ExtractorMut,Fields, orFieldsRef. For#[derive(ExtractField)], the error names an item the user did not write.higher_order_providers/lifetime_inner_provider.rssnapshots a higher-order provider whose inner bound lacks anIsProviderForcounterpart. The rewrite treats the bound's first generic argument as the context but encounters a lifetime. The stack compiles and runs, but loses the propagation that lets#[check_providers]identify a broken layer.The reserved-variant-name defect needs fully qualified projections in code generation. Until that fix, string snapshots record the emitted paths. The test adds
cgp-macro-test-util-libas a dev-dependency ofcgp-macro-teststo usepretty_format.Documentation changes
Repository documentation now separates test navigation, testing conventions, and macro reference material. The changes are:
crates/tests/README.md: Maps the suite and delegates conventions to itsAGENTS.md.cgp-macro-test-util/README.md: Shrinks from 592 to 112 lines by removing repetitive per-macro sections and a completed migration guide. It points to the knowledge base's snapshot-macros document.crates/main/cgp/README.md: Replaces the retired "new modular programming paradigm" positioning with the current wording and links to the knowledge base on the crates.io front page.AGENTS.mdshortens the knowledge-base link text, andcrates/tests/AGENTS.mdcloses a code span split across lines.The sibling knowledge base and
/cgpskill already describe the implementation changes. The knowledge base at../cgp-knowledge-basecovers dependency-orderedground_specs, cycle rejection,groundable_types,to_bound_tokens, extractedinner_structsinEvaluatedNamespaceTable, both known defects, and the new snapshot macros. It also links the new test files by name. The skill'sabstract-typesreference states the same#[use_type]rules, and the primer's import table routesStaticFormatthroughcgp::core::base::traits.The new cycle diagnostic does not change
cargo-cgpUI fixtures because none exercises a#[use_type]cycle.