mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-01 11:31:15 +00:00
feat: Support for GOTO def from *inside* files included with include! macro
This commit is contained in:
parent
a911652360
commit
b22e772cab
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -483,6 +483,7 @@ dependencies = [
|
|||||||
"profile",
|
"profile",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
|
"span",
|
||||||
"stdx",
|
"stdx",
|
||||||
"syntax",
|
"syntax",
|
||||||
"triomphe",
|
"triomphe",
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
//! Defines database & queries for name resolution.
|
//! 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 either::Either;
|
||||||
use hir_expand::{db::ExpandDatabase, HirFileId, MacroDefId};
|
use hir_expand::{db::ExpandDatabase, HirFileId, MacroDefId};
|
||||||
use intern::Interned;
|
use intern::Interned;
|
||||||
use la_arena::ArenaMap;
|
use la_arena::ArenaMap;
|
||||||
|
use span::MacroCallId;
|
||||||
use syntax::{ast, AstPtr};
|
use syntax::{ast, AstPtr};
|
||||||
use triomphe::Arc;
|
use triomphe::Arc;
|
||||||
|
|
||||||
@ -234,6 +235,22 @@ pub trait DefDatabase: InternDatabase + ExpandDatabase + Upcast<dyn ExpandDataba
|
|||||||
fn crate_notable_traits(&self, krate: CrateId) -> Option<Arc<[TraitId]>>;
|
fn crate_notable_traits(&self, krate: CrateId) -> Option<Arc<[TraitId]>>;
|
||||||
|
|
||||||
fn crate_supports_no_std(&self, crate_id: CrateId) -> bool;
|
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> {
|
fn crate_def_map_wait(db: &dyn DefDatabase, krate: CrateId) -> Arc<DefMap> {
|
||||||
|
@ -336,6 +336,12 @@ impl ItemScope {
|
|||||||
pub(crate) fn macro_invoc(&self, call: AstId<ast::MacroCall>) -> Option<MacroCallId> {
|
pub(crate) fn macro_invoc(&self, call: AstId<ast::MacroCall>) -> Option<MacroCallId> {
|
||||||
self.macro_invocations.get(&call).copied()
|
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 {
|
impl ItemScope {
|
||||||
|
@ -520,6 +520,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 {
|
impl MacroCallKind {
|
||||||
|
@ -30,9 +30,10 @@ profile.workspace = true
|
|||||||
stdx.workspace = true
|
stdx.workspace = true
|
||||||
syntax.workspace = true
|
syntax.workspace = true
|
||||||
tt.workspace = true
|
tt.workspace = true
|
||||||
|
span.workspace = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
in-rust-tree = []
|
in-rust-tree = []
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
@ -20,11 +20,12 @@ use hir_def::{
|
|||||||
};
|
};
|
||||||
use hir_expand::{
|
use hir_expand::{
|
||||||
attrs::collect_attrs, db::ExpandDatabase, files::InRealFile, name::AsName, ExpansionInfo,
|
attrs::collect_attrs, db::ExpandDatabase, files::InRealFile, name::AsName, ExpansionInfo,
|
||||||
InMacroFile, MacroCallId, MacroFileId, MacroFileIdExt,
|
HirFileIdExt, InMacroFile, MacroCallId, MacroFileId, MacroFileIdExt,
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use rustc_hash::{FxHashMap, FxHashSet};
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
use span::Span;
|
||||||
use stdx::TupleExt;
|
use stdx::TupleExt;
|
||||||
use syntax::{
|
use syntax::{
|
||||||
algo::skip_trivia_token,
|
algo::skip_trivia_token,
|
||||||
@ -607,22 +608,97 @@ impl<'db> SemanticsImpl<'db> {
|
|||||||
res
|
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,
|
&self,
|
||||||
token: SyntaxToken,
|
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<()>,
|
f: &mut dyn FnMut(InFile<SyntaxToken>) -> ControlFlow<()>,
|
||||||
) {
|
) {
|
||||||
let _p = profile::span("descend_into_macros");
|
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)) {
|
let sa = match token.parent().and_then(|parent| self.analyze_no_infer(&parent)) {
|
||||||
Some(it) => it,
|
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() {
|
let span = if let Some((_, s)) = include_macro_file_id_and_span {
|
||||||
Some(file_id) => self.db.real_span_map(file_id).span_for_range(token.text_range()),
|
s
|
||||||
None => {
|
} else {
|
||||||
stdx::never!();
|
match sa.file_id.file_id() {
|
||||||
return;
|
Some(file_id) => self.db.real_span_map(file_id).span_for_range(token.text_range()),
|
||||||
|
None => {
|
||||||
|
stdx::never!();
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -630,6 +706,13 @@ impl<'db> SemanticsImpl<'db> {
|
|||||||
let mut mcache = self.macro_call_cache.borrow_mut();
|
let mut mcache = self.macro_call_cache.borrow_mut();
|
||||||
let def_map = sa.resolver.def_map();
|
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 mut process_expansion_for_token = |stack: &mut Vec<_>, macro_file| {
|
||||||
let expansion_info = cache
|
let expansion_info = cache
|
||||||
.entry(macro_file)
|
.entry(macro_file)
|
||||||
@ -651,8 +734,6 @@ impl<'db> SemanticsImpl<'db> {
|
|||||||
res
|
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((file_id, mut tokens)) = stack.pop() {
|
||||||
while let Some(token) = tokens.pop() {
|
while let Some(token) = tokens.pop() {
|
||||||
let was_not_remapped = (|| {
|
let was_not_remapped = (|| {
|
||||||
|
@ -226,6 +226,7 @@ mod tests {
|
|||||||
.map(|(FileRange { file_id, range }, _)| FileRange { file_id, range })
|
.map(|(FileRange { file_id, range }, _)| FileRange { file_id, range })
|
||||||
.sorted_by_key(cmp)
|
.sorted_by_key(cmp)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
assert_eq!(expected, navs);
|
assert_eq!(expected, navs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,6 +237,32 @@ mod tests {
|
|||||||
assert!(navs.is_empty(), "didn't expect this to resolve anywhere: {navs:?}")
|
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]
|
#[test]
|
||||||
fn goto_def_if_items_same_name() {
|
fn goto_def_if_items_same_name() {
|
||||||
check(
|
check(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user