Skip to content

Commit 397c8e5

Browse files
committed
fix: don't try determining type of token inside macro calls
1 parent 615aaa4 commit 397c8e5

File tree

1 file changed

+61
-26
lines changed

1 file changed

+61
-26
lines changed

crates/ide/src/goto_type_definition.rs

Lines changed: 61 additions & 26 deletions
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)