about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/tools/rust-analyzer/crates/hir/src/semantics.rs221
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs86
2 files changed, 209 insertions, 98 deletions
diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs
index 53242611f81..43de2a6ee7d 100644
--- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs
+++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs
@@ -380,6 +380,27 @@ impl<'db> SemanticsImpl<'db> {
         self.with_ctx(|ctx| ctx.has_derives(adt))
     }
 
+    pub fn derive_helper(&self, attr: &ast::Attr) -> Option<Vec<(Macro, MacroFileId)>> {
+        let adt = attr.syntax().ancestors().find_map(ast::Item::cast).and_then(|it| match it {
+            ast::Item::Struct(it) => Some(ast::Adt::Struct(it)),
+            ast::Item::Enum(it) => Some(ast::Adt::Enum(it)),
+            ast::Item::Union(it) => Some(ast::Adt::Union(it)),
+            _ => None,
+        })?;
+        let attr_name = attr.path().and_then(|it| it.as_single_name_ref())?.as_name();
+        let sa = self.analyze_no_infer(adt.syntax())?;
+        let id = self.db.ast_id_map(sa.file_id).ast_id(&adt);
+        let res: Vec<_> = sa
+            .resolver
+            .def_map()
+            .derive_helpers_in_scope(InFile::new(sa.file_id, id))?
+            .iter()
+            .filter(|&(name, _, _)| *name == attr_name)
+            .map(|&(_, macro_, call)| (macro_.into(), call.as_macro_file()))
+            .collect();
+        res.is_empty().not().then_some(res)
+    }
+
     pub fn is_attr_macro_call(&self, item: &ast::Item) -> bool {
         let file_id = self.find_file(item.syntax()).file_id;
         let src = InFile::new(file_id, item.clone());
@@ -409,6 +430,20 @@ impl<'db> SemanticsImpl<'db> {
         )
     }
 
