Skip to content

AI-revise implementation: Span-leak fixes, richer field reads, and a real compile-fail suite#249

Merged
soareschen merged 17 commits into
mainfrom
ai-tests-2
Jul 3, 2026
Merged

AI-revise implementation: Span-leak fixes, richer field reads, and a real compile-fail suite#249
soareschen merged 17 commits into
mainfrom
ai-tests-2

Conversation

@soareschen

@soareschen soareschen commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

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 a trybuild suite that the normal test runner actually executes. Around those code changes it also renames every CLAUDE.md to AGENTS.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_site span, 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 shared override_span helper 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 single FieldMode::apply method 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 rustc reject the result. The old suite pinned these cases as compile_fail doctests, which cargo nextest silently skips, so they were effectively unrun in CI. This PR moves them to trybuild fixtures 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 match over FieldMode that used to be duplicated between the getter-method builder and the implicit-argument expander is gone, replaced by a single FieldMode::apply(call_expr, field_mut) method in field_mode.rs that both callers invoke. In tandem, parse_field_type changed 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 a require_mut_receiver helper. A new FieldMode::OptionStr variant carries the Option<&str> case.

The override_span helper moved out of the check_components module into a shared override_span.rs under functions/, so both check_components! and delegate_components! can use it. Evaluated wiring keys and entries now carry a span field sourced from the user's original tokens rather than from a possibly-synthesized key type, and a new PathElement::span method recovers the span of a path segment (including a Symbol, whose regenerated tokens would otherwise be call_site).

The compile-fail crate was restructured end to end. Its src/lib.rs shrank from a doctest host to a documentation-only stub that points at the fixtures, a tests/compile_fail_tests.rs driver now globs the fixtures, and the fixtures themselves are organized into tests/acceptable/ (failures CGP intentionally delegates to rustc) and tests/problematic/ (failures that are a CGP defect), each grouped into one subdirectory per owning macro, with a committed .stderr snapshot beside every fixture and a README.md documenting the scheme. One acceptable fixture pins the boundary of the widened field-reading mapping: Option<&[T]> combines the Option<&T> and &[T] shorthands into a shape CGP provides no rule for, so parse_field_type lowers it literally to a HasField bound over the unsized Option<[T]> and lets rustc reject it rather than giving it a bespoke rule.

The largest file-count change is documentation and tooling rather than code. Every CLAUDE.md became AGENTS.md across the repository, the .claude/skills for CGP and dual-reader prose were checked in, and the implementation and reference docs picked up the new cgp_impl span rules, the extended delegate_components failure modes, and the implicit attribute 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. MRef is now exported from cgp-macro-core and re-exported from cgp::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 by check_components! points at the checked component; a provider-name clash (E0428) from #[cgp_impl(new Foo)] points at the name Foo — 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 via AsMut<[T]>); optional string slices Option<&str> backed by Option<String> (read via .as_deref()/.as_deref_mut()); and mutable optional references Option<&mut T>. New passing tests in cgp-tests cover each shape (cgp_fn_owned_tuple, cgp_fn_slice, cgp_fn_mut_slice, cgp_fn_option_str, cgp_fn_option_mut, plus the mut_* and option_str_auto getter 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 attribute implicit" error. To carry that rejection, extract_implicit_args and is_implicit_arg now return Result.

  • Compile-fail tests actually run in CI. The trybuild suite is an ordinary integration test, so both cargo test and cargo nextest run execute it — unlike the previous compile_fail doctests, 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 duplicate cgp_impl default-impl and provider names, plus one acceptable getter shape CGP defers to rustcOption<&[u8]>, whose literal lowering names an unsized Option<[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}; and cgp-dispatch gained a needless_lifetimes clippy allow.

  • A single-parameter provider struct no longer trips unused_parens. Now that a generated provider struct carries the user's span, EmptyStruct emits PhantomData<T> for one parameter and reserves the tuple form PhantomData<(A, B)> for two or more, avoiding a lint firing in the user's crate.

  • Documentation and tooling reorganization. Every CLAUDE.md is now AGENTS.md, a "span leaks" rule and the macro-review workflow are codified in the top-level AGENTS.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.toml and the AGENTS.md Commands note, and the .stderr snapshots for the option_slice and missing_dependency fixtures are regenerated to match 1.96's diagnostic formatting. The GitHub Actions workflow drops the unmaintained third-party actions-rs/* actions in favor of the rustup/cargo already on the runner, adds a least-privilege permissions: contents: read, and runs the whole workspace — trybuild fixtures included — under cargo nextest. This affects contributors and CI, not the compiled crate.

@soareschen soareschen added the AI label Jul 3, 2026
@soareschen soareschen changed the title AI-review implementation: Span-leak fixes, richer field reads, and a real compile-fail suite AI-revise implementation: Span-leak fixes, richer field reads, and a real compile-fail suite Jul 3, 2026
@soareschen soareschen merged commit 0f66264 into main Jul 3, 2026
3 checks passed
@soareschen soareschen deleted the ai-tests-2 branch July 3, 2026 22:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant