Skip to content

Commit

Permalink
Auto merge of #16439 - wasd96040501:feat/gotodef3, r=Veykril
Browse files Browse the repository at this point in the history
feat: Support for GOTO def from *inside* files included with include! macro

close #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.
  • Loading branch information
bors committed Jan 30, 2024
2 parents d13951f + b22e772 commit 22b6f96
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 12 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 18 additions & 1 deletion crates/hir-def/src/db.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
//! Defines database & queries for name resolution.
use base_db::{salsa, CrateId, SourceDatabase, Upcast};
use base_db::{salsa, CrateId, FileId, SourceDatabase, Upcast};
use either::Either;
use hir_expand::{db::ExpandDatabase, HirFileId, MacroDefId};
use intern::Interned;
use la_arena::ArenaMap;
use span::MacroCallId;
use syntax::{ast, AstPtr};
use triomphe::Arc;

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

fn crate_supports_no_std(&self, crate_id: CrateId) -> bool;

fn include_macro_invoc(&self, crate_id: CrateId) -> Vec<(MacroCallId, FileId)>;
}

// return: macro call id and include file id
fn include_macro_invoc(db: &dyn DefDatabase, krate: CrateId) -> Vec<(MacroCallId, FileId)> {
db.crate_def_map(krate)
.modules
.values()
.flat_map(|m| m.scope.iter_macro_invoc())
.filter_map(|invoc| {
db.lookup_intern_macro_call(*invoc.1)
.include_file_id(db.upcast(), *invoc.1)
.map(|x| (*invoc.1, x))
})
.collect()
}

fn crate_def_map_wait(db: &dyn DefDatabase, krate: CrateId) -> Arc<DefMap> {
Expand Down
6 changes: 6 additions & 0 deletions crates/hir-def/src/item_scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,12 @@ impl ItemScope {
pub(crate) fn macro_invoc(&self, call: AstId<ast::MacroCall>) -> Option<MacroCallId> {
self.macro_invocations.get(&call).copied()
}

pub(crate) fn iter_macro_invoc(
&self,
) -> impl Iterator<Item = (&AstId<ast::MacroCall>, &MacroCallId)> {
self.macro_invocations.iter()
}
}

impl ItemScope {
Expand Down
18 changes: 18 additions & 0 deletions crates/hir-expand/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,24 @@ impl MacroCallLoc {
}
}
}

pub fn include_file_id(
&self,
db: &dyn ExpandDatabase,
macro_call_id: MacroCallId,
) -> Option<FileId> {
if self.def.is_include() {
if let Some(eager) = &self.eager {
if let Ok(it) =
builtin_fn_macro::include_input_to_file_id(db, macro_call_id, &eager.arg)
{
return Some(it);
}
}
}

None
}
}

impl MacroCallKind {
Expand Down
3 changes: 2 additions & 1 deletion crates/hir/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ profile.workspace = true
stdx.workspace = true
syntax.workspace = true
tt.workspace = true
span.workspace = true

[features]
in-rust-tree = []

[lints]
workspace = true
workspace = true
101 changes: 91 additions & 10 deletions crates/hir/src/semantics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ use hir_def::{
};
use hir_expand::{
attrs::collect_attrs, db::ExpandDatabase, files::InRealFile, name::AsName, ExpansionInfo,
InMacroFile, MacroCallId, MacroFileId, MacroFileIdExt,
HirFileIdExt, InMacroFile, MacroCallId, MacroFileId, MacroFileIdExt,
};
use itertools::Itertools;
use rustc_hash::{FxHashMap, FxHashSet};
use smallvec::{smallvec, SmallVec};
use span::Span;
use stdx::TupleExt;
use syntax::{
algo::skip_trivia_token,
Expand Down Expand Up @@ -607,29 +608,111 @@ impl<'db> SemanticsImpl<'db> {
res
}

fn descend_into_macros_impl(
// return:
// SourceAnalyzer(file_id that original call include!)
// macro file id
// token in include! macro mapped from token in params
// span for the mapped token
fn is_from_include_file(
&self,
token: SyntaxToken,
) -> Option<(SourceAnalyzer, HirFileId, SyntaxToken, Span)> {
let parent = token.parent()?;
let file_id = self.find_file(&parent).file_id.file_id()?;

// iterate related crates and find all include! invocations that include_file_id matches
for (invoc, _) in self
.db
.relevant_crates(file_id)
.iter()
.flat_map(|krate| self.db.include_macro_invoc(*krate))
.filter(|(_, include_file_id)| *include_file_id == file_id)
{
// find file_id which original calls include!
let Some(callnode) = invoc.as_file().original_call_node(self.db.upcast()) else {
continue;
};

// call .parse to avoid panic in .find_file
let _ = self.parse(callnode.file_id);
let Some(sa) = self.analyze_no_infer(&callnode.value) else { continue };

let expinfo = invoc.as_macro_file().expansion_info(self.db.upcast());
{
let InMacroFile { file_id, value } = expinfo.expanded();
self.cache(value, file_id.into());
}

// map token to the corresponding span in include! macro file
let Some((_, span)) =
expinfo.exp_map.iter().find(|(_, x)| x.range == token.text_range())
else {
continue;
};

// get mapped token in the include! macro file
let Some(InMacroFile { file_id: _, value: mapped_tokens }) =
expinfo.map_range_down(span)
else {
continue;
};

// if we find one, then return
if let Some(t) = mapped_tokens.into_iter().next() {
return Some((sa, invoc.as_file(), t, span));
};
}

None
}

fn descend_into_macros_impl(
&self,
mut token: SyntaxToken,
f: &mut dyn FnMut(InFile<SyntaxToken>) -> ControlFlow<()>,
) {
let _p = profile::span("descend_into_macros");

let mut include_macro_file_id_and_span = None;

let sa = match token.parent().and_then(|parent| self.analyze_no_infer(&parent)) {
Some(it) => it,
None => return,
None => {
// if we cannot find a source analyzer for this token, then we try to find out whether this file is included from other file
let Some((it, macro_file_id, mapped_token, s)) = self.is_from_include_file(token)
else {
return;
};

include_macro_file_id_and_span = Some((macro_file_id, s));
token = mapped_token;
it
}
};

let span = match sa.file_id.file_id() {
Some(file_id) => self.db.real_span_map(file_id).span_for_range(token.text_range()),
None => {
stdx::never!();
return;
let span = if let Some((_, s)) = include_macro_file_id_and_span {
s
} else {
match sa.file_id.file_id() {
Some(file_id) => self.db.real_span_map(file_id).span_for_range(token.text_range()),
None => {
stdx::never!();
return;
}
}
};

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

let mut stack: Vec<(_, SmallVec<[_; 2]>)> =
if let Some((macro_file_id, _)) = include_macro_file_id_and_span {
vec![(macro_file_id, smallvec![token])]
} else {
vec![(sa.file_id, smallvec![token])]
};

let mut process_expansion_for_token = |stack: &mut Vec<_>, macro_file| {
let expansion_info = cache
.entry(macro_file)
Expand All @@ -651,8 +734,6 @@ impl<'db> SemanticsImpl<'db> {
res
};

let mut stack: Vec<(_, SmallVec<[_; 2]>)> = vec![(sa.file_id, smallvec![token])];

while let Some((file_id, mut tokens)) = stack.pop() {
while let Some(token) = tokens.pop() {
let was_not_remapped = (|| {
Expand Down
27 changes: 27 additions & 0 deletions crates/ide/src/goto_definition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ mod tests {
.map(|(FileRange { file_id, range }, _)| FileRange { file_id, range })
.sorted_by_key(cmp)
.collect::<Vec<_>>();

assert_eq!(expected, navs);
}

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

#[test]
fn goto_def_in_included_file() {
check(
r#"
//- /main.rs
#[rustc_builtin_macro]
macro_rules! include {}
include!("a.rs");
fn main() {
foo();
}
//- /a.rs
fn func_in_include() {
//^^^^^^^^^^^^^^^
}
fn foo() {
func_in_include$0();
}
"#,
);
}

#[test]
fn goto_def_if_items_same_name() {
check(
Expand Down

0 comments on commit 22b6f96

Please sign in to comment.