Skip to content

Commit 22b6f96

Browse files
committed
Auto merge of rust-lang#16439 - wasd96040501:feat/gotodef3, r=Veykril
feat: Support for GOTO def from *inside* files included with include! macro close rust-lang#14937 Try to implement goto def from *inside* files included with include! macro. This implementation has two limitations: 1. Only **one** file which calls include! will be tracked. (I think multiple file be included is a rare case and we may let it go for now) 2. Mapping token from included file to macro call file (semantics.rs:646~658) works fine but I am not sure is this the correct way to implement.
2 parents d13951f + b22e772 commit 22b6f96

File tree

7 files changed

+163
-12
lines changed

7 files changed

+163
-12
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/hir-def/src/db.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
//! Defines database & queries for name resolution.
2-
use base_db::{salsa, CrateId, SourceDatabase, Upcast};
2+
use base_db::{salsa, CrateId, FileId, SourceDatabase, Upcast};
33
use either::Either;
44
use hir_expand::{db::ExpandDatabase, HirFileId, MacroDefId};
55
use intern::Interned;
66
use la_arena::ArenaMap;
7+
use span::MacroCallId;
78
use syntax::{ast, AstPtr};
89
use triomphe::Arc;
910

@@ -234,6 +235,22 @@ pub trait DefDatabase: InternDatabase + ExpandDatabase + Upcast<dyn ExpandDataba
234235
fn crate_notable_traits(&self, krate: CrateId) -> Option<Arc<[TraitId]>>;
235236

236237
fn crate_supports_no_std(&self, crate_id: CrateId) -> bool;
238+
239+
fn include_macro_invoc(&self, crate_id: CrateId) -> Vec<(MacroCallId, FileId)>;
240+
}
241+
242+
// return: macro call id and include file id
243+
fn include_macro_invoc(db: &dyn DefDatabase, krate: CrateId) -> Vec<(MacroCallId, FileId)> {
244+
db.crate_def_map(krate)
245+
.modules
246+
.values()
247+
.flat_map(|m| m.scope.iter_macro_invoc())
248+
.filter_map(|invoc| {
249+
db.lookup_intern_macro_call(*invoc.1)
250+
.include_file_id(db.upcast(), *invoc.1)
251+
.map(|x| (*invoc.1, x))
252+
})
253+
.collect()
237254
}
238255

239256
fn crate_def_map_wait(db: &dyn DefDatabase, krate: CrateId) -> Arc<DefMap> {

crates/hir-def/src/item_scope.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,12 @@ impl ItemScope {
336336
pub(crate) fn macro_invoc(&self, call: AstId<ast::MacroCall>) -> Option<MacroCallId> {
337337
self.macro_invocations.get(&call).copied()
338338
}
339+
340+
pub(crate) fn iter_macro_invoc(
341+
&self,
342+
) -> impl Iterator<Item = (&AstId<ast::MacroCall>, &MacroCallId)> {
343+
self.macro_invocations.iter()
344+
}
339345
}
340346

341347
impl ItemScope {

crates/hir-expand/src/lib.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,24 @@ impl MacroCallLoc {
523523
}
524524
}
525525
}
526+
527+
pub fn include_file_id(
528+
&self,
529+
db: &dyn ExpandDatabase,
530+
macro_call_id: MacroCallId,
531+
) -> Option<FileId> {
532+
if self.def.is_include() {
533+
if let Some(eager) = &self.eager {
534+
if let Ok(it) =
535+
builtin_fn_macro::include_input_to_file_id(db, macro_call_id, &eager.arg)
536+
{
537+
return Some(it);
538+
}
539+
}
540+
}
541+
542+
None
543+
}
526544
}
527545

