Skip to content

AI Updates #7: span the data-derive impls, and catalog the equality-pinned abstract-type error#260

Merged
soareschen merged 5 commits into
mainfrom
ai-updates-20260721
Jul 24, 2026
Merged

AI Updates #7: span the data-derive impls, and catalog the equality-pinned abstract-type error#260
soareschen merged 5 commits into
mainfrom
ai-updates-20260721

Conversation

@soareschen

Copy link
Copy Markdown
Collaborator

AI Overview

This PR makes CGP's data derives point their compiler errors at the right place, and documents one new error class. It is a diagnostics-quality change: no macro accepts different input, no expansion changes shape, and no runtime behavior moves. Three commits carry it — two fix the spans the data-derive family stamps on its generated impls, and one adds an error-catalog section for an abstract-type mismatch the derives can surface. It touches 14 codegen files and 9 documentation files, all under cgp, with no change to any crate's public API or version.

High-level concepts

The core change is that every impl the data-derive family generates is now re-spanned onto the token the user actually wrote, instead of onto the whole #[derive(...)] attribute. A proc macro builds its output from quasi-quoted tokens, and those tokens all carry the macro's call_site span — the entire derived type. Left alone, a compiler error on a generated impl's header underlines the whole derive and says nothing about which field, variant, or type is at fault. Routing each finished impl through the existing override_item_span helper moves only the impl's boundary tokens — the impl keyword and the { … } body — onto a narrower span, collapsing the caret onto the entry the user wrote.

The re-span is deliberately confined to boundary tokens so the editor keeps working. rust-analyzer maps a source token to its expansion by source range alone, ignoring hygiene, so dragging a resolvable reference like HasField onto a user token would hijack go-to-definition on that token. The two boundary tokens moved here are structural — a keyword and a delimiter group, never references — so the compiler caret lands correctly while every interior reference stays at call_site and every user token keeps its own span. Correctness for the compiler and correctness for the IDE are the same concern handled together, exactly as the repository's span guidance requires.

The second concept is a newly documented failure mode: the equality-pinned abstract-type mismatch. A provider can pin an abstract type to a concrete one — #[use_type(HasErrorType.{Error = AppError})], which emits Self: HasErrorType<Error = AppError> — while the context binds that same abstract type to a different concrete type through its wiring. When the two disagree, the trait half of the bound still holds and only the associated-type projection fails, so the compiler reports an E0271 type mismatch rather than an E0277 unimplemented-trait error, with a caret on the #[cgp_type] attribute that defined the type — nowhere near either side of the disagreement. This PR records that class in the error catalog and describes how cargo-cgp reshapes it into the [CGP-E017] / [CGP-E112] form that names both types and the wiring entry to change.

Structural changes

On the code side, every generator in crates/macros/cgp-macro-core/src/types/cgp_data/ now calls override_item_span before returning its impls. The helper itself is unchanged — the PR reuses infrastructure that already backed delegate_components! and #[cgp_impl], extending it to the data-derive family for the first time. Per-field and per-variant impls are aimed at their field or variant (the identifier, or the whole syn::Field for an unnamed tuple field), and whole-type impls at the struct or enum name. The change spans the builder slice (has_builder_impl, into_builder_impl, partial_data, finalize_build_impl, has_field_impls, update_field_impls), the extractor slice (has_extractor_impl, extract_field_impls, finalize_extract_impl, partial_data), and the standalone derive_from_variant, derive_has_field, and derive_has_fields generators.

On the documentation side, derive_has_field.md gains the canonical Error spans section that explains the mechanism, the two diagnostics it improves, and why only boundary tokens move. Matching sections are added to the other slice docs — derive_build_field.md, derive_extract_field.md, and derive_from_variant.md — and to derive_has_fields.md. The three umbrella-derive docs (derive_cgp_data.md, derive_cgp_record.md, derive_cgp_variant.md) each note that they inherit the spans because they run the same slice helpers. Finally, errors/checks/check-trait-failure.md grows two subsections and a fixture link documenting the equality-pinned abstract-type class.

Impacts

