Skip to content

Commit 2fe586a

Browse files
bors[bot]Veykril
andauthored
Merge #9688
9688: feat: Resolve derive attribute input macro paths in ide layer r=Veykril a=Veykril Enables goto def, syntax highlighting and hover for `Foo` in `#[derive(Foo)]`. Fixes #4413 bors r+ Co-authored-by: Lukas Wirth <[email protected]>
2 parents 31306ba + bfe0fa0 commit 2fe586a

File tree

6 files changed

+164
-50
lines changed

6 files changed

+164
-50
lines changed

crates/ide/src/goto_definition.rs

Lines changed: 57 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use hir::{AsAssocItem, InFile, ModuleDef, Semantics};
55
use ide_db::{
66
base_db::{AnchoredPath, FileId, FileLoader},
77
defs::{Definition, NameClass, NameRefClass},
8-
helpers::pick_best_token,
8+
helpers::{pick_best_token, try_resolve_derive_input_at},
99
RootDatabase,
1010
};
1111
use syntax::{ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, TextRange, T};
@@ -78,38 +78,49 @@ pub(crate) fn goto_definition(
7878
} else {
7979
reference_definition(&sema, Either::Left(&lt))
8080
},
81-
ast::TokenTree(tt) => try_lookup_include_path(sema.db, tt, token, position.file_id)?,
81+
ast::TokenTree(tt) => try_lookup_include_path_or_derive(&sema, tt, token, position.file_id)?,
8282
_ => return None,
8383
}
8484
};
8585

8686
Some(RangeInfo::new(original_token.text_range(), navs))
8787
}
8888

89-
fn try_lookup_include_path(
90-
db: &RootDatabase,
89+
fn try_lookup_include_path_or_derive(
90+
sema: &Semantics<RootDatabase>,
9191
tt: ast::TokenTree,
9292
token: SyntaxToken,
9393
file_id: FileId,
9494
) -> Option<Vec<NavigationTarget>> {
95-
let path = ast::String::cast(token)?.value()?.into_owned();
96-
let macro_call = tt.syntax().parent().and_then(ast::MacroCall::cast)?;
97-
let name = macro_call.path()?.segment()?.name_ref()?;
98-
if !matches!(&*name.text(), "include" | "include_str" | "include_bytes") {
99-
return None;
100-
}
101-
let file_id = db.resolve_path(AnchoredPath { anchor: file_id, path: &path })?;
102-
let size = db.file_text(file_id).len().try_into().ok()?;
103-
Some(vec![NavigationTarget {
104-
file_id,
105-
full_range: TextRange::new(0.into(), size),
106-
name: path.into(),
107-
focus_range: None,
108-
kind: None,
109-
container_name: None,
110-
description: None,
111-
docs: None,
112-
}])
95+
match ast::String::cast(token.clone()) {
96+
Some(token) => {
97+
let path = token.value()?.into_owned();
98+
let macro_call = tt.syntax().parent().and_then(ast::MacroCall::cast)?;
99+
let name = macro_call.path()?.segment()?.name_ref()?;
100+
if !matches!(&*name.text(), "include" | "include_str" | "include_bytes") {
101+
return None;
102+
}
103+
let file_id = sema.db.resolve_path(AnchoredPath { anchor: file_id, path: &path })?;
104+
let size = sema.db.file_text(file_id).len().try_into().ok()?;
105+
Some(vec![NavigationTarget {
106+
file_id,
107+
full_range: TextRange::new(0.into(), size),
108+
name: path.into(),
109+
focus_range: None,
110+
kind: None,
111+
container_name: None,
112+
description: None,
113+
docs: None,
114+
}])
115+
}
116+
None => try_resolve_derive_input_at(
117+
sema,
118+
&tt.syntax().ancestors().nth(2).and_then(ast::Attr::cast)?,
119+
&token,
120+
)
121+
.and_then(|it| it.try_to_nav(sema.db))
122+
.map(|it| vec![it]),
123+
}
113124
}
114125

115126
/// finds the trait definition of an impl'd item
@@ -1383,4 +1394,28 @@ impl Twait for Stwuct {
13831394
"#,
13841395
);
13851396
}
1397+
1398+
#[test]
1399+
fn goto_def_derive_input() {
1400+
check(
1401+
r#"
1402+
#[rustc_builtin_macro]
1403+
pub macro Copy {}
1404+
// ^^^^
1405+
#[derive(Copy$0)]
1406+
struct Foo;
1407+
"#,
1408+
);
1409+
check(
1410+
r#"
1411+
mod foo {
1412+
#[rustc_builtin_macro]
1413+
pub macro Copy {}
1414+
// ^^^^
1415+
}
1416+
#[derive(foo::Copy$0)]
1417+
struct Foo;
1418+
"#,
1419+
);
1420+
}
13861421
}

