about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/tools/rust-analyzer/Cargo.toml2
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/dyn_map.rs10
-rw-r--r--src/tools/rust-analyzer/crates/hir/src/semantics.rs298
-rw-r--r--src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs18
4 files changed, 211 insertions, 117 deletions
diff --git a/src/tools/rust-analyzer/Cargo.toml b/src/tools/rust-analyzer/Cargo.toml
index 8c507189846..975fe277b22 100644
--- a/src/tools/rust-analyzer/Cargo.toml
+++ b/src/tools/rust-analyzer/Cargo.toml
@@ -21,6 +21,8 @@ smol_str.opt-level = 3
 text-size.opt-level = 3
 serde.opt-level = 3
 salsa.opt-level = 3
+dissimilar.opt-level = 3
+
 # This speeds up `cargo xtask dist`.
 miniz_oxide.opt-level = 3
 
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/dyn_map.rs b/src/tools/rust-analyzer/crates/hir-def/src/dyn_map.rs
index eed1490a7af..20018b61e5c 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/dyn_map.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/dyn_map.rs
@@ -67,8 +67,14 @@ pub mod keys {
     pub const PROC_MACRO: Key<ast::Fn, ProcMacroId> = Key::new();
     pub const MACRO_CALL: Key<ast::MacroCall, MacroCallId> = Key::new();
     pub const ATTR_MACRO_CALL: Key<ast::Item, MacroCallId> = Key::new();
-    pub const DERIVE_MACRO_CALL: Key<ast::Attr, (AttrId, MacroCallId, Box<[Option<MacroCallId>]>)> =
-        Key::new();
+    pub const DERIVE_MACRO_CALL: Key<
+        ast::Attr,
+        (
+            AttrId,
+            /* derive() */ MacroCallId,
+            /* actual derive macros */ Box<[Option<MacroCallId>]>,
+        ),
+    > = Key::new();
 
     /// XXX: AST Nodes and SyntaxNodes have identity equality semantics: nodes are
     /// equal if they point to exactly the same object.
diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs
index a75c56aa8fc..587e1013b9e 100644
--- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs
+++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs
@@ -550,7 +550,7 @@ impl<'db> SemanticsImpl<'db> {
     }
 
     pub fn is_derive_annotated(&self, adt: InFile<&ast::Adt>) -> bool {
-        self.with_ctx(|ctx| ctx.has_derives(adt))
+        self.with_ctx(|ctx| ctx.file_of_adt_has_derives(adt))
     }
 
     pub fn derive_helpers_in_scope(&self, adt: &ast::Adt) -> Option<Vec<(Symbol, Symbol)>> {
@@ -894,6 +894,7 @@ impl<'db> SemanticsImpl<'db> {
             // node is just the token, so descend the token
             self.descend_into_macros_impl(
                 InFile::new(file.file_id, first),
+                false,
                 &mut |InFile { value, .. }, _ctx| {
                     if let Some(node) = value
                         .parent_ancestors()
@@ -908,14 +909,19 @@ impl<'db> SemanticsImpl<'db> {
         } else {
             // Descend first and last token, then zip them to look for the node they belong to
             let mut scratch: SmallVec<[_; 1]> = smallvec![];
-            self.descend_into_macros_impl(InFile::new(file.file_id, first), &mut |token, _ctx| {
-                scratch.push(token);
-                CONTINUE_NO_BREAKS
-            });
+            self.descend_into_macros_impl(
+                InFile::new(file.file_id, first),
+                false,
+                &mut |token, _ctx| {
+                    scratch.push(token);
+                    CONTINUE_NO_BREAKS
+                },
+            );
 
             let mut scratch = scratch.into_iter();
             self.descend_into_macros_impl(
                 InFile::new(file.file_id, last),
+                false,
                 &mut |InFile { value: last, file_id: last_fid }, _ctx| {
                     if let Some(InFile { value: first, file_id: first_fid }) = scratch.next() {
                         if first_fid == last_fid {
@@ -967,7 +973,7 @@ impl<'db> SemanticsImpl<'db> {
                     ast::Item::Union(it) => it.into(),
                     _ => return false,
                 };
-                ctx.has_derives(token.with_value(&adt))
+                ctx.file_of_adt_has_derives(token.with_value(&adt))
             })
         })
     }
@@ -977,7 +983,7 @@ impl<'db> SemanticsImpl<'db> {
         token: SyntaxToken,
         mut cb: impl FnMut(InFile<SyntaxToken>, SyntaxContext),
     ) {
-        self.descend_into_macros_impl(self.wrap_token_infile(token), &mut |t, ctx| {
+        self.descend_into_macros_impl(self.wrap_token_infile(token), false, &mut |t, ctx| {
             cb(t, ctx);
             CONTINUE_NO_BREAKS
         });
@@ -985,10 +991,14 @@ impl<'db> SemanticsImpl<'db> {
 
     pub fn descend_into_macros(&self, token: SyntaxToken) -> SmallVec<[SyntaxToken; 1]> {
         let mut res = smallvec![];
-        self.descend_into_macros_impl(self.wrap_token_infile(token.clone()), &mut |t, _ctx| {
-            res.push(t.value);
-            CONTINUE_NO_BREAKS
-        });
+        self.descend_into_macros_impl(
+            self.wrap_token_infile(token.clone()),
+            false,
+            &mut |t, _ctx| {
+                res.push(t.value);
+                CONTINUE_NO_BREAKS
+            },
+        );
         if res.is_empty() {
             res.push(token);
         }
@@ -1001,7 +1011,7 @@ impl<'db> SemanticsImpl<'db> {
     ) -> SmallVec<[InFile<SyntaxToken>; 1]> {
         let mut res = smallvec![];
         let token = self.wrap_token_infile(token);
-        self.descend_into_macros_impl(token.clone(), &mut |t, ctx| {
+        self.descend_into_macros_impl(token.clone(), true, &mut |t, ctx| {
             if !ctx.is_opaque(self.db) {
                 // Don't descend into opaque contexts
                 res.push(t);
@@ -1019,7 +1029,7 @@ impl<'db> SemanticsImpl<'db> {
         token: InFile<SyntaxToken>,
         mut cb: impl FnMut(InFile<SyntaxToken>, SyntaxContext) -> ControlFlow<T>,
     ) -> Option<T> {
-        self.descend_into_macros_impl(token, &mut cb)
+        self.descend_into_macros_impl(token, false, &mut cb)
     }
 
     /// Descends the token into expansions, returning the tokens that matches the input
@@ -1092,41 +1102,41 @@ impl<'db> SemanticsImpl<'db> {
     fn descend_into_macros_impl<T>(
         &self,
         InFile { value: token, file_id }: InFile<SyntaxToken>,
+        always_descend_into_derives: bool,
         f: &mut dyn FnMut(InFile<SyntaxToken>, SyntaxContext) -> ControlFlow<T>,
     ) -> Option<T> {
         let _p = tracing::info_span!("descend_into_macros_impl").entered();
 
-        let span = self.db.span_map(file_id).span_for_range(token.text_range());
+        let db = self.db;
+        let span = db.span_map(file_id).span_for_range(token.text_range());
 
         // Process the expansion of a call, pushing all tokens with our span in the expansion back onto our stack
-        let process_expansion_for_token = |stack: &mut Vec<_>, macro_file| {
-            let InMacroFile { file_id, value: mapped_tokens } = self.with_ctx(|ctx| {
-                Some(
-                    ctx.cache
-                        .get_or_insert_expansion(ctx.db, macro_file)
-                        .map_range_down(span)?
-                        .map(SmallVec::<[_; 2]>::from_iter),
-                )
-            })?;
-            // we have found a mapping for the token if the vec is non-empty
-            let res = mapped_tokens.is_empty().not().then_some(());
-            // requeue the tokens we got from mapping our current token down
-            stack.push((HirFileId::from(file_id), mapped_tokens));
-            res
-        };
+        let process_expansion_for_token =
+            |ctx: &mut SourceToDefCtx<'_, '_>, stack: &mut Vec<_>, macro_file| {
+                let InMacroFile { file_id, value: mapped_tokens } = ctx
+                    .cache
+                    .get_or_insert_expansion(ctx.db, macro_file)
+                    .map_range_down(span)?
+                    .map(SmallVec::<[_; 2]>::from_iter);
+                // we have found a mapping for the token if the vec is non-empty
+                let res = mapped_tokens.is_empty().not().then_some(());
+                // requeue the tokens we got from mapping our current token down
+                stack.push((HirFileId::from(file_id), mapped_tokens));
+                res
+            };
 
         // A stack of tokens to process, along with the file they came from
         // These are tracked to know which macro calls we still have to look into
         // the tokens themselves aren't that interesting as the span that is being used to map
         // things down never changes.
         let mut stack: Vec<(_, SmallVec<[_; 2]>)> = vec![];
-        let include = file_id.file_id().and_then(|file_id| {
-            self.s2d_cache.borrow_mut().get_or_insert_include_for(self.db, file_id)
-        });
+        let include = file_id
+            .file_id()
+            .and_then(|file_id| self.s2d_cache.borrow_mut().get_or_insert_include_for(db, file_id));
         match include {
             Some(include) => {
                 // include! inputs are always from real files, so they only need to be handled once upfront
-                process_expansion_for_token(&mut stack, include)?;
+                self.with_ctx(|ctx| process_expansion_for_token(ctx, &mut stack, include))?;
             }
             None => {
                 stack.push((file_id, smallvec![(token, span.ctx)]));
@@ -1148,62 +1158,120 @@ impl<'db> SemanticsImpl<'db> {
             tokens.reverse();
             while let Some((token, ctx)) = tokens.pop() {
                 let was_not_remapped = (|| {
-                    // First expand into attribute invocations
-                    let containing_attribute_macro_call = self.with_ctx(|ctx| {
-                        token.parent_ancestors().filter_map(ast::Item::cast).find_map(|item| {
-                            // Don't force populate the dyn cache for items that don't have an attribute anyways
-                            item.attrs().next()?;
-                            Some((ctx.item_to_macro_call(InFile::new(expansion, &item))?, item))
-                        })
-                    });
-                    if let Some((call_id, item)) = containing_attribute_macro_call {
-                        let attr_id = match self.db.lookup_intern_macro_call(call_id).kind {
-                            hir_expand::MacroCallKind::Attr { invoc_attr_index, .. } => {
-                                invoc_attr_index.ast_index()
-                            }
-                            _ => 0,
-                        };
-                        // FIXME: here, the attribute's text range is used to strip away all
-                        // entries from the start of the attribute "list" up the invoking
-                        // attribute. But in
-                        // ```
-                        // mod foo {
-                        //     #![inner]
-                        // }
-                        // ```
-                        // we don't wanna strip away stuff in the `mod foo {` range, that is
-                        // here if the id corresponds to an inner attribute we got strip all
-                        // text ranges of the outer ones, and then all of the inner ones up
-                        // to the invoking attribute so that the inbetween is ignored.
-                        let text_range = item.syntax().text_range();
-                        let start = collect_attrs(&item)
-                            .nth(attr_id)
-                            .map(|attr| match attr.1 {
-                                Either::Left(it) => it.syntax().text_range().start(),
-                                Either::Right(it) => it.syntax().text_range().start(),
+                    // First expand into attribute invocations, this is required to be handled
+                    // upfront as any other macro call within will not semantically resolve unless
+                    // also descended.
+                    let res = self.with_ctx(|ctx| {
+                        token
+                            .parent_ancestors()
+                            .filter_map(ast::Item::cast)
+                            // FIXME: This might work incorrectly when we have a derive, followed by
+                            // an attribute on an item, like:
+                            // ```
+                            // #[derive(Debug$0)]
+                            // #[my_attr]
+                            // struct MyStruct;
+                            // ```
+                            // here we should not consider the attribute at all, as our cursor
+                            // technically lies outside of its expansion
+                            .find_map(|item| {
+                                // Don't force populate the dyn cache for items that don't have an attribute anyways
+                                item.attrs().next()?;
+                                ctx.item_to_macro_call(InFile::new(expansion, &item))
+                                    .zip(Some(item))
+                            })
+                            .map(|(call_id, item)| {
+                                let attr_id = match db.lookup_intern_macro_call(call_id).kind {
+                                    hir_expand::MacroCallKind::Attr {
+                                        invoc_attr_index, ..
+                                    } => invoc_attr_index.ast_index(),
+                                    _ => 0,
+                                };
+                                // FIXME: here, the attribute's text range is used to strip away all
+                                // entries from the start of the attribute "list" up the invoking
+                                // attribute. But in
+                                // ```
+                                // mod foo {
+                                //     #![inner]
+                                // }
+                                // ```
+                                // we don't wanna strip away stuff in the `mod foo {` range, that is
+                                // here if the id corresponds to an inner attribute we got strip all
+                                // text ranges of the outer ones, and then all of the inner ones up
+                                // to the invoking attribute so that the inbetween is ignored.
+                                let text_range = item.syntax().text_range();
+                                let start = collect_attrs(&item)
+                                    .nth(attr_id)
+                                    .map(|attr| match attr.1 {
+                                        Either::Left(it) => it.syntax().text_range().start(),
+                                        Either::Right(it) => it.syntax().text_range().start(),
+                                    })
+                                    .unwrap_or_else(|| text_range.start());
+                                let text_range = TextRange::new(start, text_range.end());
+                                filter_duplicates(tokens, text_range);
+                                process_expansion_for_token(ctx, &mut stack, call_id)
                             })
-                            .unwrap_or_else(|| text_range.start());
-                        let text_range = TextRange::new(start, text_range.end());
-                        filter_duplicates(tokens, text_range);
-                        return process_expansion_for_token(&mut stack, call_id);
+                    });
+
+                    if let Some(res) = res {
+                        return res;
                     }
 
+                    if always_descend_into_derives {
+                        let res = self.with_ctx(|ctx| {
+                            let (derives, adt) = token
+                                .parent_ancestors()
+                                .filter_map(ast::Adt::cast)
+                                .find_map(|adt| {
+                                    Some((
+                                        ctx.derive_macro_calls(InFile::new(expansion, &adt))?
+                                            .map(|(a, b, c)| (a, b, c.to_owned()))
+                                            .collect::<SmallVec<[_; 2]>>(),
+                                        adt,
+                                    ))
+                                })?;
+                            let mut res = None;
+                            for (_, derive_attr, derives) in derives {
+                                // as there may be multiple derives registering the same helper
+                                // name, we gotta make sure to call this for all of them!
+                                // FIXME: We need to call `f` for all of them as well though!
+                                res = res.or(process_expansion_for_token(
+                                    ctx,
+                                    &mut stack,
+                                    derive_attr,
+                                ));
+                                for derive in derives.into_iter().flatten() {
+                                    res = res
+                                        .or(process_expansion_for_token(ctx, &mut stack, derive));
+                                }
+                            }
+                            // remove all tokens that are within the derives expansion
+                            filter_duplicates(tokens, adt.syntax().text_range());
+                            Some(res)
+                        });
+                        // if we found derives, we can early exit. There is no way we can be in any
+                        // macro call at this point given we are not in a token tree
+                        if let Some(res) = res {
+                            return res;
+                        }
+                    }
                     // 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(Either::<ast::TokenTree, ast::Meta>::cast)
                         .last()?;
+
                     match tt {
                         // function-like macro call
                         Either::Left(tt) => {
+                            let macro_call = tt.syntax().parent().and_then(ast::MacroCall::cast)?;
                             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;
                             }
-                            let macro_call = tt.syntax().parent().and_then(ast::MacroCall::cast)?;
                             let mcall = InFile::new(expansion, macro_call);
                             let file_id = match m_cache.get(&mcall) {
                                 Some(&it) => it,
@@ -1216,13 +1284,16 @@ impl<'db> SemanticsImpl<'db> {
                             let text_range = tt.syntax().text_range();
                             filter_duplicates(tokens, text_range);
 
-                            process_expansion_for_token(&mut stack, file_id).or(file_id
-                                .eager_arg(self.db)
-                                .and_then(|arg| {
-                                    // also descend into eager expansions
-                                    process_expansion_for_token(&mut stack, arg)
-                                }))
+                            self.with_ctx(|ctx| {
+                                process_expansion_for_token(ctx, &mut stack, file_id).or(file_id
+                                    .eager_arg(db)
+                                    .and_then(|arg| {
+                                        // also descend into eager expansions
+                                        process_expansion_for_token(ctx, &mut stack, arg)
+                                    }))
+                            })
                         }
+                        Either::Right(_) if always_descend_into_derives => None,
                         // derive or derive helper
                         Either::Right(meta) => {
                             // attribute we failed expansion for earlier, this might be a derive invocation
@@ -1231,31 +1302,33 @@ impl<'db> SemanticsImpl<'db> {
                             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| {
+                                    let res = 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(expansion, &adt),
-                                            InFile::new(expansion, attr.clone()),
-                                        )
-                                        .map(|(_, call_id, _)| call_id)
+                                        let derive_call = ctx
+                                            .attr_to_derive_macro_call(
+                                                InFile::new(expansion, &adt),
+                                                InFile::new(expansion, attr.clone()),
+                                            )?
+                                            .1;
+
+                                        // resolved to a derive
+                                        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())
+                                        });
+                                        Some(process_expansion_for_token(
+                                            ctx,
+                                            &mut stack,
+                                            derive_call,
+                                        ))
                                     });
-
-                                    match derive_call {
-                                        Some(call_id) => {
-                                            // resolved to a derive
-                                            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, call_id,
-                                            );
-                                        }
-                                        None => Some(adt),
+                                    if let Some(res) = res {
+                                        return res;
                                     }
+                                    Some(adt)
                                 }
                                 None => {
                                     // Otherwise this could be a derive helper on a variant or field
@@ -1269,12 +1342,9 @@ impl<'db> SemanticsImpl<'db> {
                                     )
                                 }
                             }?;
-                            if !self.with_ctx(|ctx| ctx.has_derives(InFile::new(expansion, &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 an intert attribute or a derive helper
+                            // Not an attribute, nor a derive, so it's either an inert attribute or a derive helper
                             // Try to resolve to a derive helper and downmap
                             let resolver = &token
                                 .parent()
@@ -1282,7 +1352,7 @@ impl<'db> SemanticsImpl<'db> {
                                     self.analyze_impl(InFile::new(expansion, &parent), None, false)
                                 })?
                                 .resolver;
-                            let id = self.db.ast_id_map(expansion).ast_id(&adt);
+                            let id = db.ast_id_map(expansion).ast_id(&adt);
                             let helpers = resolver
                                 .def_map()
                                 .derive_helpers_in_scope(InFile::new(expansion, id))?;
@@ -1293,20 +1363,22 @@ impl<'db> SemanticsImpl<'db> {
                             }
 
                             let mut res = None;
-                            for (.., derive) in
-                                helpers.iter().filter(|(helper, ..)| *helper == attr_name)
-                            {
-                                // as there may be multiple derives registering the same helper
-                                // name, we gotta make sure to call this for all of them!
-                                // FIXME: We need to call `f` for all of them as well though!
-                                res = res.or(process_expansion_for_token(&mut stack, *derive));
-                            }
-                            res
+                            self.with_ctx(|ctx| {
+                                for (.., derive) in
+                                    helpers.iter().filter(|(helper, ..)| *helper == attr_name)
+                                {
+                                    // as there may be multiple derives registering the same helper
+                                    // name, we gotta make sure to call this for all of them!
+                                    // FIXME: We need to call `f` for all of them as well though!
+                                    res = res
+                                        .or(process_expansion_for_token(ctx, &mut stack, *derive));
+                                }
+                                res
+                            })
                         }
                     }
                 })()
                 .is_none();
-
                 if was_not_remapped {
                     if let ControlFlow::Break(b) = f(InFile::new(expansion, token), ctx) {
                         return Some(b);
diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs b/src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs
index 7f6c9af4740..7dac01a2689 100644
--- a/src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs
+++ b/src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs
@@ -108,7 +108,7 @@ use span::FileId;
 use stdx::impl_from;
 use syntax::{
     AstNode, AstPtr, SyntaxNode,
-    ast::{self, HasName},
+    ast::{self, HasAttrs, HasName},
 };
 use tt::TextRange;
 
@@ -411,10 +411,24 @@ impl SourceToDefCtx<'_, '_> {
             .map(|&(attr_id, call_id, ref ids)| (attr_id, call_id, &**ids))
     }
 
-    pub(super) fn has_derives(&mut self, adt: InFile<&ast::Adt>) -> bool {
+    pub(super) fn file_of_adt_has_derives(&mut self, adt: InFile<&ast::Adt>) -> bool {
         self.dyn_map(adt).as_ref().is_some_and(|map| !map[keys::DERIVE_MACRO_CALL].is_empty())
     }
 
+    pub(super) fn derive_macro_calls<'slf>(
+        &'slf mut self,
+        adt: InFile<&ast::Adt>,
+    ) -> Option<impl Iterator<Item = (AttrId, MacroCallId, &'slf [Option<MacroCallId>])> + use<'slf>>
+    {
+        self.dyn_map(adt).as_ref().map(|&map| {
+            let dyn_map = &map[keys::DERIVE_MACRO_CALL];
+            adt.value
+                .attrs()
+                .filter_map(move |attr| dyn_map.get(&AstPtr::new(&attr)))
+                .map(|&(attr_id, call_id, ref ids)| (attr_id, call_id, &**ids))
+        })
+    }
+
     fn to_def<Ast: AstNode + 'static, ID: Copy + 'static>(
         &mut self,
         src: InFile<&Ast>,