Resolve derive attribute input macro paths in ide layer

This commit is contained in:
Lukas Wirth 2021-07-24 20:35:38 +02:00
parent 2e45e47c83
commit bfe0fa009e
6 changed files with 163 additions and 49 deletions

View File

@ -5,7 +5,7 @@ use hir::{AsAssocItem, InFile, ModuleDef, Semantics};
use ide_db::{
base_db::{AnchoredPath, FileId, FileLoader},
defs::{Definition, NameClass, NameRefClass},
helpers::pick_best_token,
helpers::{pick_best_token, try_resolve_derive_input_at},
RootDatabase,
};
use syntax::{ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, TextRange, T};
@ -78,7 +78,7 @@ pub(crate) fn goto_definition(
} else {
reference_definition(&sema, Either::Left(&lt))
},
ast::TokenTree(tt) => try_lookup_include_path(sema.db, tt, token, position.file_id)?,
ast::TokenTree(tt) => try_lookup_include_path_or_derive(&sema, tt, token, position.file_id)?,
_ => return None,
}
};
@ -86,20 +86,22 @@ pub(crate) fn goto_definition(
Some(RangeInfo::new(original_token.text_range(), navs))
}
fn try_lookup_include_path(
db: &RootDatabase,
fn try_lookup_include_path_or_derive(
sema: &Semantics<RootDatabase>,
tt: ast::TokenTree,
token: SyntaxToken,
file_id: FileId,
) -> Option<Vec<NavigationTarget>> {
let path = ast::String::cast(token)?.value()?.into_owned();
match ast::String::cast(token.clone()) {
Some(token) => {
let path = token.value()?.into_owned();
let macro_call = tt.syntax().parent().and_then(ast::MacroCall::cast)?;
let name = macro_call.path()?.segment()?.name_ref()?;
if !matches!(&*name.text(), "include" | "include_str" | "include_bytes") {
return None;
}
let file_id = db.resolve_path(AnchoredPath { anchor: file_id, path: &path })?;
let size = db.file_text(file_id).len().try_into().ok()?;
let file_id = sema.db.resolve_path(AnchoredPath { anchor: file_id, path: &path })?;
let size = sema.db.file_text(file_id).len().try_into().ok()?;
Some(vec![NavigationTarget {
file_id,
full_range: TextRange::new(0.into(), size),
@ -111,6 +113,15 @@ fn try_lookup_include_path(
docs: None,
}])
}
None => try_resolve_derive_input_at(
sema,
&tt.syntax().ancestors().nth(2).and_then(ast::Attr::cast)?,
&token,
)
.and_then(|it| it.try_to_nav(sema.db))
.map(|it| vec![it]),
}
}
/// finds the trait definition of an impl'd item
/// e.g.
@ -1383,4 +1394,28 @@ impl Twait for Stwuct {
"#,
);
}
#[test]
fn goto_def_derive_input() {
check(
r#"
#[rustc_builtin_macro]
pub macro Copy {}
// ^^^^
#[derive(Copy$0)]
struct Foo;
"#,
);
check(
r#"
mod foo {
#[rustc_builtin_macro]
pub macro Copy {}
// ^^^^
}
#[derive(foo::Copy$0)]
struct Foo;
"#,
);
}
}

View File

