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:
commit
b4e3fec176
@ -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(
|
||||||
|
Loading…
Reference in New Issue
Block a user