AI-revise implementation: Span-leak fixes, richer field reads, and a real compile-fail suite#249
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 Overview
This PR hardens the CGP macro layer along three fronts that reinforce each other. It makes compiler errors from macro-generated code point at the token the user actually wrote, extends the
#[implicit]-argument and getter machinery to cover more field shapes, and replaces the old doctest-based compile-fail tests with atrybuildsuite that the normal test runner actually executes. Around those code changes it also renames everyCLAUDE.mdtoAGENTS.md, checks the CGP and prose skills into the repository, expands the agent-facing documentation, and bumps the pinned toolchain to Rust 1.96 alongside a rewritten, supply-chain-hardened CI workflow. No user-written CGP breaks; the changes add capability, sharpen diagnostics, and widen test coverage.High-level concepts
The central theme is diagnostic quality: errors should land where the user can act on them. A CGP macro builds its output from quasi-quoted tokens that all carry the macro invocation's
call_sitespan, so when the generated Rust fails to compile — a coherence conflict between two wiring entries, an unsatisfied impl-side dependency, two providers sharing a name — the compiler underlines the whole macro block instead of the one entry at fault. This PR introduces a sharedoverride_spanhelper that re-stamps a generated item's top-level tokens onto the specific source token that produced it, and threads a real source span through the wiring and checking pipelines so each generated impl can be re-spanned onto the entry the user wrote.The second concept is completeness of the field-reading surface. CGP lets a provider pull values straight from context fields, either as
#[implicit]function arguments or through getter methods, and both paths share one notion of how a declared type maps to a field type and a read expression. This PR widens that mapping so it covers owned values that are neither references nor paths, mutable slices, optional string slices, and mutable optional references, and it unifies the two paths onto a singleFieldMode::applymethod so an implicit argument and a getter convert the same field identically.The third concept is honest test coverage for failures that only surface after macro expansion. Some bad input cannot be rejected inside a macro because the macro lacks the whole-program view that borrow-checking and coherence need; the macro must lower the input faithfully and let
rustcreject the result. The old suite pinned these cases ascompile_faildoctests, whichcargo nextestsilently skips, so they were effectively unrun in CI. This PR moves them totrybuildfixtures driven by an ordinary integration test, and splits them by whose fault the failure is.Structural changes
The field-mode conversion logic now lives in one place. The
matchoverFieldModethat used to be duplicated between the getter-method builder and the implicit-argument expander is gone, replaced by a singleFieldMode::apply(call_expr, field_mut)method in field_mode.rs that both callers invoke. In tandem,parse_field_typechanged signature to return the field-access mutability it derives from the type — a third tuple element — so callers no longer re-derive it, and its receiver-mutability check was factored into arequire_mut_receiverhelper. A newFieldMode::OptionStrvariant carries theOption<&str>case.The
override_spanhelper moved out of thecheck_componentsmodule into a shared override_span.rs underfunctions/, so bothcheck_components!anddelegate_components!can use it. Evaluated wiring keys and entries now carry aspanfield sourced from the user's original tokens rather than from a possibly-synthesized key type, and a newPathElement::spanmethod recovers the span of a path segment (including aSymbol, whose regenerated tokens would otherwise becall_site).The compile-fail crate was restructured end to end. Its
src/lib.rsshrank from a doctest host to a documentation-only stub that points at the fixtures, atests/compile_fail_tests.rsdriver now globs the fixtures, and the fixtures themselves are organized intotests/acceptable/(failures CGP intentionally delegates torustc) andtests/problematic/(failures that are a CGP defect), each grouped into one subdirectory per owning macro, with a committed.stderrsnapshot beside every fixture and aREADME.mddocumenting the scheme. One acceptable fixture pins the boundary of the widened field-reading mapping:Option<&[T]>combines theOption<&T>and&[T]shorthands into a shape CGP provides no rule for, soparse_field_typelowers it literally to aHasFieldbound over the unsizedOption<[T]>and letsrustcreject it rather than giving it a bespoke rule.The largest file-count change is documentation and tooling rather than code. Every
CLAUDE.mdbecameAGENTS.mdacross the repository, the.claude/skillsfor CGP and dual-reader prose were checked in, and the implementation and reference docs picked up the newcgp_implspan rules, the extendeddelegate_componentsfailure modes, and theimplicitattribute notes.Impacts
The changes below are grouped because each is a distinct, independently observable effect of this PR rather than a step in one argument. They span the public API, user-facing diagnostics, new macro capabilities, stricter validation, and test coverage.
New public API.
MRefis now exported fromcgp-macro-coreand re-exported fromcgp::prelude, so it is available unqualified alongside the other type-level primitives.Sharper compiler diagnostics for CGP users. A coherence conflict between two
delegate_components!entries mapping the same key (E0119) now points at the offending entry; an unsatisfied dependency surfaced bycheck_components!points at the checked component; a provider-name clash (E0428) from#[cgp_impl(new Foo)]points at the nameFoo— each instead of underlining the whole macro block. Per-entry generics keep their own spans, so an unconstrained-parameter error (E0207) still points at the generic the user wrote.Wider field-reading support.
#[implicit]arguments and getters now accept owned non-reference, non-path types such as tuples and arrays (read by value and cloned) where they were previously rejected with "return type must be a reference"; mutable slices&mut [T](read viaAsMut<[T]>); optional string slicesOption<&str>backed byOption<String>(read via.as_deref()/.as_deref_mut()); and mutable optional referencesOption<&mut T>. New passing tests incgp-testscover each shape (cgp_fn_owned_tuple,cgp_fn_slice,cgp_fn_mut_slice,cgp_fn_option_str,cgp_fn_option_mut, plus themut_*andoption_str_autogetter tests).Stricter, clearer validation. A malformed
#[implicit(...)]or#[implicit = ...]attribute is now rejected at macro time with a message telling the user to write a bare#[implicit], instead of leaking downstream as an obscure "cannot find attributeimplicit" error. To carry that rejection,extract_implicit_argsandis_implicit_argnow returnResult.Compile-fail tests actually run in CI. The
trybuildsuite is an ordinary integration test, so bothcargo testandcargo nextest runexecute it — unlike the previouscompile_faildoctests, which nextest skipped. New fixtures pin the diagnostics for duplicate wiring keys (across blocks, within a block, on open keys, on path keys), a missing impl-side dependency, overlapping and unconstrained generic entries, and duplicatecgp_impldefault-impl and provider names, plus one acceptable getter shape CGP defers torustc—Option<&[u8]>, whose literal lowering names an unsizedOption<[u8]>field.Corrected messages and lints. The
#[cgp_auto_getter]"does not accept any attribute argument" error no longer misnames itself as#[derive_auto_getter]; a getter arity error now interpolates its count instead of printing the literal{I}; andcgp-dispatchgained aneedless_lifetimesclippy allow.A single-parameter provider struct no longer trips
unused_parens. Now that a generated provider struct carries the user's span,EmptyStructemitsPhantomData<T>for one parameter and reserves the tuple formPhantomData<(A, B)>for two or more, avoiding a lint firing in the user's crate.Documentation and tooling reorganization. Every
CLAUDE.mdis nowAGENTS.md, a "span leaks" rule and the macro-review workflow are codified in the top-levelAGENTS.md, and the CGP and dual-reader-prose skills are checked into.claude/skills. These affect contributors and agents working in the repository, not the compiled crate.Newer toolchain and a hardened CI workflow. The pinned toolchain moves from Rust 1.93 to 1.96 in
rust-toolchain.tomland theAGENTS.mdCommands note, and the.stderrsnapshots for theoption_sliceandmissing_dependencyfixtures are regenerated to match 1.96's diagnostic formatting. The GitHub Actions workflow drops the unmaintained third-partyactions-rs/*actions in favor of therustup/cargoalready on the runner, adds a least-privilegepermissions: contents: read, and runs the whole workspace — trybuild fixtures included — undercargo nextest. This affects contributors and CI, not the compiled crate.