+    pub fn speculative_expand_raw(
+        &self,
+        macro_file: MacroFileId,
+        speculative_args: &SyntaxNode,
+        token_to_map: SyntaxToken,
+    ) -> Option<(SyntaxNode, SyntaxToken)> {
+        hir_expand::db::expand_speculative(
+            self.db.upcast(),
+            macro_file.macro_call_id,
+            speculative_args,
+            token_to_map,
+        )
+    }
+
     /// Expand the macro call with a different item as the input, mapping the `token_to_map` down into the
     /// expansion. `token_to_map` should be a token from the `speculative args` node.
     pub fn speculative_expand_attr_macro(
@@ -826,99 +861,109 @@ impl<'db> SemanticsImpl<'db> {
 
                     // Then check for token trees, that means we are either in a function-like macro or
                     // secondary attribute inputs
-                    let tt = token.parent_ancestors().map_while(ast::TokenTree::cast).last()?;
-                    let parent = tt.syntax().parent()?;
-
-                    if tt.left_delimiter_token().map_or(false, |it| it == token) {
-                        return None;
-                    }
-                    if tt.right_delimiter_token().map_or(false, |it| it == token) {
-                        return None;
-                    }
-
-                    if let Some(macro_call) = ast::MacroCall::cast(parent.clone()) {
-                        let mcall: hir_expand::files::InFileWrapper<HirFileId, ast::MacroCall> =
-                            InFile::new(file_id, macro_call);
-                        let file_id = match mcache.get(&mcall) {
-                            Some(&it) => it,
-                            None => {
-                                let it = sa.expand(self.db, mcall.as_ref())?;
-                                mcache.insert(mcall, it);
-                                it
+                    let tt = token
+                        .parent_ancestors()
+                        .map_while(Either::<ast::TokenTree, ast::Meta>::cast)
+                        .last()?;
+                    match tt {
+                        Either::Left(tt) => {
+                            if tt.left_delimiter_token().map_or(false, |it| it == token) {
+                                return None;
                             }
-                        };
-                        let text_range = tt.syntax().text_range();
-                        // remove any other token in this macro input, all their mappings are the
-                        // same as this one
-                        tokens.retain(|t| !text_range.contains_range(t.text_range()));
-
-                        process_expansion_for_token(&mut stack, file_id).or(file_id
-                            .eager_arg(self.db.upcast())
-                            .and_then(|arg| {
-                                // also descend into eager expansions
-                                process_expansion_for_token(&mut stack, arg.as_macro_file())
-                            }))
-                    } else if let Some(meta) = ast::Meta::cast(parent) {
-                        // attribute we failed expansion for earlier, this might be a derive invocation
-                        // or derive helper attribute
-                        let attr = meta.parent_attr()?;
-                        let adt = if let Some(adt) = attr.syntax().parent().and_then(ast::Adt::cast)
-                        {
-                            // this might be a derive on an ADT
-                            let derive_call = self.with_ctx(|ctx| {
-                                // so try downmapping the token into the pseudo derive expansion
-                                // see [hir_expand::builtin_attr_macro] for how the pseudo derive expansion works
-                                ctx.attr_to_derive_macro_call(
-                                    InFile::new(file_id, &adt),
-                                    InFile::new(file_id, attr.clone()),
-                                )
-                                .map(|(_, call_id, _)| call_id)
-                            });
-
-                            match derive_call {
-                                Some(call_id) => {
-                                    // resolved to a derive
-                                    let file_id = call_id.as_macro_file();
-                                    let text_range = attr.syntax().text_range();
-                                    // remove any other token in this macro input, all their mappings are the
-                                    // same as this
-                                    tokens.retain(|t| !text_range.contains_range(t.text_range()));
-                                    return process_expansion_for_token(&mut stack, file_id);
-                                }
-                                None => Some(adt),
+                            if tt.right_delimiter_token().map_or(false, |it| it == token) {
+                                return None;
                             }
-                        } else {
-                            // Otherwise this could be a derive helper on a variant or field
-                            attr.syntax().ancestors().find_map(ast::Item::cast).and_then(|it| {
-                                match it {
-                                    ast::Item::Struct(it) => Some(ast::Adt::Struct(it)),
-                                    ast::Item::Enum(it) => Some(ast::Adt::Enum(it)),
-                                    ast::Item::Union(it) => Some(ast::Adt::Union(it)),
-                                    _ => None,
+                            let macro_call = tt.syntax().parent().and_then(ast::MacroCall::cast)?;
+                            let mcall: hir_expand::files::InFileWrapper<HirFileId, ast::MacroCall> =
+                                InFile::new(file_id, macro_call);
+                            let file_id = match mcache.get(&mcall) {
+                                Some(&it) => it,
+                                None => {
+                                    let it = sa.expand(self.db, mcall.as_ref())?;
+                                    mcache.insert(mcall, it);
+                                    it
                                 }
-                            })
-                        }?;
-                        if !self.with_ctx(|ctx| ctx.has_derives(InFile::new(file_id, &adt))) {
-                            return None;
+                            };
+                            let text_range = tt.syntax().text_range();
+                            // remove any other token in this macro input, all their mappings are the
+                            // same as this one
+                            tokens.retain(|t| !text_range.contains_range(t.text_range()));
+
+                            process_expansion_for_token(&mut stack, file_id).or(file_id
+                                .eager_arg(self.db.upcast())
+                                .and_then(|arg| {
+                                    // also descend into eager expansions
+                                    process_expansion_for_token(&mut stack, arg.as_macro_file())
+                                }))
                         }
-                        // Not an attribute, nor a derive, so it's either a builtin or a derive helper
-                        // Try to resolve to a derive helper and downmap
-                        let attr_name =
-                            attr.path().and_then(|it| it.as_single_name_ref())?.as_name();
-                        let id = self.db.ast_id_map(file_id).ast_id(&adt);
-                        let helpers = def_map.derive_helpers_in_scope(InFile::new(file_id, id))?;
-                        let mut res = None;
-                        for (.., derive) in
-                            helpers.iter().filter(|(helper, ..)| *helper == attr_name)
-                        {
-                            res = res.or(process_expansion_for_token(
-                                &mut stack,
-                                derive.as_macro_file(),
-                            ));
+                        Either::Right(meta) => {
+                            // attribute we failed expansion for earlier, this might be a derive invocation
+                            // or derive helper attribute
+                            let attr = meta.parent_attr()?;
+                            let adt = match attr.syntax().parent().and_then(ast::Adt::cast) {
+                                Some(adt) => {
+                                    // this might be a derive on an ADT
+                                    let derive_call = self.with_ctx(|ctx| {
+                                        // so try downmapping the token into the pseudo derive expansion
+                                        // see [hir_expand::builtin_attr_macro] for how the pseudo derive expansion works
+                                        ctx.attr_to_derive_macro_call(
+                                            InFile::new(file_id, &adt),
+                                            InFile::new(file_id, attr.clone()),
+                                        )
+                                        .map(|(_, call_id, _)| call_id)
+                                    });
+
+                                    match derive_call {
+                                        Some(call_id) => {
+                                            // resolved to a derive
+                                            let file_id = call_id.as_macro_file();
+                                            let text_range = attr.syntax().text_range();
+                                            // remove any other token in this macro input, all their mappings are the
+                                            // same as this
+                                            tokens.retain(|t| {
+                                                !text_range.contains_range(t.text_range())
+                                            });
+                                            return process_expansion_for_token(
+                                                &mut stack, file_id,
+                                            );
+                                        }
+                                        None => Some(adt),
+                                    }
+                                }
+                                None => {
+                                    // Otherwise this could be a derive helper on a variant or field
+                                    attr.syntax().ancestors().find_map(ast::Item::cast).and_then(
+                                        |it| match it {
+                                            ast::Item::Struct(it) => Some(ast::Adt::Struct(it)),
+                                            ast::Item::Enum(it) => Some(ast::Adt::Enum(it)),
+                                            ast::Item::Union(it) => Some(ast::Adt::Union(it)),
+                                            _ => None,
+                                        },
+                                    )
+                                }
+                            }?;
+                            if !self.with_ctx(|ctx| ctx.has_derives(InFile::new(file_id, &adt))) {
+                                return None;
+                            }
+                            let attr_name =
+                                attr.path().and_then(|it| it.as_single_name_ref())?.as_name();
+                            // Not an attribute, nor a derive, so it's either a builtin or a derive helper
+                            // Try to resolve to a derive helper and downmap
+                            let id = self.db.ast_id_map(file_id).ast_id(&adt);
+                            let helpers =
+                                def_map.derive_helpers_in_scope(InFile::new(file_id, id))?;
+
+                            let mut res = None;
+                            for (.., derive) in
+                                helpers.iter().filter(|(helper, ..)| *helper == attr_name)
+                            {
+                                res = res.or(process_expansion_for_token(
+                                    &mut stack,
+                                    derive.as_macro_file(),
+                                ));
+                            }
+                            res
                         }
-                        res
-                    } else {
-                        None
                     }
                 })()
                 .is_none();
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs
index 79c503e0a10..f0c6e7a63b0 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs
@@ -3,8 +3,9 @@ use std::iter;
 
 use hir::{Semantics, Type, TypeInfo, Variant};
 use ide_db::{active_parameter::ActiveParameter, RootDatabase};
+use itertools::Either;
 use syntax::{
-    algo::{find_node_at_offset, non_trivia_sibling},
+    algo::{ancestors_at_offset, find_node_at_offset, non_trivia_sibling},
     ast::{self, AttrKind, HasArgList, HasGenericParams, HasLoopBody, HasName, NameOrNameRef},
     match_ast, AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode,
     SyntaxToken, TextRange, TextSize, T,
@@ -119,20 +120,45 @@ fn expand(
         }
 
         // No attributes have been expanded, so look for macro_call! token trees or derive token trees
-        let orig_tt = match find_node_at_offset::<ast::TokenTree>(&original_file, offset) {
+        let orig_tt = match ancestors_at_offset(&original_file, offset)
+            .map_while(Either::<ast::TokenTree, ast::Meta>::cast)
+            .last()
+        {
             Some(it) => it,
             None => break 'expansion,
         };
-        let spec_tt = match find_node_at_offset::<ast::TokenTree>(&speculative_file, offset) {
+        let spec_tt = match ancestors_at_offset(&speculative_file, offset)
+            .map_while(Either::<ast::TokenTree, ast::Meta>::cast)
+            .last()
+        {
             Some(it) => it,
             None => break 'expansion,
         };
 
-        // Expand pseudo-derive expansion
-        if let (Some(orig_attr), Some(spec_attr)) = (
-            orig_tt.syntax().parent().and_then(ast::Meta::cast).and_then(|it| it.parent_attr()),
-            spec_tt.syntax().parent().and_then(ast::Meta::cast).and_then(|it| it.parent_attr()),
-        ) {
+        let (tts, attrs) = match (orig_tt, spec_tt) {
+            (Either::Left(orig_tt), Either::Left(spec_tt)) => {
+                let attrs = orig_tt
+                    .syntax()
+                    .parent()
+                    .and_then(ast::Meta::cast)
+                    .and_then(|it| it.parent_attr())
+                    .zip(
+                        spec_tt
+                            .syntax()
+                            .parent()
+                            .and_then(ast::Meta::cast)
+                            .and_then(|it| it.parent_attr()),
+                    );
+                (Some((orig_tt, spec_tt)), attrs)
+            }
+            (Either::Right(orig_path), Either::Right(spec_path)) => {
+                (None, orig_path.parent_attr().zip(spec_path.parent_attr()))
+            }
+            _ => break 'expansion,
+        };
+
+        // Expand pseudo-derive expansion aka `derive(Debug$0)`
+        if let Some((orig_attr, spec_attr)) = attrs {
             if let (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) = (
                 sema.expand_derive_as_pseudo_attr_macro(&orig_attr),
                 sema.speculative_expand_derive_as_pseudo_attr_macro(
@@ -147,15 +173,54 @@ fn expand(
                     fake_mapped_token.text_range().start(),
                     orig_attr,
                 ));
+                break 'expansion;
+            }
+
+            if let Some(spec_adt) =
+                spec_attr.syntax().ancestors().find_map(ast::Item::cast).and_then(|it| match it {
+                    ast::Item::Struct(it) => Some(ast::Adt::Struct(it)),
+                    ast::Item::Enum(it) => Some(ast::Adt::Enum(it)),
+                    ast::Item::Union(it) => Some(ast::Adt::Union(it)),
+                    _ => None,
+                })
+            {
+                // might be the path of derive helper or a token tree inside of one
+                if let Some(helpers) = sema.derive_helper(&orig_attr) {
+                    for (_mac, file) in helpers {
+                        if let Some((fake_expansion, fake_mapped_token)) = sema
+                            .speculative_expand_raw(
+                                file,
+                                spec_adt.syntax(),
+                                fake_ident_token.clone(),
+                            )
+                        {
+                            // we are inside a derive helper token tree, treat this as being inside
+                            // the derive expansion
+                            let actual_expansion = sema.parse_or_expand(file.into());
+                            let new_offset = fake_mapped_token.text_range().start();
+                            if new_offset + relative_offset > actual_expansion.text_range().end() {
+                                // offset outside of bounds from the original expansion,
+                                // stop here to prevent problems from happening
+                                break 'expansion;
+                            }
+                            original_file = actual_expansion;
+                            speculative_file = fake_expansion;
+                            fake_ident_token = fake_mapped_token;
+                            offset = new_offset;
+                            continue 'expansion;
+                        }
+                    }
+                }
             }
             // at this point we won't have any more successful expansions, so stop
             break 'expansion;
         }
 
         // Expand fn-like macro calls
+        let Some((orig_tt, spec_tt)) = tts else { break 'expansion };
         if let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = (
-            orig_tt.syntax().ancestors().find_map(ast::MacroCall::cast),
-            spec_tt.syntax().ancestors().find_map(ast::MacroCall::cast),
+            orig_tt.syntax().parent().and_then(ast::MacroCall::cast),
+            spec_tt.syntax().parent().and_then(ast::MacroCall::cast),
         ) {
             let mac_call_path0 = actual_macro_call.path().as_ref().map(|s| s.syntax().text());
             let mac_call_path1 =
@@ -201,6 +266,7 @@ fn expand(
         // none of our states have changed so stop the loop
         break 'expansion;
     }
+
     ExpansionResult { original_file, speculative_file, offset, fake_ident_token, derive_ctx }
 }