Auto merge of #14895 - lowr:fix/goto-type-def-tokens-in-tt, r=Veykril

fix: don't try determining type of token inside macro calls

When we're requested `Go to Type Definition`, we first downmap the token in question to tokens in every macro call expansion involved, and then determine the type of those mapped tokens by looking for the nearest ancestor node that is either expression or pattern (or a few others). This procedure has one flaw: When the downmapped token is inside another macro call, the nearest ancestor node to retrieve the type of is *that* macro call. That's not what we should return in general and therefore we should disregard it.

Notably, now that we expand built-in `format_arg!` and its family macros, we're always returning [`Arguments`] when one `Go to Type Definition` at `dbg!(variable$0)` along with the actual type of `variable` without this patch.

[`Arguments`]: https://doc.rust-lang.org/nightly/core/fmt/struct.Arguments.html
This commit is contained in:
bors 2023-05-26 10:01:11 +00:00
commit b4e3fec176

View File

@ -38,32 +38,41 @@ pub(crate) fn goto_type_definition(
}; };
let range = token.text_range(); let range = token.text_range();
sema.descend_into_macros(token) sema.descend_into_macros(token)
.iter() .into_iter()
.filter_map(|token| { .filter_map(|token| {
let ty = sema.token_ancestors_with_macros(token.clone()).find_map(|node| { let ty = sema
let ty = match_ast! { .token_ancestors_with_macros(token)
match node { // When `token` is within a macro call, we can't determine its type. Don't continue
ast::Expr(it) => sema.type_of_expr(&it)?.original, // this traversal because otherwise we'll end up returning the type of *that* macro
ast::Pat(it) => sema.type_of_pat(&it)?.original, // call, which is not what we want in general.
ast::SelfParam(it) => sema.type_of_self(&it)?, //
ast::Type(it) => sema.resolve_type(&it)?, // Macro calls always wrap `TokenTree`s, so it's sufficient and efficient to test
ast::RecordField(it) => sema.to_def(&it).map(|d| d.ty(db.upcast()))?, // if the current node is a `TokenTree`.
// can't match on RecordExprField directly as `ast::Expr` will match an iteration too early otherwise .take_while(|node| !ast::TokenTree::can_cast(node.kind()))
ast::NameRef(it) => { .find_map(|node| {
if let Some(record_field) = ast::RecordExprField::for_name_ref(&it) { let ty = match_ast! {
let (_, _, ty) = sema.resolve_record_field(&record_field)?; match node {
ty ast::Expr(it) => sema.type_of_expr(&it)?.original,
} else { ast::Pat(it) => sema.type_of_pat(&it)?.original,
let record_field = ast::RecordPatField::for_field_name_ref(&it)?; ast::SelfParam(it) => sema.type_of_self(&it)?,
sema.resolve_record_pat_field(&record_field)?.1 ast::Type(it) => sema.resolve_type(&it)?,
} ast::RecordField(it) => sema.to_def(&it)?.ty(db.upcast()),
}, // can't match on RecordExprField directly as `ast::Expr` will match an iteration too early otherwise
_ => return None, ast::NameRef(it) => {
} if let Some(record_field) = ast::RecordExprField::for_name_ref(&it) {
}; let (_, _, ty) = sema.resolve_record_field(&record_field)?;
ty
} else {
let record_field = ast::RecordPatField::for_field_name_ref(&it)?;
sema.resolve_record_pat_field(&record_field)?.1
}
},
_ => return None,
}
};
Some(ty) Some(ty)
}); });
ty ty
}) })
.for_each(|ty| { .for_each(|ty| {
@ -94,7 +103,7 @@ mod tests {
fn check(ra_fixture: &str) { fn check(ra_fixture: &str) {
let (analysis, position, expected) = fixture::annotations(ra_fixture); let (analysis, position, expected) = fixture::annotations(ra_fixture);
let navs = analysis.goto_type_definition(position).unwrap().unwrap().info; let navs = analysis.goto_type_definition(position).unwrap().unwrap().info;
assert_ne!(navs.len(), 0); assert!(!navs.is_empty(), "navigation is empty");
let cmp = |&FileRange { file_id, range }: &_| (file_id, range.start()); let cmp = |&FileRange { file_id, range }: &_| (file_id, range.start());
let navs = navs let navs = navs
@ -104,7 +113,7 @@ fn check(ra_fixture: &str) {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let expected = expected let expected = expected
.into_iter() .into_iter()
.map(|(FileRange { file_id, range }, _)| FileRange { file_id, range }) .map(|(file_range, _)| file_range)
.sorted_by_key(cmp) .sorted_by_key(cmp)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
assert_eq!(expected, navs); assert_eq!(expected, navs);
@ -198,6 +207,32 @@ struct Foo {}
); );
} }
#[test]
fn dont_collect_type_from_token_in_macro_call() {
check(
r#"
struct DontCollectMe;
struct S;
//^
macro_rules! inner {
($t:tt) => { DontCollectMe }
}
macro_rules! m {
($t:ident) => {
match $t {
_ => inner!($t);
}
}
}
fn test() {
m!($0S);
}
"#,
);
}
#[test] #[test]
fn goto_type_definition_for_param() { fn goto_type_definition_for_param() {
check( check(