528546
impl MacroCallKind {

crates/hir/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,10 @@ profile.workspace = true
3030
stdx.workspace = true
3131
syntax.workspace = true
3232
tt.workspace = true
33+
span.workspace = true
3334

3435
[features]
3536
in-rust-tree = []
3637

3738
[lints]
38-
workspace = true
39+
workspace = true

crates/hir/src/semantics.rs

Lines changed: 91 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@ use hir_def::{
2020
};
2121
use hir_expand::{
2222
attrs::collect_attrs, db::ExpandDatabase, files::InRealFile, name::AsName, ExpansionInfo,
23-
InMacroFile, MacroCallId, MacroFileId, MacroFileIdExt,
23+
HirFileIdExt, InMacroFile, MacroCallId, MacroFileId, MacroFileIdExt,
2424
};
2525
use itertools::Itertools;
2626
use rustc_hash::{FxHashMap, FxHashSet};
2727
use smallvec::{smallvec, SmallVec};
28+
use span::Span;
2829
use stdx::TupleExt;
2930
use syntax::{
3031
algo::skip_trivia_token,
@@ -607,29 +608,111 @@ impl<'db> SemanticsImpl<'db> {
607608
res
608609
}
609610

610-
fn descend_into_macros_impl(
611+
// return:
612+
// SourceAnalyzer(file_id that original call include!)
613+
// macro file id
614+
// token in include! macro mapped from token in params
615+
// span for the mapped token
616+
fn is_from_include_file(
611617
&self,
612618
token: SyntaxToken,
619+
) -> Option<(SourceAnalyzer, HirFileId, SyntaxToken, Span)> {
620+
let parent = token.parent()?;
621+
let file_id = self.find_file(&parent).file_id.file_id()?;
622+
623+
// iterate related crates and find all include! invocations that include_file_id matches
624+
for (invoc, _) in self
625+
.db
626+
.relevant_crates(file_id)
627+
.iter()
628+
.flat_map(|krate| self.db.include_macro_invoc(*krate))
629+
.filter(|(_, include_file_id)| *include_file_id == file_id)
630+
{
631+
// find file_id which original calls include!
632+
let Some(callnode) = invoc.as_file().original_call_node(self.db.upcast()) else {
633+
continue;
634+
};
635+
636+
// call .parse to avoid panic in .find_file
637+
let _ = self.parse(callnode.file_id);
638+
let Some(sa) = self.analyze_no_infer(&callnode.value) else { continue };
639+
640+
let expinfo = invoc.as_macro_file().expansion_info(self.db.upcast());
641+
{
642+
let InMacroFile { file_id, value } = expinfo.expanded();
643+
self.cache(value, file_id.into());
644+
}
645+
646+
// map token to the corresponding span in include! macro file
647+
let Some((_, span)) =
648+
expinfo.exp_map.iter().find(|(_, x)| x.range == token.text_range())
649+
else {
650+
continue;
651+
};
652+
653+
// get mapped token in the include! macro file
654+
let Some(InMacroFile { file_id: _, value: mapped_tokens }) =
655+
expinfo.map_range_down(span)
656+
else {
657+
continue;
658+
};
659+
660+
// if we find one, then return
661+
if let Some(t) = mapped_tokens.into_iter().next() {
662+
return Some((sa, invoc.as_file(), t, span));
663+
};
664+
}
665+
666+
None
667+
}
668+
669+
fn descend_into_macros_impl(
670+
&self,
671+
mut token: SyntaxToken,
613672
f: &mut dyn FnMut(InFile<SyntaxToken>) -> ControlFlow<()>,
614673
) {
615674
let _p = profile::span("descend_into_macros");
675+
676+
let mut include_macro_file_id_and_span = None;
677+
616678
let sa = match token.parent().and_then(|parent| self.analyze_no_infer(&parent)) {
617679
Some(it) => it,
618-
None => return,
680+
None => {
681+
// if we cannot find a source analyzer for this token, then we try to find out whether this file is included from other file
682+
let Some((it, macro_file_id, mapped_token, s)) = self.is_from_include_file(token)
683+
else {
684+
return;
685+
};
686+
687+
include_macro_file_id_and_span = Some((macro_file_id, s));
688+
token = mapped_token;
689+
it
690+
}
619691
};
620692

621-
let span = match sa.file_id.file_id() {
622-
Some(file_id) => self.db.real_span_map(file_id).span_for_range(token.text_range()),
623-
None => {
624-
stdx::never!();
625-
return;
693+
let span = if let Some((_, s)) = include_macro_file_id_and_span {
694+
s
695+
} else {
696+
match sa.file_id.file_id() {
697+
Some(file_id) => self.db.real_span_map(file_id).span_for_range(token.text_range()),
698+
None => {
699+
stdx::never!();
700+
return;
701+
}
626702
}
627703
};
628704

629705
let mut cache = self.expansion_info_cache.borrow_mut();
630706
let mut mcache = self.macro_call_cache.borrow_mut();
631707
let def_map = sa.resolver.def_map();
632708

709+
let mut stack: Vec<(_, SmallVec<[_; 2]>)> =
710+
if let Some((macro_file_id, _)) = include_macro_file_id_and_span {
711+
vec![(macro_file_id, smallvec![token])]
712+
} else {
713+
vec![(sa.file_id, smallvec![token])]
714+
};
715+
633716
let mut process_expansion_for_token = |stack: &mut Vec<_>, macro_file| {
634717
let expansion_info = cache
635718
.entry(macro_file)
@@ -651,8 +734,6 @@ impl<'db> SemanticsImpl<'db> {
651734
res
652735
};
653736

654-
let mut stack: Vec<(_, SmallVec<[_; 2]>)> = vec![(sa.file_id, smallvec![token])];
655-
656737
while let Some((file_id, mut tokens)) = stack.pop() {
657738
while let Some(token) = tokens.pop() {
658739
let was_not_remapped = (|| {

crates/ide/src/goto_definition.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ mod tests {
226226
.map(|(FileRange { file_id, range }, _)| FileRange { file_id, range })
227227
.sorted_by_key(cmp)
228228
.collect::<Vec<_>>();
229+
229230
assert_eq!(expected, navs);
230231
}
231232

@@ -236,6 +237,32 @@ mod tests {
236237
assert!(navs.is_empty(), "didn't expect this to resolve anywhere: {navs:?}")
237238
}
238239

240+
#[test]
241+
fn goto_def_in_included_file() {
242+
check(
243+
r#"
244+
//- /main.rs
245+
#[rustc_builtin_macro]
246+
macro_rules! include {}
247+
248+
include!("a.rs");
249+
250+
fn main() {
251+
foo();
252+
}
253+
254+
//- /a.rs
255+
fn func_in_include() {
256+
//^^^^^^^^^^^^^^^
257+
}
258+
259+
fn foo() {
260+
func_in_include$0();
261+
}
262+
"#,
263+
);
264+
}
265+
239266
#[test]
240267
fn goto_def_if_items_same_name() {
241268
check(

0 commit comments

Comments
 (0)