Skip to content

Commit b4e3fec

Browse files
committed
Auto merge of rust-lang#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
2 parents 615aaa4 + 397c8e5 commit b4e3fec

File tree

1 file changed

+61
-26
lines changed

1 file changed

+61
-26
lines changed

crates/ide/src/goto_type_definition.rs

+61-26
Original file line numberDiff line numberDiff line change
@@ -38,32 +38,41 @@ pub(crate) fn goto_type_definition(
3838
};
3939
let range = token.text_range();
4040
sema.descend_into_macros(token)
41-
.iter()
41+
.into_iter()
4242
.filter_map(|token| {
43-
let ty = sema.token_ancestors_with_macros(token.clone()).find_map(|node| {
44-
let ty = match_ast! {
45-
match node {
46-
ast::Expr(it) => sema.type_of_expr(&it)?.original,
47-
ast::Pat(it) => sema.type_of_pat(&it)?.original,
48-
ast::SelfParam(it) => sema.type_of_self(&it)?,
49-
ast::Type(it) => sema.resolve_type(&it)?,
50-
ast::RecordField(it) => sema.to_def(&it).map(|d| d.ty(db.upcast()))?,
51-
// can't match on RecordExprField directly as `ast::Expr` will match an iteration too early otherwise
52-
ast::NameRef(it) => {
53-
if let Some(record_field) = ast::RecordExprField::for_name_ref(&it) {
54-
let (_, _, ty) = sema.resolve_record_field(&record_field)?;
55-
ty
56-
} else {
57-
let record_field = ast::RecordPatField::for_field_name_ref(&it)?;
58-
sema.resolve_record_pat_field(&record_field)?.1
59-
}
60-
},
61-
_ => return None,
62-
}
63-
};
43+
let ty = sema
44+
.token_ancestors_with_macros(token)
45+
// When `token` is within a macro call, we can't determine its type. Don't continue
46+
// this traversal because otherwise we'll end up returning the type of *that* macro
47+
// call, which is not what we want in general.
48+
//
49+
// Macro calls always wrap `TokenTree`s, so it's sufficient and efficient to test
50+
// if the current node is a `TokenTree`.
51+
.take_while(|node| !ast::TokenTree::can_cast(node.kind()))
52+
.find_map(|node| {
53+
let ty = match_ast! {
54+
match node {
55+
ast::Expr(it) => sema.type_of_expr(&it)?.original,
56+
ast::Pat(it) => sema.type_of_pat(&it)?.original,
57+
ast::SelfParam(it) => sema.type_of_self(&it)?,
58+
ast::Type(it) => sema.resolve_type(&it)?,
59+
ast::RecordField(it) => sema.to_def(&it)?.ty(db.upcast()),
60+
// can't match on RecordExprField directly as `ast::Expr` will match an iteration too early otherwise
61+
ast::NameRef(it) => {
62+
if let Some(record_field) = ast::RecordExprField::for_name_ref(&it) {
63+
let (_, _, ty) = sema.resolve_record_field(&record_field)?;
64+
ty
65+
} else {
66+
let record_field = ast::RecordPatField::for_field_name_ref(&it)?;
67+
sema.resolve_record_pat_field(&record_field)?.1
68+
}
69+
},
70+
_ => return None,
71+
}
72+
};
6473

65-
Some(ty)
66-
});
74+
Some(ty)
75+
});
6776
ty
6877
})
6978
.for_each(|ty| {
@@ -94,7 +103,7 @@ mod tests {
94103
fn check(ra_fixture: &str) {
95104
let (analysis, position, expected) = fixture::annotations(ra_fixture);
96105
let navs = analysis.goto_type_definition(position).unwrap().unwrap().info;
97-
assert_ne!(navs.len(), 0);
106+
assert!(!navs.is_empty(), "navigation is empty");
98107

99108
let cmp = |&FileRange { file_id, range }: &_| (file_id, range.start());
100109
let navs = navs
@@ -104,7 +113,7 @@ mod tests {
104113
.collect::<Vec<_>>();
105114
let expected = expected
106115
.into_iter()
107-
.map(|(FileRange { file_id, range }, _)| FileRange { file_id, range })
116+
.map(|(file_range, _)| file_range)
108117
.sorted_by_key(cmp)
109118
.collect::<Vec<_>>();
110119
assert_eq!(expected, navs);
@@ -198,6 +207,32 @@ id! {
198207
);
199208
}
200209

210+
#[test]
211+
fn dont_collect_type_from_token_in_macro_call() {
212+
check(
213+
r#"
214+
struct DontCollectMe;
215+
struct S;
216+
//^
217+
218+
macro_rules! inner {
219+
($t:tt) => { DontCollectMe }
220+
}
221+
macro_rules! m {
222+
($t:ident) => {
223+
match $t {
224+
_ => inner!($t);
225+
}
226+
}
227+
}
228+
229+
fn test() {
230+
m!($0S);
231+
}
232+
"#,
233+
);
234+
}
235+
201236
#[test]
202237
fn goto_type_definition_for_param() {
203238
check(

0 commit comments

Comments
 (0)