Skip to content

fix(observed_macros): resolve renamed dependencies and reject mutable-reference fields - #726

Draft
Evgenii (Vaiz) wants to merge 2 commits into
mainfrom
u/eshutov/observed-macro-hygiene
Draft

fix(observed_macros): resolve renamed dependencies and reject mutable-reference fields#726
Evgenii (Vaiz) wants to merge 2 commits into
mainfrom
u/eshutov/observed-macro-hygiene

Conversation

@Vaiz

Copy link
Copy Markdown
Contributor

🤖 Clawpilot here! Posted automatically by Clawpilot (an AI agent), not by a human. Please verify before acting.

Two independent hygiene fixes in the observed procedural macros, both in crates/observed_macros_impl. One commit each, so they can be reviewed separately.

Resolve a renamed observed dependency in generated paths (AB#7757619)

Both generators emitted a hard-coded ::observed into every path they produce. extern crate self as observed in the runtime crate only helps expansions inside that crate, so a consumer that renames its dependency:

telemetry = { package = "observed", version = "0.24" }

had no observed in scope and failed to compile for both #[event(...)] and #[derive(Enrichment)].

The runtime crate is now resolved once through proc-macro-crate and the resolved path is threaded through event generation, enrichment generation and the where-clause predicates the two share. The new resolver module follows the local pattern in routerama_build::macro_impl::resolver, including its handling of FoundCrate::Itself.

Behaviour is unchanged for every crate that does not rename the dependency: FoundCrate::Itself (the runtime crate expanding its own events, which reaches itself via extern crate self as observed) and an unreadable manifest both keep ::observed. All 32 pre-existing expansion snapshots pass untouched, which is the evidence for that.

Coverage: a new expansion snapshot drives both entry points against a renamed path and pins every emitted path — the trait, the descriptors, Value, the visitor function type, the __private items and the spelled-out bounds — so a single hard-coded ::observed left anywhere would show up in it. Unit tests cover the path derivation itself (rename, dash-containing alias, keyword alias needing a raw identifier, Itself, unresolvable). Hidden *_with_runtime_path entry points exist only so the tests can vary a path the production entry points read from the caller's manifest.

Reject mutable-reference event fields (AB#7757620)

is_reference_type matched any syn::Type::Reference and never inspected TypeReference::mutability, so #[event(...)] accepted a &mut T field and generated a visit body for it. An event is visited through &self, so that body cannot hand out the exclusive borrow it asks for — the input was rejected only later, by the compiler, against generated code the author never wrote.

Mutability is now checked during field parsing and reported against the offending field type, saying that event fields support only shared references. Option<&mut T> is checked too, because the visit body dereferences the inner type rather than the field type, and the check recurses through nested references so &&mut T is caught as well. A companion snapshot proves shared references (&'a T, &'a &'a T, Option<&'a T>) still expand exactly as before.

Deliberately not fixed here

AB#7757617 ("Expose fields of signal-less events to name-based processors") lives in the same file — field_reaches_signal in src/event.rs — but needs a design decision about what the contract for signal-less fields should be. It is left untouched, and the expansion__an_event_with_no_signal_at_all_visits_nothing snapshot is unchanged.

Validation

Run for observed, observed_macros, observed_macros_impl and observed_testing:

Check Result
cargo +nightly-2026-05-30 fmt --check pass
cargo +1.96.1 clippy --all-targets (-D warnings) pass
cargo +1.96.1 test pass (all suites, incl. doctests)

No pre-existing snapshot was re-blessed; the only new .snap files are the three added by the new tests.

Evgenii (Vaiz) and others added 2 commits September 3, 2026 20:12
…d paths

Both generators emitted a hard-coded `::observed` into every path they
produce. `extern crate self as observed` in the runtime crate only helps
expansions inside that crate, so a consumer that renames its dependency

    telemetry = { package = "observed", version = "0.24" }

had no `observed` in scope and failed to compile for both `#[event(...)]`
and `#[derive(Enrichment)]`.

Resolve the runtime crate once through `proc-macro-crate` and thread the
resolved path through event generation, enrichment generation and the
`where`-clause predicates shared by both. The resolver follows the local
pattern in `routerama_build::macro_impl::resolver`: `FoundCrate::Itself`
and an unreadable manifest both keep `::observed`, so the runtime crate's
own expansions and every crate that does not rename the dependency emit
exactly what they emitted before - the existing expansion snapshots are
unchanged.

Add hidden `*_with_runtime_path` entry points so the expansion tests can
drive the generators against a renamed path, and cover the path
derivation itself (rename, dashes, keyword alias, `Itself`, unresolvable)
with unit tests.

Fixes AB#7757619

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
`is_reference_type` matched any `syn::Type::Reference` and never looked at
`TypeReference::mutability`, so `#[event(...)]` accepted a `&mut T` field
and generated a visit body for it. An event is visited through `&self`, so
that body cannot hand out the exclusive borrow it asks for, and the input
was only rejected later - by the compiler, against generated code the
author never wrote.

Check mutability during field parsing and report it against the offending
field type. `Option<&mut T>` is checked as well, because the visit body
dereferences the inner type rather than the field type, and the check
recurses through nested references so `&&mut T` is caught too. Shared
references are untouched.

Fixes AB#7757620

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@codecov

codecov Bot commented Sep 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.0%. Comparing base (a8b589f) to head (215afdd).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #726   +/-   ##
=======================================
  Coverage   100.0%   100.0%           
=======================================
  Files         582      583    +1     
  Lines       62852    62942   +90     
=======================================
+ Hits        62852    62942   +90     
Flag Coverage Δ
linux 72.7% <100.0%> (?)
linux-arm 73.5% <100.0%> (?)
windows 65.3% <100.0%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown

⚠️ Potential breaking changes detected

cargo semver-checks flagged the following on this PR. This is informational -- breaking changes between commits are expected; the major-version bump happens at release time, not on every PR.

fetch_winhttp

     Cloning origin/main
    Building fetch_winhttp v0.1.1 (current)
       Built [   0.335s] (current)
     Parsing fetch_winhttp v0.1.1 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-fetch_winhttp-0_1_1-default-01666ec060466c14/target/doc/fetch_winhttp.json
(supported formats are v55, v56, v57)

fetch_winhttp_impl

     Cloning origin/main
    Building fetch_winhttp_impl v0.1.1 (current)
       Built [   0.363s] (current)
     Parsing fetch_winhttp_impl v0.1.1 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-fetch_winhttp_impl-0_1_1-default-f6bbf157605592e2/target/doc/fetch_winhttp_impl.json
(supported formats are v55, v56, v57)

observed

     Cloning origin/main
    Building observed v0.25.0 (current)
       Built [   6.674s] (current)
     Parsing observed v0.25.0 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-observed-0_25_0-default-d7b8c5ec6ce39049/target/doc/observed.json
(supported formats are v55, v56, v57)

observed_macros_impl

     Cloning origin/main
    Building observed_macros_impl v0.24.0 (current)
       Built [   2.500s] (current)
     Parsing observed_macros_impl v0.24.0 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-observed_macros_impl-0_24_0-default-01666ec060466c14/target/doc/observed_macros_impl.json
(supported formats are v55, v56, v57)

observed_testing

     Cloning origin/main
    Building observed_testing v0.0.0 (current)
       Built [   6.928s] (current)
     Parsing observed_testing v0.0.0 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-observed_testing-0_0_0-default-01666ec060466c14/target/doc/observed_testing.json
(supported formats are v55, v56, v57)

observed_utils

     Cloning origin/main
    Building observed_utils v0.2.0 (current)
       Built [   6.958s] (current)
     Parsing observed_utils v0.2.0 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-observed_utils-0_2_0-default-01666ec060466c14/target/doc/observed_utils.json
(supported formats are v55, v56, v57)

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