diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index 4a029072741..ef4fa968007 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs @@ -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(<)) }, - 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,30 +86,41 @@ 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, tt: ast::TokenTree, token: SyntaxToken, file_id: FileId, ) -> Option> { - let path = ast::String::cast(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; + 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 = 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), + name: path.into(), + focus_range: None, + kind: None, + container_name: None, + description: 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]), } - let file_id = db.resolve_path(AnchoredPath { anchor: file_id, path: &path })?; - let size = db.file_text(file_id).len().try_into().ok()?; - Some(vec![NavigationTarget { - file_id, - full_range: TextRange::new(0.into(), size), - name: path.into(), - focus_range: None, - kind: None, - container_name: None, - description: None, - docs: None, - }]) } /// finds the trait definition of an impl'd item @@ -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; + "#, + ); + } } diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index d0201d315db..5a23e05ae88 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs @@ -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) { - return res; + } 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> { - let attr = token.ancestors().find_map(ast::Attr::cast)?; +fn try_hover_for_lint(attr: &ast::Attr, token: &SyntaxToken) -> Option> { 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 + ``` + "#]], + ); + } } diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs index a8e93c39f89..6ace8ce83c6 100644 --- a/crates/ide/src/syntax_highlighting/highlight.rs +++ b/crates/ide/src/syntax_highlighting/highlight.rs @@ -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::(&element) => HlTag::None.into(), + IDENT if parent_matches::(&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::(&element) => HlOperator::Bitwise.into(), T![&] => { diff --git a/crates/ide/src/syntax_highlighting/test_data/highlighting.html b/crates/ide/src/syntax_highlighting/test_data/highlighting.html index 04148767d0c..42416afa251 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlighting.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlighting.html @@ -91,7 +91,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd } } -#[derive(Copy)] +#[derive(Copy)] struct FooCopy { x: u32, } diff --git a/crates/ide_assists/src/handlers/fill_match_arms.rs b/crates/ide_assists/src/handlers/fill_match_arms.rs index c4bd5eaa7b7..2cee442bac9 100644 --- a/crates/ide_assists/src/handlers/fill_match_arms.rs +++ b/crates/ide_assists/src/handlers/fill_match_arms.rs @@ -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 = 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> = 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> = 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, expr: &ast::Expr) -> Option { 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> into Option>. - _ => { - if ty.is_bool() { - Some(ExtendedEnum::Bool) - } else { - None - } - } + _ => ty.is_bool().then(|| ExtendedEnum::Bool), }) }) .collect() diff --git a/crates/ide_db/src/helpers.rs b/crates/ide_db/src/helpers.rs index c71b6d64798..f9da39f833d 100644 --- a/crates/ide_db/src/helpers.rs +++ b/crates/ide_db/src/helpers.rs @@ -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 { } } +/// 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, + derive_attr: &ast::Attr, + cursor: &SyntaxToken, +) -> Option { + 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,