Resolve derive attribute input macro paths in ide layer
This commit is contained in:
parent
2e45e47c83
commit
bfe0fa009e
@ -5,7 +5,7 @@ use hir::{AsAssocItem, InFile, ModuleDef, Semantics};
|
|||||||
use ide_db::{
|
use ide_db::{
|
||||||
base_db::{AnchoredPath, FileId, FileLoader},
|
base_db::{AnchoredPath, FileId, FileLoader},
|
||||||
defs::{Definition, NameClass, NameRefClass},
|
defs::{Definition, NameClass, NameRefClass},
|
||||||
helpers::pick_best_token,
|
helpers::{pick_best_token, try_resolve_derive_input_at},
|
||||||
RootDatabase,
|
RootDatabase,
|
||||||
};
|
};
|
||||||
use syntax::{ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, TextRange, T};
|
use syntax::{ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, TextRange, T};
|
||||||
@ -78,7 +78,7 @@ pub(crate) fn goto_definition(
|
|||||||
} else {
|
} else {
|
||||||
reference_definition(&sema, Either::Left(<))
|
reference_definition(&sema, Either::Left(<))
|
||||||
},
|
},
|
||||||
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,
|
_ => return None,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -86,20 +86,22 @@ pub(crate) fn goto_definition(
|
|||||||
Some(RangeInfo::new(original_token.text_range(), navs))
|
Some(RangeInfo::new(original_token.text_range(), navs))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_lookup_include_path(
|
fn try_lookup_include_path_or_derive(
|
||||||
db: &RootDatabase,
|
sema: &Semantics<RootDatabase>,
|
||||||
tt: ast::TokenTree,
|
tt: ast::TokenTree,
|
||||||
token: SyntaxToken,
|
token: SyntaxToken,
|
||||||
file_id: FileId,
|
file_id: FileId,
|
||||||
) -> Option<Vec<NavigationTarget>> {
|
) -> 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 macro_call = tt.syntax().parent().and_then(ast::MacroCall::cast)?;
|
||||||
let name = macro_call.path()?.segment()?.name_ref()?;
|
let name = macro_call.path()?.segment()?.name_ref()?;
|
||||||
if !matches!(&*name.text(), "include" | "include_str" | "include_bytes") {
|
if !matches!(&*name.text(), "include" | "include_str" | "include_bytes") {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let file_id = db.resolve_path(AnchoredPath { anchor: file_id, path: &path })?;
|
let file_id = sema.db.resolve_path(AnchoredPath { anchor: file_id, path: &path })?;
|
||||||
let size = db.file_text(file_id).len().try_into().ok()?;
|
let size = sema.db.file_text(file_id).len().try_into().ok()?;
|
||||||
Some(vec![NavigationTarget {
|
Some(vec![NavigationTarget {
|
||||||
file_id,
|
file_id,
|
||||||
full_range: TextRange::new(0.into(), size),
|
full_range: TextRange::new(0.into(), size),
|
||||||
@ -111,6 +113,15 @@ fn try_lookup_include_path(
|
|||||||
docs: None,
|
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
|
/// finds the trait definition of an impl'd item
|
||||||
/// e.g.
|
/// 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;
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ use ide_db::{
|
|||||||
defs::{Definition, NameClass, NameRefClass},
|
defs::{Definition, NameClass, NameRefClass},
|
||||||
helpers::{
|
helpers::{
|
||||||
generated_lints::{CLIPPY_LINTS, DEFAULT_LINTS, FEATURES},
|
generated_lints::{CLIPPY_LINTS, DEFAULT_LINTS, FEATURES},
|
||||||
pick_best_token, FamousDefs,
|
pick_best_token, try_resolve_derive_input_at, FamousDefs,
|
||||||
},
|
},
|
||||||
RootDatabase,
|
RootDatabase,
|
||||||
};
|
};
|
||||||
@ -129,8 +129,12 @@ pub(crate) fn hover(
|
|||||||
})?;
|
})?;
|
||||||
range = Some(idl_range);
|
range = Some(idl_range);
|
||||||
resolve_doc_path_for_def(db, def, &link, ns).map(Definition::ModuleDef)
|
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;
|
return res;
|
||||||
|
} else {
|
||||||
|
try_resolve_derive_input_at(&sema, &attr, &token).map(Definition::Macro)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -197,8 +201,7 @@ pub(crate) fn hover(
|
|||||||
Some(RangeInfo::new(range, res))
|
Some(RangeInfo::new(range, res))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_hover_for_attribute(token: &SyntaxToken) -> Option<RangeInfo<HoverResult>> {
|
fn try_hover_for_lint(attr: &ast::Attr, token: &SyntaxToken) -> Option<RangeInfo<HoverResult>> {
|
||||||
let attr = token.ancestors().find_map(ast::Attr::cast)?;
|
|
||||||
let (path, tt) = attr.as_simple_call()?;
|
let (path, tt) = attr.as_simple_call()?;
|
||||||
if !tt.syntax().text_range().contains(token.text_range().start()) {
|
if !tt.syntax().text_range().contains(token.text_range().start()) {
|
||||||
return None;
|
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
|
||||||
|
```
|
||||||
|
"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
use hir::{AsAssocItem, HasVisibility, Semantics};
|
use hir::{AsAssocItem, HasVisibility, Semantics};
|
||||||
use ide_db::{
|
use ide_db::{
|
||||||
defs::{Definition, NameClass, NameRefClass},
|
defs::{Definition, NameClass, NameRefClass},
|
||||||
|
helpers::try_resolve_derive_input_at,
|
||||||
RootDatabase, SymbolKind,
|
RootDatabase, SymbolKind,
|
||||||
};
|
};
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
@ -87,7 +88,18 @@ pub(super) fn element(
|
|||||||
_ => Highlight::from(SymbolKind::LifetimeParam) | HlMod::Definition,
|
_ => 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 {
|
p if p.is_punct() => match p {
|
||||||
T![&] if parent_matches::<ast::BinExpr>(&element) => HlOperator::Bitwise.into(),
|
T![&] if parent_matches::<ast::BinExpr>(&element) => HlOperator::Bitwise.into(),
|
||||||
T![&] => {
|
T![&] => {
|
||||||
|
@ -91,7 +91,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
|
|||||||
<span class="brace">}</span>
|
<span class="brace">}</span>
|
||||||
<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="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="field declaration">x</span><span class="colon">:</span> <span class="builtin_type">u32</span><span class="comma">,</span>
|
||||||
<span class="brace">}</span>
|
<span class="brace">}</span>
|
||||||
|
@ -43,8 +43,8 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
|
|||||||
let expr = match_expr.expr()?;
|
let expr = match_expr.expr()?;
|
||||||
|
|
||||||
let mut arms: Vec<MatchArm> = match_arm_list.arms().collect();
|
let mut arms: Vec<MatchArm> = match_arm_list.arms().collect();
|
||||||
if arms.len() == 1 {
|
if let [arm] = arms.as_slice() {
|
||||||
if let Some(Pat::WildcardPat(..)) = arms[0].pat() {
|
if let Some(Pat::WildcardPat(..)) = arm.pat() {
|
||||||
arms.clear();
|
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_map(|variant| build_pat(ctx.db(), module, variant))
|
||||||
.filter(|variant_pat| is_variant_missing(&top_lvl_pats, variant_pat));
|
.filter(|variant_pat| is_variant_missing(&top_lvl_pats, variant_pat));
|
||||||
|
|
||||||
let missing_pats: Box<dyn Iterator<Item = _>> = if Some(enum_def)
|
let option_enum =
|
||||||
== FamousDefs(&ctx.sema, Some(module.krate())).core_option_Option().map(lift_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.
|
// Match `Some` variant first.
|
||||||
cov_mark::hit!(option_order);
|
cov_mark::hit!(option_order);
|
||||||
Box::new(missing_pats.rev())
|
Box::new(missing_pats.rev())
|
||||||
@ -136,7 +136,7 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
|
|||||||
.arms()
|
.arms()
|
||||||
.find(|arm| matches!(arm.pat(), Some(ast::Pat::WildcardPat(_))));
|
.find(|arm| matches!(arm.pat(), Some(ast::Pat::WildcardPat(_))));
|
||||||
if let Some(arm) = catch_all_arm {
|
if let Some(arm) = catch_all_arm {
|
||||||
arm.remove()
|
arm.remove();
|
||||||
}
|
}
|
||||||
let mut first_new_arm = None;
|
let mut first_new_arm = None;
|
||||||
for arm in missing_arms {
|
for arm in missing_arms {
|
||||||
@ -214,13 +214,7 @@ impl ExtendedEnum {
|
|||||||
fn resolve_enum_def(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<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() {
|
sema.type_of_expr(expr)?.autoderef(sema.db).find_map(|ty| match ty.as_adt() {
|
||||||
Some(Adt::Enum(e)) => Some(ExtendedEnum::Enum(e)),
|
Some(Adt::Enum(e)) => Some(ExtendedEnum::Enum(e)),
|
||||||
_ => {
|
_ => ty.is_bool().then(|| ExtendedEnum::Bool),
|
||||||
if ty.is_bool() {
|
|
||||||
Some(ExtendedEnum::Bool)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -237,13 +231,7 @@ fn resolve_tuple_of_enum_def(
|
|||||||
// For now we only handle expansion for a tuple of enums. Here
|
// For now we only handle expansion for a tuple of enums. Here
|
||||||
// we map non-enum items to None and rely on `collect` to
|
// we map non-enum items to None and rely on `collect` to
|
||||||
// convert Vec<Option<hir::Enum>> into Option<Vec<hir::Enum>>.
|
// convert Vec<Option<hir::Enum>> into Option<Vec<hir::Enum>>.
|
||||||
_ => {
|
_ => ty.is_bool().then(|| ExtendedEnum::Bool),
|
||||||
if ty.is_bool() {
|
|
||||||
Some(ExtendedEnum::Bool)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
@ -12,7 +12,7 @@ use either::Either;
|
|||||||
use hir::{Crate, Enum, ItemInNs, MacroDef, Module, ModuleDef, Name, ScopeDef, Semantics, Trait};
|
use hir::{Crate, Enum, ItemInNs, MacroDef, Module, ModuleDef, Name, ScopeDef, Semantics, Trait};
|
||||||
use syntax::{
|
use syntax::{
|
||||||
ast::{self, make, LoopBodyOwner},
|
ast::{self, make, LoopBodyOwner},
|
||||||
AstNode, SyntaxKind, SyntaxToken, TokenAtOffset, WalkEvent,
|
AstNode, Direction, SyntaxElement, SyntaxKind, SyntaxToken, TokenAtOffset, WalkEvent, T,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::RootDatabase;
|
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.
|
/// Picks the token with the highest rank returned by the passed in function.
|
||||||
pub fn pick_best_token(
|
pub fn pick_best_token(
|
||||||
tokens: TokenAtOffset<SyntaxToken>,
|
tokens: TokenAtOffset<SyntaxToken>,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user