Bugfix: rust-analyzer can't resolve symbols in #[function_component]
Description
Problem: When yew-macro is sourced from a remote (crates.io or github), rust-analyzer "control-click" resolution of code inside #[function_component] definitions fails to resolve; instead, it resolves to the top of the main yew crate.
Cause: Apparently, spans get mangled during cross-crate resolution when the crate is from a remote such as crates.io or github, etc. When yew-macro is overridden with a local path patch in Cargo.toml, e.g.:
[patch.crates-io]
yew-macro = { version = "0.21", path = "/Users/tom/src/yew-macro-0.21.0" }
even if the source is copied verbatim from the crates.io cache, this problem does not occur (at least on nightly), making reproduction for the yew authors difficult (because they are likely working on the workspace in the local filesystem in which case rust-analyzer doesn't seem to mangle the spans and resolution works?).
Solution: anchor parse_quote_spanned! calls to their call sites.
Impact: fixes control-click editor navigation for all symbols in #[function_component] bodies.
Fixes #3904
Tested on
nightly-aarch64-apple-darwin (active, default)
rustc 1.91.0-nightly (fe5536432 2025-08-29)
Checklist
- [x] I have reviewed my own code
- [ ] I have added tests - NO
- [x] This requires human testing because it concerns
rust-analyzerbehavior inside of an IDE. Instead of tests, I have added comments to the changes to indicate why they were changed.
Visit the preview URL for this PR (updated for commit 74fee05):
https://yew-rs-api--pr3916-fix-analyzer-master-zfyo9fyc.web.app
(expires Sun, 07 Sep 2025 01:55:42 GMT)
🔥 via Firebase Hosting GitHub Action 🌎
Benchmark - SSR
Yew Master
| Benchmark | Round | Min (ms) | Max (ms) | Mean (ms) | Standard Deviation |
|---|---|---|---|---|---|
| Baseline | 10 | 310.649 | 311.060 | 310.819 | 0.150 |
| Hello World | 10 | 466.947 | 478.175 | 471.706 | 3.610 |
| Function Router | 10 | 1592.349 | 1617.174 | 1601.537 | 9.102 |
| Concurrent Task | 10 | 1005.473 | 1007.612 | 1006.419 | 0.654 |
| Many Providers | 10 | 1076.033 | 1113.111 | 1095.999 | 13.445 |
Pull Request
| Benchmark | Round | Min (ms) | Max (ms) | Mean (ms) | Standard Deviation |
|---|---|---|---|---|---|
| Baseline | 10 | 310.449 | 311.375 | 310.810 | 0.236 |
| Hello World | 10 | 464.044 | 471.872 | 468.219 | 2.434 |
| Function Router | 10 | 1617.841 | 1656.505 | 1625.136 | 11.388 |
| Concurrent Task | 10 | 1005.613 | 1007.132 | 1006.518 | 0.513 |
| Many Providers | 10 | 1088.491 | 1130.185 | 1110.060 | 14.262 |
Size Comparison
| examples | master (KB) | pull request (KB) | diff (KB) | diff (%) |
|---|---|---|---|---|
| async_clock | 99.447 | 99.447 | 0 | 0.000% |
| boids | 168.602 | 168.602 | 0 | 0.000% |
| communication_child_to_parent | 91.687 | 91.687 | 0 | 0.000% |
| communication_grandchild_with_grandparent | 102.711 | 102.711 | 0 | 0.000% |
| communication_grandparent_to_grandchild | 97.716 | 97.716 | 0 | 0.000% |
| communication_parent_to_child | 87.604 | 87.604 | 0 | 0.000% |
| contexts | 103.775 | 103.775 | 0 | 0.000% |
| counter | 85.106 | 85.106 | 0 | 0.000% |
| counter_functional | 85.276 | 85.276 | 0 | 0.000% |
| dyn_create_destroy_apps | 87.907 | 87.907 | 0 | 0.000% |
| file_upload | 98.468 | 98.468 | 0 | 0.000% |
| function_delayed_input | 91.698 | 91.698 | 0 | 0.000% |
| function_memory_game | 170.650 | 170.650 | 0 | 0.000% |
| function_router | 337.697 | 337.697 | 0 | 0.000% |
| function_todomvc | 163.286 | 163.286 | 0 | 0.000% |
| futures | 238.378 | 238.378 | 0 | 0.000% |
| game_of_life | 104.443 | 104.443 | 0 | 0.000% |
| immutable | 194.554 | 194.554 | 0 | 0.000% |
| inner_html | 79.993 | 79.993 | 0 | 0.000% |
| js_callback | 108.273 | 108.273 | 0 | 0.000% |
| keyed_list | 196.446 | 196.446 | 0 | 0.000% |
| mount_point | 83.317 | 83.317 | 0 | 0.000% |
| nested_list | 115.048 | 115.048 | 0 | 0.000% |
| node_refs | 90.797 | 90.797 | 0 | 0.000% |
| password_strength | 1776.575 | 1776.575 | 0 | 0.000% |
| portals | 93.262 | 93.262 | 0 | 0.000% |
| router | 307.351 | 307.351 | 0 | 0.000% |
| suspense | 112.261 | 112.261 | 0 | 0.000% |
| timer | 88.679 | 88.679 | 0 | 0.000% |
| timer_functional | 95.012 | 95.012 | 0 | 0.000% |
| todomvc | 143.637 | 143.637 | 0 | 0.000% |
| two_apps | 85.721 | 85.721 | 0 | 0.000% |
| web_worker_fib | 134.763 | 134.763 | 0 | 0.000% |
| web_worker_prime | 187.480 | 187.480 | 0 | 0.000% |
| webgl | 82.962 | 82.962 | 0 | 0.000% |
✅ None of the examples has changed their size significantly.
It doesn't seem to fix anything for me on linux. Although I use neovim, I also have a "go to definition" action provided by rust-analyzer. And I can trigger it by control-clicking too. (It seems like the action is allowed to provide more than one option because my ui shows a list of dozen options, in which some weird ones like "yew root" and "std root" exist. And control clicking leads me to a random one). I tried this minimum code:
use yew::prelude::*;
#[function_component]
pub fn Foo() -> Html {
html! {
<Bar />
}
}
#[function_component]
pub fn Bar() -> Html {
html! {
}
}
in one repo, with
[dependencies]
yew = { git = "https://github.com/yewstack/yew", branch = "master", features = ["csr"] }
in the other with
[dependencies]
yew = { git = "https://github.com/dra11y/yew.git", branch = "master", features = ["csr"] }
In both cases, the control click destination and the list on <Bar/> seem identical. Am I missing something? Can someone reproduce this fix on Mac?
I was using rustc 1.91.0-nightly (a1208bf76 2025-09-03) when I tested