From 3018ffd85e6921ba57d4340f666269ec85e58902 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 28 Oct 2021 16:13:37 +0200 Subject: [PATCH] Refactor ide handling for paths in derive inputs --- crates/ide/src/doc_links.rs | 6 +- crates/ide/src/goto_definition.rs | 2 + crates/ide/src/hover/tests.rs | 3 + crates/ide/src/static_index.rs | 4 +- .../ide/src/syntax_highlighting/highlight.rs | 6 +- .../test_data/highlighting.html | 13 +-- crates/ide/src/syntax_highlighting/tests.rs | 10 +-- .../ide_assists/src/handlers/auto_import.rs | 7 +- crates/ide_db/src/defs.rs | 29 +++---- crates/ide_db/src/helpers.rs | 80 +++++++++++++------ crates/ide_db/src/helpers/famous_defs.rs | 13 ++- crates/ide_db/src/helpers/import_assets.rs | 21 ++++- crates/rust-analyzer/src/to_proto.rs | 2 +- crates/syntax/src/ast/generated/tokens.rs | 21 +++++ crates/syntax/src/tests/sourcegen_ast.rs | 2 +- 15 files changed, 142 insertions(+), 77 deletions(-) diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs index c184b014ba..8c47b809b9 100644 --- a/crates/ide/src/doc_links.rs +++ b/crates/ide/src/doc_links.rs @@ -4,7 +4,7 @@ mod intra_doc_links; use either::Either; use pulldown_cmark::{BrokenLink, CowStr, Event, InlineStr, LinkType, Options, Parser, Tag}; -use pulldown_cmark_to_cmark::{cmark_with_options, Options as CmarkOptions}; +use pulldown_cmark_to_cmark::{cmark_with_options, Options as CMarkOptions}; use stdx::format_to; use url::Url; @@ -65,7 +65,7 @@ pub(crate) fn rewrite_links(db: &RootDatabase, markdown: &str, definition: Defin doc, &mut out, None, - CmarkOptions { code_block_backticks: 3, ..Default::default() }, + CMarkOptions { code_block_backticks: 3, ..Default::default() }, ) .ok(); out @@ -103,7 +103,7 @@ pub(crate) fn remove_links(markdown: &str) -> String { doc, &mut out, None, - CmarkOptions { code_block_backticks: 3, ..Default::default() }, + CMarkOptions { code_block_backticks: 3, ..Default::default() }, ) .ok(); out diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index 1f0b9aaaa1..1edb17739b 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs @@ -1371,6 +1371,7 @@ impl Twait for Stwuct { fn goto_def_derive_input() { check( r#" +//- minicore:derive #[rustc_builtin_macro] pub macro Copy {} // ^^^^ @@ -1380,6 +1381,7 @@ struct Foo; ); check( r#" +//- minicore:derive mod foo { #[rustc_builtin_macro] pub macro Copy {} diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs index b6cec50038..2aa54fc33f 100644 --- a/crates/ide/src/hover/tests.rs +++ b/crates/ide/src/hover/tests.rs @@ -3651,6 +3651,7 @@ use crate as foo$0; fn hover_attribute_in_macro() { check( r#" +//- minicore:derive macro_rules! identity { ($struct:item) => { $struct @@ -3681,6 +3682,7 @@ identity!{ fn hover_derive_input() { check( r#" +//- minicore:derive #[rustc_builtin_macro] pub macro Copy {} #[derive(Copy$0)] @@ -3700,6 +3702,7 @@ struct Foo; ); check( r#" +//- minicore:derive mod foo { #[rustc_builtin_macro] pub macro Copy {} diff --git a/crates/ide/src/static_index.rs b/crates/ide/src/static_index.rs index 5e98ac1756..75249a959d 100644 --- a/crates/ide/src/static_index.rs +++ b/crates/ide/src/static_index.rs @@ -266,12 +266,10 @@ enum E { X(Foo) } fn derives() { check_all_ranges( r#" +//- minicore:derive #[rustc_builtin_macro] pub macro Copy {} //^^^^ -#[rustc_builtin_macro] -pub macro derive {} - //^^^^^^ #[derive(Copy)] //^^^^^^ ^^^^ struct Hello(i32); diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs index 1285c2f846..2bf83da4df 100644 --- a/crates/ide/src/syntax_highlighting/highlight.rs +++ b/crates/ide/src/syntax_highlighting/highlight.rs @@ -3,7 +3,7 @@ use hir::{AsAssocItem, HasVisibility, Semantics}; use ide_db::{ defs::{Definition, NameClass, NameRefClass}, - helpers::{try_resolve_derive_input_at, FamousDefs}, + helpers::{try_resolve_derive_input, FamousDefs}, RootDatabase, SymbolKind, }; use rustc_hash::FxHashMap; @@ -56,8 +56,8 @@ fn token( T![?] => HlTag::Operator(HlOperator::Other) | HlMod::ControlFlow, IDENT if parent_matches::(&token) => { if let Some(attr) = token.ancestors().nth(2).and_then(ast::Attr::cast) { - match try_resolve_derive_input_at(sema, &attr, &token) { - Some(makro) => highlight_def(sema, krate, Definition::Macro(makro)), + match try_resolve_derive_input(sema, &attr, &ast::Ident::cast(token).unwrap()) { + Some(res) => highlight_def(sema, krate, Definition::from(res)), None => HlTag::None.into(), } } else { diff --git a/crates/ide/src/syntax_highlighting/test_data/highlighting.html b/crates/ide/src/syntax_highlighting/test_data/highlighting.html index 05e6739830..1a10a78d24 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlighting.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlighting.html @@ -43,15 +43,6 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
use inner::{self as inner_mod};
 mod inner {}
 
-#[rustc_builtin_macro]
-macro Copy {}
-
-// Needed for function consuming vs normal
-pub mod marker {
-    #[lang = "copy"]
-    pub trait Copy {}
-}
-
 #[proc_macros::identity]
 pub mod ops {
     #[lang = "fn_once"]
@@ -95,7 +86,7 @@ proc_macros::mirror! {
     }
 }
 
-#[derive(Copy)]
+#[derive(Copy)]
 struct FooCopy {
     x: u32,
 }
@@ -135,7 +126,7 @@ proc_macros::mirror! {
     f()
 }
 
-fn foobar() -> impl Copy {}
+fn foobar() -> impl Copy {}
 
 fn foo() {
     let bar = foobar();
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index 850de3908f..42d34040fa 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -11,19 +11,11 @@ fn test_highlighting() {
     check_highlighting(
         r#"
 //- proc_macros: identity, mirror
+//- minicore: derive, copy
 //- /main.rs crate:main deps:foo
 use inner::{self as inner_mod};
 mod inner {}
 
-#[rustc_builtin_macro]
-macro Copy {}
-
-// Needed for function consuming vs normal
-pub mod marker {
-    #[lang = "copy"]
-    pub trait Copy {}
-}
-
 #[proc_macros::identity]
 pub mod ops {
     #[lang = "fn_once"]
diff --git a/crates/ide_assists/src/handlers/auto_import.rs b/crates/ide_assists/src/handlers/auto_import.rs
index 1ec3932a2d..c877748246 100644
--- a/crates/ide_assists/src/handlers/auto_import.rs
+++ b/crates/ide_assists/src/handlers/auto_import.rs
@@ -3,7 +3,7 @@ use ide_db::helpers::{
     insert_use::{insert_use, ImportScope},
     mod_path_to_ast,
 };
-use syntax::{ast, AstNode, SyntaxNode};
+use syntax::{ast, AstNode, AstToken, SyntaxNode};
 
 use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel};
 
@@ -128,9 +128,10 @@ pub(super) fn find_importable_node(ctx: &AssistContext) -> Option<(ImportAssets,
         .find_node_at_offset_with_descend::()
         .filter(ast::IdentPat::is_simple_ident)
     {
-        ImportAssets::for_ident_pat(&pat, &ctx.sema).zip(Some(pat.syntax().clone()))
+        ImportAssets::for_ident_pat(&ctx.sema, &pat).zip(Some(pat.syntax().clone()))
     } else {
-        None
+        let ident = ctx.find_token_at_offset()?;
+        ImportAssets::for_derive_ident(&ctx.sema, &ident).zip(ident.syntax().parent())
     }
 }
 
diff --git a/crates/ide_db/src/defs.rs b/crates/ide_db/src/defs.rs
index effa694aae..5bc8e8764f 100644
--- a/crates/ide_db/src/defs.rs
+++ b/crates/ide_db/src/defs.rs
@@ -12,10 +12,10 @@ use hir::{
 };
 use syntax::{
     ast::{self, AstNode},
-    match_ast, SyntaxKind, SyntaxNode, SyntaxToken,
+    match_ast, AstToken, SyntaxKind, SyntaxNode, SyntaxToken,
 };
 
-use crate::{helpers::try_resolve_derive_input_at, RootDatabase};
+use crate::{helpers::try_resolve_derive_input, RootDatabase};
 
 // FIXME: a more precise name would probably be `Symbol`?
 #[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
@@ -38,19 +38,20 @@ impl Definition {
             Some(parent) => parent,
             None => return Default::default(),
         };
-        let attr = parent
-            .ancestors()
-            .find_map(ast::TokenTree::cast)
-            .and_then(|tt| tt.parent_meta())
-            .and_then(|meta| meta.parent_attr());
-        if let Some(attr) = attr {
-            try_resolve_derive_input_at(&sema, &attr, &token)
-                .map(Definition::Macro)
-                .into_iter()
-                .collect()
-        } else {
-            Self::from_node(sema, &parent)
+        if let Some(ident) = ast::Ident::cast(token.clone()) {
+            let attr = parent
+                .ancestors()
+                .find_map(ast::TokenTree::cast)
+                .and_then(|tt| tt.parent_meta())
+                .and_then(|meta| meta.parent_attr());
+            if let Some(attr) = attr {
+                return try_resolve_derive_input(&sema, &attr, &ident)
+                    .map(Into::into)
+                    .into_iter()
+                    .collect();
+            }
         }
+        Self::from_node(sema, &parent)
     }
 
     pub fn from_node(sema: &Semantics, node: &SyntaxNode) -> ArrayVec {
diff --git a/crates/ide_db/src/helpers.rs b/crates/ide_db/src/helpers.rs
index 173e55b33f..f6a1a55218 100644
--- a/crates/ide_db/src/helpers.rs
+++ b/crates/ide_db/src/helpers.rs
@@ -7,14 +7,16 @@ pub mod merge_imports;
 pub mod node_ext;
 pub mod rust_doc;
 
-use std::collections::VecDeque;
+use std::{collections::VecDeque, iter};
 
 use base_db::FileId;
 use either::Either;
-use hir::{ItemInNs, MacroDef, ModuleDef, Name, Semantics};
+use hir::{ItemInNs, MacroDef, ModuleDef, Name, PathResolution, Semantics};
+use itertools::Itertools;
 use syntax::{
-    ast::{self, make, HasLoopBody},
-    AstNode, Direction, SyntaxElement, SyntaxKind, SyntaxToken, TokenAtOffset, WalkEvent, T,
+    ast::{self, make, HasLoopBody, Ident},
+    AstNode, AstToken, Direction, SyntaxElement, SyntaxKind, SyntaxToken, TokenAtOffset, WalkEvent,
+    T,
 };
 
 use crate::RootDatabase;
@@ -29,33 +31,59 @@ 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(
+/// Parses and returns the derive path at the cursor position in the given attribute, if it is a derive.
+/// This special case is required because the derive macro is a compiler builtin that discards the input derives.
+///
+/// The returned path is synthesized from TokenTree tokens and as such cannot be used with the [`Semantics`].
+pub fn get_path_in_derive_attr(
     sema: &hir::Semantics,
-    derive_attr: &ast::Attr,
-    cursor: &SyntaxToken,
-) -> Option {
-    use itertools::Itertools;
-    if cursor.kind() != T![ident] {
+    attr: &ast::Attr,
+    cursor: &Ident,
+) -> Option {
+    let cursor = cursor.syntax();
+    let path = attr.path()?;
+    let tt = attr.token_tree()?;
+    if !tt.syntax().text_range().contains_range(cursor.text_range()) {
         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
+    let scope = sema.scope(attr.syntax());
+    let resolved_attr = sema.resolve_path(&path)?;
+    let derive = FamousDefs(sema, scope.krate()).core_macros_builtin_derive()?;
+    if PathResolution::Macro(derive) != resolved_attr {
+        return None;
+    }
+
+    let first = cursor
         .siblings_with_tokens(Direction::Prev)
-        .flat_map(SyntaxElement::into_token)
+        .filter_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()?;
-    sema.scope(tt.syntax())
-        .speculative_resolve_as_mac(&path)
-        .filter(|mac| mac.kind() == hir::MacroKind::Derive)
+        .last()?;
+    let path_tokens = first
+        .siblings_with_tokens(Direction::Next)
+        .filter_map(SyntaxElement::into_token)
+        .take_while(|tok| tok != cursor);
+
+    ast::Path::parse(&path_tokens.chain(iter::once(cursor.clone())).join("")).ok()
+}
+
+/// Parses and resolves the path at the cursor position in the given attribute, if it is a derive.
+/// This special case is required because the derive macro is a compiler builtin that discards the input derives.
+pub fn try_resolve_derive_input(
+    sema: &hir::Semantics,
+    attr: &ast::Attr,
+    cursor: &Ident,
+) -> Option {
+    let path = get_path_in_derive_attr(sema, attr, cursor)?;
+    let scope = sema.scope(attr.syntax());
+    // FIXME: This double resolve shouldn't be necessary
+    // It's only here so we prefer macros over other namespaces
+    match scope.speculative_resolve_as_mac(&path) {
+        Some(mac) if mac.kind() == hir::MacroKind::Derive => Some(PathResolution::Macro(mac)),
+        Some(_) => return None,
+        None => scope
+            .speculative_resolve(&path)
+            .filter(|res| matches!(res, PathResolution::Def(ModuleDef::Module(_)))),
+    }
 }
 
 /// Picks the token with the highest rank returned by the passed in function.
diff --git a/crates/ide_db/src/helpers/famous_defs.rs b/crates/ide_db/src/helpers/famous_defs.rs
index b5e3907cfa..e8993d327f 100644
--- a/crates/ide_db/src/helpers/famous_defs.rs
+++ b/crates/ide_db/src/helpers/famous_defs.rs
@@ -1,5 +1,5 @@
 //! See [`FamousDefs`].
-use hir::{Crate, Enum, Module, ScopeDef, Semantics, Trait};
+use hir::{Crate, Enum, MacroDef, Module, ScopeDef, Semantics, Trait};
 
 use crate::RootDatabase;
 
@@ -80,6 +80,10 @@ impl FamousDefs<'_, '_> {
         self.find_trait("core:marker:Copy")
     }
 
+    pub fn core_macros_builtin_derive(&self) -> Option {
+        self.find_macro("core:macros:builtin:derive")
+    }
+
     pub fn alloc(&self) -> Option {
         self.find_crate("alloc")
     }
@@ -110,6 +114,13 @@ impl FamousDefs<'_, '_> {
         }
     }
 
+    fn find_macro(&self, path: &str) -> Option {
+        match self.find_def(path)? {
+            hir::ScopeDef::MacroDef(it) => Some(it),
+            _ => None,
+        }
+    }
+
     fn find_enum(&self, path: &str) -> Option {
         match self.find_def(path)? {
             hir::ScopeDef::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Enum(it))) => Some(it),
diff --git a/crates/ide_db/src/helpers/import_assets.rs b/crates/ide_db/src/helpers/import_assets.rs
index f1e8ee4935..0b3ecd095b 100644
--- a/crates/ide_db/src/helpers/import_assets.rs
+++ b/crates/ide_db/src/helpers/import_assets.rs
@@ -8,10 +8,11 @@ use rustc_hash::FxHashSet;
 use syntax::{
     ast::{self, HasName},
     utils::path_to_string_stripping_turbo_fish,
-    AstNode, SyntaxNode,
+    AstNode, AstToken, SyntaxNode,
 };
 
 use crate::{
+    helpers::get_path_in_derive_attr,
     items_locator::{self, AssocItemSearch, DEFAULT_QUERY_SEARCH_LIMIT},
     RootDatabase,
 };
@@ -119,7 +120,7 @@ impl ImportAssets {
         })
     }
 
-    pub fn for_ident_pat(pat: &ast::IdentPat, sema: &Semantics) -> Option {
+    pub fn for_ident_pat(sema: &Semantics, pat: &ast::IdentPat) -> Option {
         if !pat.is_simple_ident() {
             return None;
         }
@@ -132,6 +133,22 @@ impl ImportAssets {
         })
     }
 
+    pub fn for_derive_ident(sema: &Semantics, ident: &ast::Ident) -> Option {
+        let attr = ident.syntax().ancestors().find_map(ast::Attr::cast)?;
+        let path = get_path_in_derive_attr(sema, &attr, ident)?;
+
+        if let Some(_) = path.qualifier() {
+            return None;
+        }
+        let name = NameToImport::Exact(path.segment()?.name_ref()?.to_string());
+        let candidate_node = attr.syntax().clone();
+        Some(Self {
+            import_candidate: ImportCandidate::Path(PathImportCandidate { qualifier: None, name }),
+            module_with_candidate: sema.scope(&candidate_node).module()?,
+            candidate_node,
+        })
+    }
+
     pub fn for_fuzzy_path(
         module_with_candidate: Module,
         qualifier: Option,
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index 473b7a870f..ea39f799b7 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -673,7 +673,7 @@ pub(crate) fn location(
     Ok(loc)
 }
 
-/// Perefer using `location_link`, if the client has the cap.
+/// Prefer using `location_link`, if the client has the cap.
 pub(crate) fn location_from_nav(
     snap: &GlobalStateSnapshot,
     nav: NavigationTarget,
diff --git a/crates/syntax/src/ast/generated/tokens.rs b/crates/syntax/src/ast/generated/tokens.rs
index 83fd5ce899..30f23b9d96 100644
--- a/crates/syntax/src/ast/generated/tokens.rs
+++ b/crates/syntax/src/ast/generated/tokens.rs
@@ -131,3 +131,24 @@ impl AstToken for FloatNumber {
     }
     fn syntax(&self) -> &SyntaxToken { &self.syntax }
 }
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct Ident {
+    pub(crate) syntax: SyntaxToken,
+}
+impl std::fmt::Display for Ident {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        std::fmt::Display::fmt(&self.syntax, f)
+    }
+}
+impl AstToken for Ident {
+    fn can_cast(kind: SyntaxKind) -> bool { kind == IDENT }
+    fn cast(syntax: SyntaxToken) -> Option {
+        if Self::can_cast(syntax.kind()) {
+            Some(Self { syntax })
+        } else {
+            None
+        }
+    }
+    fn syntax(&self) -> &SyntaxToken { &self.syntax }
+}
diff --git a/crates/syntax/src/tests/sourcegen_ast.rs b/crates/syntax/src/tests/sourcegen_ast.rs
index a1a96581b4..36fedd2f0b 100644
--- a/crates/syntax/src/tests/sourcegen_ast.rs
+++ b/crates/syntax/src/tests/sourcegen_ast.rs
@@ -560,7 +560,7 @@ impl Field {
 
 fn lower(grammar: &Grammar) -> AstSrc {
     let mut res = AstSrc {
-        tokens: "Whitespace Comment String ByteString IntNumber FloatNumber"
+        tokens: "Whitespace Comment String ByteString IntNumber FloatNumber Ident"
             .split_ascii_whitespace()
             .map(|it| it.to_string())
             .collect::>(),