AI Updates #4: Fix spans for IDEs, more permissive #[uses], and improve modern idioms#252
Merged
Conversation
#[uses], and improve modern idioms
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 procedural macros along two independent axes and fixes one code-generation bug. The macros now place the spans on their generated code so that both the compiler's error carets and rust-analyzer's go-to-definition land on the right token, where before only the compiler was considered. The
#[uses(...)]attribute now accepts any trait bound awhereclause allows, not just the plainTrait<Params>form. And#[cgp_impl]now honors an explicit component-name override instead of always deriving the conventional{Provider}Componentname. Everything else in the diff — new tests, a restructuredAGENTS.md, and documentation edits — supports or documents these three changes. No wiring semantics change; the expansions are identical apart from where their tokens are spanned.High-level concepts
Spans now serve two tools, not one
The central change is a shift in how the macros think about spans. A CGP macro builds its output from quasi-quoted tokens, and every token it emits carries the macro's
call_sitespan — the range of the whole invocation. That default misleads two separate tools, and this PR treats both as first-class concerns. The compiler is one: an error on a generated item's header, such as a coherence conflict (E0119) or an unsatisfied bound, underlines the entire macro block instead of the specific entry the user wrote. The IDE is the other, and it was previously ignored: rust-analyzer maps a source token to its expansion purely by source range, so dragging a generated item's tokens onto a user token's range hijacks go-to-definition on that token, and a leaked derived name makes navigation offer the defining macro alongside the item.The old approach re-spanned every top-level token of a generated item onto the originating token, then restored the generics afterward. This satisfied the compiler but broke the IDE, because it dragged synthesized references like
IsProviderForandDelegateComponentonto the user's key token. The new approach, embodied in a newoverride_item_spanhelper, re-spans only the item's two boundary tokens — its leading keyword and its trailing brace group — and leaves everything in between untouched. The compiler derives an item's error caret from its first and last tokens joined together, so moving just those two is enough to aim the caret; and leaving the interior alone keeps every user-written token (the wired provider, the target type, a per-entry generic) and every synthesized reference at a span that never collides with a narrow user token, so navigation resolves to the one correct definition. Each boundary token is moved only if it is itself synthesized, detected by a newis_synthesizedhelper that compares a token's source text against thecall_sitetext, so a hand-assembled item whose boundary the user wrote is left alone.The same two-audience thinking drives two smaller span fixes on the component machinery. The
#[cgp_component]marker struct — the{Provider}Componentdefinition — is now spanned on the provider identifier the user wrote rather than oncall_site, so go-to-definition on a delegate key lands cleanly on the component instead of also offering thecgp_componentmacro path. Its reference-side dual, the{Provider}Componentreference that#[cgp_provider]synthesizes inside the generatedIsProviderForimpl, moves the opposite way: it is now spanned oncall_siterather than on the provider trait, because it is an interior token that anchors no caret and would otherwise make go-to-definition on the provider trait wrongly offer the component struct too.#[uses(...)]accepts any trait boundThe
#[uses(...)]attribute previously parsed only a simplified trait-path form, so a dependency that needed associated-type equality — for exampleHasErrorType<Error = String>— had to be written as a hand-rolledwhereclause. The parser now accepts a fullsyn::TypeParamBoundfor each entry, so an associated-type binding, a higher-ranked bound, or a lifetime bound is imported verbatim onto the generated impl'swhereclause. The simpleTrait<Params>form remains the idiomatic one, and the documentation is careful to steer abstract-type pins toward the#[use_type]equality form instead; the relaxation exists for the general bounds that neither#[use_type]nor the old syntax could express. As a side benefit the parsing path simplified, since the attribute no longer re-parses each entry throughparse_internal!to reach a bound.#[cgp_impl]honors an explicit component overrideA provider written for a component whose marker name does not follow the
{Provider}Componentconvention was mis-compiled.#[cgp_impl]'scomponent_type()always derived the conventional name and ignored an explicit#[cgp_impl(new Provider: Component)]override, so the generatedIsProviderForimpl referenced a component that did not exist and failed to resolve. The fix returns the user-supplied component type directly when an override is present, falling back to the derived name only when none is given. Because the override is a user-written type, it also keeps its own span for free.Structural changes
The span work reshapes
override_span.rs. The file previously held one function; it now holds three.override_spansurvives but is narrowed to a single documented purpose —check_components!re-spanning the one shared context token onto each checked component in turn, which is the one place clobbering a user token's span is the actual intent. The newoverride_item_spanis the boundary-only re-spanner thatdelegate_components!(eval.rs) and thedefault_implattribute now call in place of the old whole-item re-span, both of which dropped their clone-and-restore-generics dance in the process. The new privateis_synthesizedhelper backs the boundary check.The
#[uses]relaxation changes a field type in three parser structs.UsesAttributes,FunctionAttributes, andCgpImplAttributeseach changed theiruses/importsfield fromVec<PathWithTypeArgs>toVec<TypeParamBound>, dropped the now-unneededPathWithTypeArgsimports, and replaced the per-entryparse_internal!re-parsing inuses.rsandcgp_fn/preprocessed.rswith a direct clone into the bound list. The#[cgp_impl]override fix is localized tocgp_provider/item.rs, which grows an early return incomponent_type().The remaining changes are tests and prose. Three new tests land: a snapshot-plus-behavior test that a non-conventionally-named component works through the
: Componentoverride, and two tests — one on#[cgp_fn], one on#[cgp_impl]— that#[uses]now carries an associated-type-equality bound, each registered in its module tree.AGENTS.mdrestructures the "scrutinize the macro codegen" review checklist from a flat bullet list into titled subsections, promoting spans to a two-part concern (compiler carets and IDE go-to-definition) and cross-linking the implementation notes. The documentation set is updated in step:uses.md,modern-idioms.md, theimplicit/use_typereferences, the CGP skill files, and the money-transfer example, which is rewritten to prefer an#[implicit]argument over a#[cgp_auto_getter]trait for reading a context field.Impacts
The impacts fall into distinct buckets, listed here because they touch separable parts of the developer experience.
delegate_components!and default-impl wiring still point at the offending entry rather than the whole macro block; the boundary-only re-span keeps the caret placement that thetrybuild.stderrfixtures pin.#[uses]gains expressive power. Providers can now import bounds such asHasErrorType<Error = String>, higher-ranked bounds, and lifetime bounds directly, removing a class of fall-back-to-whereworkarounds. The guidance still prefers#[use_type]'s equality form for pinning an abstract type, so the new generality is a capability, not a new default.{Provider}Componentnow compile and wire correctly through the: Componentoverride, where before the generatedIsProviderForimpl silently referenced a non-existent component.AGENTS.mdnow instructs reviewers to audit spans for both the compiler and the IDE on every item-emitting macro, flags the IDE half as untested and manual, and the documentation steers new code toward implicit arguments, combined attributes, and the#[use_type]equality form.