crates/ide/src/hover.rs

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use ide_db::{
55
defs::{Definition, NameClass, NameRefClass},
66
helpers::{
77
generated_lints::{CLIPPY_LINTS, DEFAULT_LINTS, FEATURES},
8-
pick_best_token, FamousDefs,
8+
pick_best_token, try_resolve_derive_input_at, FamousDefs,
99
},
1010
RootDatabase,
1111
};
@@ -129,8 +129,12 @@ pub(crate) fn hover(
129129
})?;
130130
range = Some(idl_range);
131131
resolve_doc_path_for_def(db, def, &link, ns).map(Definition::ModuleDef)
132-
} else if let res@Some(_) = try_hover_for_attribute(&token) {
133-
return res;
132+
} else if let Some(attr) = token.ancestors().find_map(ast::Attr::cast) {
133+
if let res@Some(_) = try_hover_for_lint(&attr, &token) {
134+
return res;
135+
} else {
136+
try_resolve_derive_input_at(&sema, &attr, &token).map(Definition::Macro)
137+
}
134138
} else {
135139
None
136140
}
@@ -197,8 +201,7 @@ pub(crate) fn hover(
197201
Some(RangeInfo::new(range, res))
198202
}
199203

200-
fn try_hover_for_attribute(token: &SyntaxToken) -> Option<RangeInfo<HoverResult>> {
201-
let attr = token.ancestors().find_map(ast::Attr::cast)?;
204+
fn try_hover_for_lint(attr: &ast::Attr, token: &SyntaxToken) -> Option<RangeInfo<HoverResult>> {
202205
let (path, tt) = attr.as_simple_call()?;
203206
if !tt.syntax().text_range().contains(token.text_range().start()) {
204207
return None;
@@ -3839,4 +3842,48 @@ use crate as foo$0;
38393842
"#]],
38403843
);
38413844
}
3845+
3846+
#[test]
3847+
fn hover_derive_input() {
3848+
check(
3849+
r#"
3850+
#[rustc_builtin_macro]
3851+
pub macro Copy {}
3852+
#[derive(Copy$0)]
3853+
struct Foo;
3854+
"#,
3855+
expect![[r#"
3856+
*(Copy)*
3857+
3858+
```rust
3859+
test
3860+
```
3861+
3862+
```rust
3863+
pub macro Copy
3864+
```
3865+
"#]],
3866+
);
3867+
check(
3868+
r#"
3869+
mod foo {
3870+
#[rustc_builtin_macro]
3871+
pub macro Copy {}
3872+
}
3873+
#[derive(foo::Copy$0)]
3874+
struct Foo;
3875+
"#,
3876+
expect![[r#"
3877+
*(foo::Copy)*
3878+
3879+
```rust
3880+
test
3881+
```
3882+
3883+
```rust
3884+
pub macro Copy
3885+
```
3886+
"#]],
3887+
);
3888+
}
38423889
}

crates/ide/src/syntax_highlighting/highlight.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use hir::{AsAssocItem, HasVisibility, Semantics};
44
use ide_db::{
55
defs::{Definition, NameClass, NameRefClass},
6+
helpers::try_resolve_derive_input_at,
67
RootDatabase, SymbolKind,
78
};
89
use rustc_hash::FxHashMap;
@@ -87,7 +88,18 @@ pub(super) fn element(
8788
_ => Highlight::from(SymbolKind::LifetimeParam) | HlMod::Definition,
8889
}
8990
}
90-
IDENT if parent_matches::<ast::TokenTree>(&element) => HlTag::None.into(),
91+
IDENT if parent_matches::<ast::TokenTree>(&element) => {
92+
if let Some((attr, token)) =
93+
element.ancestors().nth(2).and_then(ast::Attr::cast).zip(element.as_token())
94+
{
95+
match try_resolve_derive_input_at(sema, &attr, token) {
96+
Some(makro) => highlight_def(sema.db, krate, Definition::Macro(makro)),
97+
None => HlTag::None.into(),
98+
}
99+
} else {
100+
HlTag::None.into()
101+
}
102+
}
91103
p if p.is_punct() => match p {
92104
T![&] if parent_matches::<ast::BinExpr>(&element) => HlOperator::Bitwise.into(),
93105
T![&] => {

crates/ide/src/syntax_highlighting/test_data/highlighting.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@
9191
<span class="brace">}</span>
9292
<span class="brace">}</span>
9393

94-
<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute">derive</span><span class="parenthesis attribute">(</span><span class="none attribute">Copy</span><span class="parenthesis attribute">)</span><span class="attribute attribute">]</span>
94+
<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="builtin_attr attribute">derive</span><span class="parenthesis attribute">(</span><span class="macro attribute">Copy</span><span class="parenthesis attribute">)</span><span class="attribute attribute">]</span>
9595
<span class="keyword">struct</span> <span class="struct declaration">FooCopy</span> <span class="brace">{</span>
9696
<span class="field declaration">x</span><span class="colon">:</span> <span class="builtin_type">u32</span><span class="comma">,</span>
9797
<span class="brace">}</span>

crates/ide_assists/src/handlers/fill_match_arms.rs

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
4343
let expr = match_expr.expr()?;
4444

4545
let mut arms: Vec<MatchArm> = match_arm_list.arms().collect();
46-
if arms.len() == 1 {
47-
if let Some(Pat::WildcardPat(..)) = arms[0].pat() {
46+
if let [arm] = arms.as_slice() {
47+
if let Some(Pat::WildcardPat(..)) = arm.pat() {
4848
arms.clear();
4949
}
5050
}
@@ -73,9 +73,9 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
7373
.filter_map(|variant| build_pat(ctx.db(), module, variant))
7474
.filter(|variant_pat| is_variant_missing(&top_lvl_pats, variant_pat));
7575

76-
let missing_pats: Box<dyn Iterator<Item = _>> = if Some(enum_def)
77-
== FamousDefs(&ctx.sema, Some(module.krate())).core_option_Option().map(lift_enum)
78-
{
76+
let option_enum =
77+
FamousDefs(&ctx.sema, Some(module.krate())).core_option_Option().map(lift_enum);
78+
let missing_pats: Box<dyn Iterator<Item = _>> = if Some(enum_def) == option_enum {
7979
// Match `Some` variant first.
8080
cov_mark::hit!(option_order);
8181
Box::new(missing_pats.rev())
@@ -136,7 +136,7 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
136136
.arms()
137137
.find(|arm| matches!(arm.pat(), Some(ast::Pat::WildcardPat(_))));
138138
if let Some(arm) = catch_all_arm {
139-
arm.remove()
139+
arm.remove();
140140
}
141141
let mut first_new_arm = None;
142142
for arm in missing_arms {
@@ -214,13 +214,7 @@ impl ExtendedEnum {
214214
fn resolve_enum_def(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<ExtendedEnum> {
215215
sema.type_of_expr(expr)?.autoderef(sema.db).find_map(|ty| match ty.as_adt() {
216216
Some(Adt::Enum(e)) => Some(ExtendedEnum::Enum(e)),
217-
_ => {
218-
if ty.is_bool() {
219-
Some(ExtendedEnum::Bool)
220-
} else {
221-
None
222-
}
223-
}
217+
_ => ty.is_bool().then(|| ExtendedEnum::Bool),
224218
})
225219
}
226220

@@ -237,13 +231,7 @@ fn resolve_tuple_of_enum_def(
237231
// For now we only handle expansion for a tuple of enums. Here
238232
// we map non-enum items to None and rely on `collect` to
239233
// convert Vec<Option<hir::Enum>> into Option<Vec<hir::Enum>>.
240-
_ => {
241-
if ty.is_bool() {
242-
Some(ExtendedEnum::Bool)
243-
} else {
244-
None
245-
}
246-
}
234+
_ => ty.is_bool().then(|| ExtendedEnum::Bool),
247235
})
248236
})
249237
.collect()

crates/ide_db/src/helpers.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use either::Either;
1212
use hir::{Crate, Enum, ItemInNs, MacroDef, Module, ModuleDef, Name, ScopeDef, Semantics, Trait};
1313
use syntax::{
1414
ast::{self, make, LoopBodyOwner},
15-
AstNode, SyntaxKind, SyntaxToken, TokenAtOffset, WalkEvent,
15+
AstNode, Direction, SyntaxElement, SyntaxKind, SyntaxToken, TokenAtOffset, WalkEvent, T,
1616
};
1717

1818
use crate::RootDatabase;
@@ -25,6 +25,38 @@ pub fn item_name(db: &RootDatabase, item: ItemInNs) -> Option<Name> {
2525
}
2626
}
2727

28+
/// Resolves the path at the cursor token as a derive macro if it inside a token tree of a derive attribute.
29+
pub fn try_resolve_derive_input_at(
30+
sema: &Semantics<RootDatabase>,
31+
derive_attr: &ast::Attr,
32+
cursor: &SyntaxToken,
33+
) -> Option<MacroDef> {
34+
use itertools::Itertools;
35+
if cursor.kind() != T![ident] {
36+
return None;
37+
}
38+
let tt = match derive_attr.as_simple_call() {
39+
Some((name, tt))
40+
if name == "derive" && tt.syntax().text_range().contains_range(cursor.text_range()) =>
41+
{
42+
tt
43+
}
44+
_ => return None,
45+
};
46+
let tokens: Vec<_> = cursor
47+
.siblings_with_tokens(Direction::Prev)
48+
.flat_map(SyntaxElement::into_token)
49+
.take_while(|tok| tok.kind() != T!['('] && tok.kind() != T![,])
50+
.collect();
51+
let path = ast::Path::parse(&tokens.into_iter().rev().join("")).ok()?;
52+
match sema.scope(tt.syntax()).speculative_resolve(&path) {
53+
Some(hir::PathResolution::Macro(makro)) if makro.kind() == hir::MacroKind::Derive => {
54+
Some(makro)
55+
}
56+
_ => None,
57+
}
58+
}
59+
2860
/// Picks the token with the highest rank returned by the passed in function.
2961
pub fn pick_best_token(
3062
tokens: TokenAtOffset<SyntaxToken>,

0 commit comments

Comments
 (0)