The change touches diagnostics and documentation only, and its effects fall into the following distinct areas.

  • Coherence-conflict carets are now precise. An E0119 from a hand-written impl clashing with a derived one — HasField, HasFields, FromVariant, and the builder and extractor impls — lands on the specific field, variant, or type name rather than on the whole #[derive(...)] attribute.
  • Near-impl hints in missing-field errors are disambiguated. When a provider needs a field the struct lacks, rustc's "but trait HasField<…> is implemented for it" hint now points at the nearest existing field's own line. A struct that derives HasField for several fields shows each near-miss on its own field, instead of collapsing them all onto the derive attribute where the encoded Symbol<len, …> tags were the only way to tell them apart.
  • Editor navigation is preserved, by design. Because only structural boundary tokens are moved and every interior reference is left at call_site, go-to-definition on a field, variant, or wired name still resolves to the one correct definition. This half has no automated fixture and is verified by hand.
  • Coverage now spans the entire data-derive family. Every generated impl across the builder, extractor, variant, and field derives is re-spanned, and the CgpData / CgpRecord / CgpVariant umbrella derives inherit the behavior automatically because they run the same helpers.
  • No behavioral, API, or expansion-shape change. The generated code is semantically identical; only its spans differ. Expansion snapshots (pretty-printed, span-blind) are unaffected, so no snapshot updates were needed, and the workspace stays no_std and on the same versions.
  • The compiler-caret half is pinned cross-repo. The exact caret positions are recorded in cargo-cgp UI fixtures' .rust.stderr baselines — fields/base_area_1, the multi-field duplication/density_3, and others — so a regression that drops a re-span changes those snapshots there rather than here.
  • The error catalog covers a previously undocumented class. The equality-pinned abstract-type mismatch (E0271, reshaped to [CGP-E017]) is now documented with its diagnostic shape, its cascade behavior across every consumer that shares the abstract type, and its resolution. Its backing fixture, acceptable/types/abstract_type_mismatch.rs, already exists in the sibling cargo-cgp checkout, so the new documentation links resolve.

soareschen and others added 5 commits July 21, 2026 08:33
cargo-cgp reshapes the orphan-rule namespace-registration class into a
[CGP-E011] message naming the foreign namespace and key. Update the class doc's
"How cargo-cgp presents it", "Notes for tooling", raw-diagnostic framing, and
backing-fixture paths (usability/ -> acceptable/wiring/orphan/) to match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
cargo-cgp collapses a check-trait-failure cascade to a single block: it drops
the intermediate provider-trait noise and coalesces the checked components that
share one root cause into one headline listing every affected consumer trait
over a single root-cause tree. Update the class doc's "How cargo-cgp presents
it", "Notes for tooling", and backing-fixture entries (usability/ ->
acceptable/duplication/) to match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The HasField/HasFieldMut impls generated by `#[derive(HasField)]` carried
the macro's `call_site` span on their `impl`/`{ … }` boundary, so any error
about one field's impl — a coherence conflict (E0119), or the "but trait
`HasField<…>` is implemented for it" near-miss hint in a missing-field check
error — underlined the whole `#[derive(HasField)]` instead of the field.

Re-span each generated impl onto its field via `override_item_span` (the
field identifier for a named field, the whole `syn::Field` for a tuple
field), matching the delegate_components!/default_impl technique. Only the
boundary tokens move, so interior references stay at call_site and
go-to-definition on the field is unaffected.

The raw-diagnostic caret is pinned by the cargo-cgp UI fixtures (22
`.rust.stderr` baselines re-blessed there); documents the mechanism in the
HasField implementation doc's new Error spans section.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extend the HasField span fix to every other extensible-data derive:
HasFields, FromVariant, ExtractField, BuildField, and the CgpData/
CgpRecord/CgpVariant umbrellas that compose them. Each generated impl
carried the derive's `call_site` span on its `impl`/`{ … }` boundary, so
any error about it underlined the whole `#[derive(...)]` rather than the
field, variant, or type it derives from.

Re-span each impl via `override_item_span`, keyed by kind: per-field
impls (`UpdateField`, builder `HasField`) onto their field, per-variant
impls (`FromVariant`, `ExtractField`) onto their variant, and whole-type
impls (`HasFields`/`FromFields`/`ToFields`/…, `HasBuilder`, the
`HasExtractor` family, `FinalizeBuild`/`FinalizeExtract`, `PartialData`)
onto the struct/enum name. Re-spanning lives in the shared codegen
helpers, so every composing entry point inherits it. The `__Partial…`
companion types are cloned from user input and keep their own spans.

Only the boundary tokens move, so interior references stay at call_site
and go-to-definition is unaffected. Documents the mechanism in each
affected implementation doc's new Error spans section.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A provider that pins an abstract type with `#[use_type(Trait.{Assoc = Concrete})]`
while the context binds it elsewhere fails as an `E0271` projection mismatch
rather than the `E0277` the rest of this class produces: the trait half of the
bound holds, so only the associated type disagrees. Record it as a sub-case with
its own tells — the caret landing on the `#[cgp_type]` attribute that defined the
type, the supplied type absent from the message, and the cascade one shared
abstract type produces — and note that cargo-cgp now reshapes it into
[CGP-E017], naming both types and the wiring entry to change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@soareschen
soareschen merged commit ebc24db into main Jul 24, 2026
3 checks passed
@soareschen
soareschen deleted the ai-updates-20260721 branch July 24, 2026 21:16
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