@ -5,7 +5,7 @@ use ide_db::{
defs::{Definition, NameClass, NameRefClass},
helpers::{
generated_lints::{CLIPPY_LINTS, DEFAULT_LINTS, FEATURES},
pick_best_token, FamousDefs,
pick_best_token, try_resolve_derive_input_at, FamousDefs,
},
RootDatabase,
};
@ -129,8 +129,12 @@ pub(crate) fn hover(
})?;
range = Some(idl_range);
resolve_doc_path_for_def(db, def, &link, ns).map(Definition::ModuleDef)
} else if let res@Some(_) = try_hover_for_attribute(&token) {
} else if let Some(attr) = token.ancestors().find_map(ast::Attr::cast) {
if let res@Some(_) = try_hover_for_lint(&attr, &token) {
return res;
} else {
try_resolve_derive_input_at(&sema, &attr, &token).map(Definition::Macro)
}
} else {
None
}
@ -197,8 +201,7 @@ pub(crate) fn hover(
Some(RangeInfo::new(range, res))
}
fn try_hover_for_attribute(token: &SyntaxToken) -> Option<RangeInfo<HoverResult>> {
let attr = token.ancestors().find_map(ast::Attr::cast)?;
fn try_hover_for_lint(attr: &ast::Attr, token: &SyntaxToken) -> Option<RangeInfo<HoverResult>> {
let (path, tt) = attr.as_simple_call()?;
if !tt.syntax().text_range().contains(token.text_range().start()) {
return None;
@ -3839,4 +3842,48 @@ use crate as foo$0;
"#]],
);
}
#[test]
fn hover_derive_input() {
check(
r#"
#[rustc_builtin_macro]
pub macro Copy {}
#[derive(Copy$0)]
struct Foo;
"#,
expect![[r#"
*(Copy)*
```rust
test
```
```rust
pub macro Copy
```
"#]],
);
check(
r#"
mod foo {
#[rustc_builtin_macro]
pub macro Copy {}
}
#[derive(foo::Copy$0)]
struct Foo;
"#,
expect![[r#"
*(foo::Copy)*
```rust
test
```
```rust
pub macro Copy
```
"#]],
);
}
}

View File

@ -3,6 +3,7 @@
use hir::{AsAssocItem, HasVisibility, Semantics};
use ide_db::{
defs::{Definition, NameClass, NameRefClass},
helpers::try_resolve_derive_input_at,
RootDatabase, SymbolKind,
};
use rustc_hash::FxHashMap;
@ -87,7 +88,18 @@ pub(super) fn element(
_ => Highlight::from(SymbolKind::LifetimeParam) | HlMod::Definition,
}
}
IDENT if parent_matches::<ast::TokenTree>(&element) => HlTag::None.into(),
IDENT if parent_matches::<ast::TokenTree>(&element) => {
if let Some((attr, token)) =
element.ancestors().nth(2).and_then(ast::Attr::cast).zip(element.as_token())
{
match try_resolve_derive_input_at(sema, &attr, token) {
Some(makro) => highlight_def(sema.db, krate, Definition::Macro(makro)),
None => HlTag::None.into(),
}
} else {
HlTag::None.into()
}
}
p if p.is_punct() => match p {
T![&] if parent_matches::<ast::BinExpr>(&element) => HlOperator::Bitwise.into(),
T![&] => {

View File

@ -91,7 +91,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
<span class="brace">}</span>
<span class="brace">}</span>
<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>
<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>
<span class="keyword">struct</span> <span class="struct declaration">FooCopy</span> <span class="brace">{</span>
<span class="field declaration">x</span><span class="colon">:</span> <span class="builtin_type">u32</span><span class="comma">,</span>
<span class="brace">}</span>

View File

@ -43,8 +43,8 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
let expr = match_expr.expr()?;
let mut arms: Vec<MatchArm> = match_arm_list.arms().collect();
if arms.len() == 1 {
if let Some(Pat::WildcardPat(..)) = arms[0].pat() {
if let [arm] = arms.as_slice() {
if let Some(Pat::WildcardPat(..)) = arm.pat() {
arms.clear();
}
}
@ -73,9 +73,9 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
.filter_map(|variant| build_pat(ctx.db(), module, variant))
.filter(|variant_pat| is_variant_missing(&top_lvl_pats, variant_pat));
let missing_pats: Box<dyn Iterator<Item = _>> = if Some(enum_def)
== FamousDefs(&ctx.sema, Some(module.krate())).core_option_Option().map(lift_enum)
{
let option_enum =
FamousDefs(&ctx.sema, Some(module.krate())).core_option_Option().map(lift_enum);
let missing_pats: Box<dyn Iterator<Item = _>> = if Some(enum_def) == option_enum {
// Match `Some` variant first.
cov_mark::hit!(option_order);
Box::new(missing_pats.rev())
@ -136,7 +136,7 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
.arms()
.find(|arm| matches!(arm.pat(), Some(ast::Pat::WildcardPat(_))));
if let Some(arm) = catch_all_arm {
arm.remove()
arm.remove();
}
let mut first_new_arm = None;
for arm in missing_arms {
@ -214,13 +214,7 @@ impl ExtendedEnum {
fn resolve_enum_def(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<ExtendedEnum> {
sema.type_of_expr(expr)?.autoderef(sema.db).find_map(|ty| match ty.as_adt() {
Some(Adt::Enum(e)) => Some(ExtendedEnum::Enum(e)),
_ => {
if ty.is_bool() {
Some(ExtendedEnum::Bool)
} else {
None
}
}
_ => ty.is_bool().then(|| ExtendedEnum::Bool),
})
}
@ -237,13 +231,7 @@ fn resolve_tuple_of_enum_def(
// For now we only handle expansion for a tuple of enums. Here
// we map non-enum items to None and rely on `collect` to
// convert Vec<Option<hir::Enum>> into Option<Vec<hir::Enum>>.
_ => {
if ty.is_bool() {
Some(ExtendedEnum::Bool)
} else {
None
}
}
_ => ty.is_bool().then(|| ExtendedEnum::Bool),
})
})
.collect()

View File

@ -12,7 +12,7 @@ use either::Either;
use hir::{Crate, Enum, ItemInNs, MacroDef, Module, ModuleDef, Name, ScopeDef, Semantics, Trait};
use syntax::{
ast::{self, make, LoopBodyOwner},
AstNode, SyntaxKind, SyntaxToken, TokenAtOffset, WalkEvent,
AstNode, Direction, SyntaxElement, SyntaxKind, SyntaxToken, TokenAtOffset, WalkEvent, T,
};
use crate::RootDatabase;
@ -25,6 +25,38 @@ pub fn item_name(db: &RootDatabase, item: ItemInNs) -> Option<Name> {
}
}
/// Resolves the path at the cursor token as a derive macro if it inside a token tree of a derive attribute.
pub fn try_resolve_derive_input_at(
sema: &Semantics<RootDatabase>,
derive_attr: &ast::Attr,
cursor: &SyntaxToken,
) -> Option<MacroDef> {
use itertools::Itertools;
if cursor.kind() != T![ident] {
return None;
}
let tt = match derive_attr.as_simple_call() {
Some((name, tt))
if name == "derive" && tt.syntax().text_range().contains_range(cursor.text_range()) =>
{
tt
}
_ => return None,
};
let tokens: Vec<_> = cursor
.siblings_with_tokens(Direction::Prev)
.flat_map(SyntaxElement::into_token)
.take_while(|tok| tok.kind() != T!['('] && tok.kind() != T![,])
.collect();
let path = ast::Path::parse(&tokens.into_iter().rev().join("")).ok()?;
match sema.scope(tt.syntax()).speculative_resolve(&path) {
Some(hir::PathResolution::Macro(makro)) if makro.kind() == hir::MacroKind::Derive => {
Some(makro)
}
_ => None,
}
}
/// Picks the token with the highest rank returned by the passed in function.
pub fn pick_best_token(
tokens: TokenAtOffset<SyntaxToken>,