Repository: rust-lang/rust-analyzer
Pull request #12028 (2022-04-19 06:07:29)

internal: Fix changelog generation after repo move


Patch:
diff --git a/crates/ide_completion/src/completions.rs b/crates/ide_completion/src/completions.rs
index 36cee48aed..b8a904a3a0 100644
--- a/crates/ide_completion/src/completions.rs
+++ b/crates/ide_completion/src/completions.rs
@@ -103,12 +103,8 @@ impl Completions {
         item.add_to(self);
     }
 
-    pub(crate) fn add_nameref_keywords_with_colon(&mut self, ctx: &CompletionContext) {
-        ["self::", "super::", "crate::"].into_iter().for_each(|kw| self.add_keyword(ctx, kw));
-    }
-
     pub(crate) fn add_nameref_keywords(&mut self, ctx: &CompletionContext) {
-        ["self", "super", "crate"].into_iter().for_each(|kw| self.add_keyword(ctx, kw));
+        ["self::", "super::", "crate::"].into_iter().for_each(|kw| self.add_keyword(ctx, kw));
     }
 
     pub(crate) fn add_crate_roots(&mut self, ctx: &CompletionContext) {
diff --git a/crates/ide_completion/src/completions/attribute.rs b/crates/ide_completion/src/completions/attribute.rs
index eb887c3f73..c79a9816f4 100644
--- a/crates/ide_completion/src/completions/attribute.rs
+++ b/crates/ide_completion/src/completions/attribute.rs
@@ -107,7 +107,7 @@ pub(crate) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext)
                     acc.add_resolution(ctx, name, def);
                 }
             });
-            acc.add_nameref_keywords_with_colon(ctx);
+            acc.add_nameref_keywords(ctx);
         }
     }
 
diff --git a/crates/ide_completion/src/completions/attribute/derive.rs b/crates/ide_completion/src/completions/attribute/derive.rs
index 4f524dd73b..4611811539 100644
--- a/crates/ide_completion/src/completions/attribute/derive.rs
+++ b/crates/ide_completion/src/completions/attribute/derive.rs
@@ -102,7 +102,7 @@ pub(crate) fn complete_derive(acc: &mut Completions, ctx: &CompletionContext) {
                     None => acc.add_resolution(ctx, name, def),
                 }
             });
-            acc.add_nameref_keywords_with_colon(ctx);
+            acc.add_nameref_keywords(ctx);
         }
     }
 }
diff --git a/crates/ide_completion/src/completions/mod_.rs b/crates/ide_completion/src/completions/mod_.rs
index 7641086ff8..fb42e4e72f 100644
--- a/crates/ide_completion/src/completions/mod_.rs
+++ b/crates/ide_completion/src/completions/mod_.rs
@@ -9,16 +9,14 @@ use ide_db::{
 };
 use rustc_hash::FxHashSet;
 
-use crate::{context::NameContext, CompletionItem};
+use crate::{patterns::ImmediateLocation, CompletionItem};
 
 use crate::{context::CompletionContext, Completions};
 
 /// Complete mod declaration, i.e. `mod $0;`
 pub(crate) fn complete_mod(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
-    let mod_under_caret = match &ctx.name_ctx {
-        Some(NameContext::Module(mod_under_caret)) if mod_under_caret.item_list().is_none() => {
-            mod_under_caret
-        }
+    let mod_under_caret = match &ctx.completion_location {
+        Some(ImmediateLocation::ModDeclaration(mod_under_caret)) => mod_under_caret,
         _ => return None,
     };
 
diff --git a/crates/ide_completion/src/completions/pattern.rs b/crates/ide_completion/src/completions/pattern.rs
index a11bcc96fa..00f9bdfb51 100644
--- a/crates/ide_completion/src/completions/pattern.rs
+++ b/crates/ide_completion/src/completions/pattern.rs
@@ -202,7 +202,7 @@ fn pattern_path_completion(
                     acc.add_resolution(ctx, name, res);
                 }
             });
-            acc.add_nameref_keywords_with_colon(ctx);
+            acc.add_nameref_keywords(ctx);
         }
     }
 }
diff --git a/crates/ide_completion/src/completions/unqualified_path.rs b/crates/ide_completion/src/completions/unqualified_path.rs
index 94142e274a..235d7870c7 100644
--- a/crates/ide_completion/src/completions/unqualified_path.rs
+++ b/crates/ide_completion/src/completions/unqualified_path.rs
@@ -17,15 +17,21 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC
     }
     match ctx.path_context {
         Some(PathCompletionCtx {
-            is_absolute_path: false,
-            qualifier: None,
-            kind: None | Some(PathKind::Expr | PathKind::Type | PathKind::Mac),
+            kind:
+                Some(
+                    PathKind::Attr { .. }
+                    | PathKind::Derive
+                    | PathKind::Pat
+                    | PathKind::Use { .. }
+                    | PathKind::Vis { .. },
+                ),
             ..
-        }) => (),
+        }) => return,
+        Some(PathCompletionCtx { is_absolute_path: false, qualifier: None, .. }) => (),
         _ => return,
     }
 
-    acc.add_nameref_keywords(ctx);
+    ["self", "super", "crate"].into_iter().for_each(|kw| acc.add_keyword(ctx, kw));
 
     match &ctx.completion_location {
         Some(ImmediateLocation::ItemList | ImmediateLocation::Trait | ImmediateLocation::Impl) => {
diff --git a/crates/ide_completion/src/completions/use_.rs b/crates/ide_completion/src/completions/use_.rs
index 94df46efb0..3f757f9816 100644
--- a/crates/ide_completion/src/completions/use_.rs
+++ b/crates/ide_completion/src/completions/use_.rs
@@ -113,7 +113,7 @@ pub(crate) fn complete_use_tree(acc: &mut Completions, ctx: &CompletionContext)
                     acc.add_resolution(ctx, name, res);
                 }
             });
-            acc.add_nameref_keywords_with_colon(ctx);
+            acc.add_nameref_keywords(ctx);
         }
     }
 }
diff --git a/crates/ide_completion/src/completions/vis.rs b/crates/ide_completion/src/completions/vis.rs
index 338e003437..ca6f6dff1e 100644
--- a/crates/ide_completion/src/completions/vis.rs
+++ b/crates/ide_completion/src/completions/vis.rs
@@ -45,7 +45,7 @@ pub(crate) fn complete_vis(acc: &mut Completions, ctx: &CompletionContext) {
                 cov_mark::hit!(kw_completion_in);
                 acc.add_keyword(ctx, "in");
             }
-            acc.add_nameref_keywords(ctx);
+            ["self", "super", "crate"].into_iter().for_each(|kw| acc.add_keyword(ctx, kw));
         }
         _ => {}
     }
diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs
index 3ba02d78b5..12747b3ce4 100644
--- a/crates/ide_completion/src/context.rs
+++ b/crates/ide_completion/src/context.rs
@@ -100,30 +100,6 @@ pub(super) enum LifetimeContext {
     LabelDef,
 }
 
-#[derive(Debug)]
-#[allow(dead_code)]
-pub(super) enum NameContext {
-    Const,
-    ConstParam,
-    Enum,
-    Function,
-    IdentPat,
-    MacroDef,
-    MacroRules,
-    /// Fake node
-    Module(ast::Module),
-    RecordField,
-    Rename,
-    SelfParam,
-    Static,
-    Struct,
-    Trait,
-    TypeAlias,
-    TypeParam,
-    Union,
-    Variant,
-}
-
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub(crate) enum ParamKind {
     Function(ast::Fn),
@@ -148,11 +124,7 @@ pub(crate) struct CompletionContext<'a> {
     pub(super) krate: hir::Crate,
     /// The module of the `scope`.
     pub(super) module: hir::Module,
-
-    /// The expected name of what we are completing.
-    /// This is usually the parameter name of the function argument we are completing.
     pub(super) expected_name: Option<NameOrNameRef>,
-    /// The expected type of what we are completing.
     pub(super) expected_type: Option<Type>,
 
     /// The parent function of the cursor position if it exists.
@@ -161,7 +133,6 @@ pub(crate) struct CompletionContext<'a> {
     pub(super) impl_def: Option<ast::Impl>,
     /// The NameLike under the cursor in the original file if it exists.
     pub(super) name_syntax: Option<ast::NameLike>,
-    /// Are we completing inside a let statement with a missing semicolon?
     pub(super) incomplete_let: bool,
 
     pub(super) completion_location: Option<ImmediateLocation>,
@@ -169,7 +140,6 @@ pub(crate) struct CompletionContext<'a> {
     pub(super) fake_attribute_under_caret: Option<ast::Attr>,
     pub(super) previous_token: Option<SyntaxToken>,
 
-    pub(super) name_ctx: Option<NameContext>,
     pub(super) lifetime_ctx: Option<LifetimeContext>,
     pub(super) pattern_ctx: Option<PatternContext>,
     pub(super) path_context: Option<PathCompletionCtx>,
@@ -227,7 +197,7 @@ impl<'a> CompletionContext<'a> {
     }
 
     pub(crate) fn expects_variant(&self) -> bool {
-        matches!(self.name_ctx, Some(NameContext::Variant))
+        matches!(self.completion_location, Some(ImmediateLocation::Variant))
     }
 
     pub(crate) fn expects_non_trait_assoc_item(&self) -> bool {
@@ -251,8 +221,10 @@ impl<'a> CompletionContext<'a> {
     }
 
     pub(crate) fn expect_field(&self) -> bool {
-        matches!(self.completion_location, Some(ImmediateLocation::TupleField))
-            || matches!(self.name_ctx, Some(NameContext::RecordField))
+        matches!(
+            self.completion_location,
+            Some(ImmediateLocation::RecordField | ImmediateLocation::TupleField)
+        )
     }
 
     pub(crate) fn has_impl_or_trait_prev_sibling(&self) -> bool {
@@ -282,9 +254,13 @@ impl<'a> CompletionContext<'a> {
             )
             || matches!(
                 self.completion_location,
-                Some(ImmediateLocation::RecordPat(_) | ImmediateLocation::RecordExpr(_))
+                Some(
+                    ImmediateLocation::ModDeclaration(_)
+                        | ImmediateLocation::RecordPat(_)
+                        | ImmediateLocation::RecordExpr(_)
+                        | ImmediateLocation::Rename
+                )
             )
-            || matches!(self.name_ctx, Some(NameContext::Module(_) | NameContext::Rename))
     }
 
     pub(crate) fn expects_expression(&self) -> bool {
@@ -429,7 +405,6 @@ impl<'a> CompletionContext<'a> {
         let scope = sema.scope_at_offset(&token.parent()?, offset)?;
         let krate = scope.krate();
         let module = scope.module();
-
         let mut locals = FxHashMap::default();
         scope.process_all_names(&mut |name, scope| {
             if let ScopeDef::Local(local) = scope {
@@ -454,7 +429,6 @@ impl<'a> CompletionContext<'a> {
             name_syntax: None,
             lifetime_ctx: None,
             pattern_ctx: None,
-            name_ctx: None,
             completion_location: None,
             prev_sibling: None,
             fake_attribute_under_caret: None,
@@ -473,9 +447,8 @@ impl<'a> CompletionContext<'a> {
         Some(ctx)
     }
 
-    /// Expand attributes and macro calls at the current cursor position for both the original file
-    /// and fake file repeatedly. As soon as one of the two expansions fail we stop so the original
-    /// and speculative states stay in sync.
+    /// Do the attribute expansion at the current cursor position for both original file and fake file
+    /// as long as possible. As soon as one of the two expansions fail we stop to stay in sync.
     fn expand_and_fill(
         &mut self,
         mut original_file: SyntaxNode,
@@ -496,9 +469,7 @@ impl<'a> CompletionContext<'a> {
                 ),
                 |(a, b)| parent_item(a).zip(parent_item(b)),
             );
-
-            // first try to expand attributes as these are always the outermost macro calls
-            'ancestors: for (actual_item, item_with_fake_ident) in ancestor_items {
+            for (actual_item, item_with_fake_ident) in ancestor_items {
                 match (
                     self.sema.expand_attr_macro(&actual_item),
                     self.sema.speculative_expand_attr_macro(
@@ -507,14 +478,12 @@ impl<'a> CompletionContext<'a> {
                         fake_ident_token.clone(),
                     ),
                 ) {
-                    // maybe parent items have attributes, so continue walking the ancestors
-                    (None, None) => continue 'ancestors,
+                    // maybe parent items have attributes
+                    (None, None) => (),
                     // successful expansions
                     (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) => {
                         let new_offset = fake_mapped_token.text_range().start();
                         if new_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;
@@ -527,15 +496,13 @@ impl<'a> CompletionContext<'a> {
                     _ => break 'expansion,
                 }
             }
-
-            // 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) {
                 Some(it) => it,
-                None => break 'expansion,
+                None => break,
             };
             let spec_tt = match find_node_at_offset::<ast::TokenTree>(&speculative_file, offset) {
                 Some(it) => it,
-                None => break 'expansion,
+                None => break,
             };
 
             // Expand pseudo-derive expansion
@@ -543,7 +510,7 @@ impl<'a> CompletionContext<'a> {
                 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()),
             ) {
-                if let (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) = (
+                match (
                     self.sema.expand_derive_as_pseudo_attr_macro(&orig_attr),
                     self.sema.speculative_expand_derive_as_pseudo_attr_macro(
                         &orig_attr,
@@ -551,15 +518,17 @@ impl<'a> CompletionContext<'a> {
                         fake_ident_token.clone(),
                     ),
                 ) {
-                    derive_ctx = Some((
-                        actual_expansion,
-                        fake_expansion,
-                        fake_mapped_token.text_range().start(),
-                        orig_attr,
-                    ));
+                    // Clearly not a derive macro
+                    (None, None) => (),
+                    // successful expansions
+                    (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) => {
+                        let new_offset = fake_mapped_token.text_range().start();
+                        derive_ctx = Some((actual_expansion, fake_expansion, new_offset));
+                        break 'expansion;
+                    }
+                    // exactly one expansion failed, inconsistent state so stop expanding completely
+                    _ => break 'expansion,
                 }
-                // at this point we won't have any more successful expansions, so stop
-                break 'expansion;
             }
 
             // Expand fn-like macro calls
@@ -570,14 +539,12 @@ impl<'a> CompletionContext<'a> {
                 let mac_call_path0 = actual_macro_call.path().as_ref().map(|s| s.syntax().text());
                 let mac_call_path1 =
                     macro_call_with_fake_ident.path().as_ref().map(|s| s.syntax().text());
-
-                // inconsistent state, stop expanding
                 if mac_call_path0 != mac_call_path1 {
-                    break 'expansion;
+                    break;
                 }
                 let speculative_args = match macro_call_with_fake_ident.token_tree() {
                     Some(tt) => tt,
-                    None => break 'expansion,
+                    None => break,
                 };
 
                 match (
@@ -592,30 +559,24 @@ impl<'a> CompletionContext<'a> {
                     (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) => {
                         let new_offset = fake_mapped_token.text_range().start();
                         if new_offset > actual_expansion.text_range().end() {
-                            // offset outside of bounds from the original expansion,
-                            // stop here to prevent problems from happening
-                            break 'expansion;
+                            break;
                         }
                         original_file = actual_expansion;
                         speculative_file = fake_expansion;
                         fake_ident_token = fake_mapped_token;
                         offset = new_offset;
-                        continue 'expansion;
+                        continue;
                     }
-                    // at least on expansion failed, we won't have anything to expand from this point
-                    // onwards so break out
-                    _ => break 'expansion,
+                    _ => break,
                 }
             }
 
-            // none of our states have changed so stop the loop
-            break 'expansion;
+            break;
         }
 
         self.fill(&original_file, speculative_file, offset, derive_ctx);
     }
 
-    /// Calculate the expected type and name of the cursor position.
     fn expected_type_and_name(&self) -> (Option<Type>, Option<NameOrNameRef>) {
         let mut node = match self.token.parent() {
             Some(it) => it,
@@ -752,14 +713,12 @@ impl<'a> CompletionContext<'a> {
         }
     }
 
-    /// Fill the completion context, this is what does semantic reasoning about the surrounding context
-    /// of the completion location.
     fn fill(
         &mut self,
         original_file: &SyntaxNode,
         file_with_fake_ident: SyntaxNode,
         offset: TextSize,
-        derive_ctx: Option<(SyntaxNode, SyntaxNode, TextSize, ast::Attr)>,
+        derive_ctx: Option<(SyntaxNode, SyntaxNode, TextSize)>,
     ) {
         let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap();
         let syntax_element = NodeOrToken::Token(fake_ident_token);
@@ -783,14 +742,16 @@ impl<'a> CompletionContext<'a> {
         (self.expected_type, self.expected_name) = self.expected_type_and_name();
 
         // Overwrite the path kind for derives
-        if let Some((original_file, file_with_fake_ident, offset, origin_attr)) = derive_ctx {
-            self.existing_derives = self
+        if let Some((original_file, file_with_fake_ident, offset)) = derive_ctx {
+            let attr = self
                 .sema
-                .resolve_derive_macro(&origin_attr)
-                .into_iter()
-                .flatten()
-                .flatten()
-                .collect();
+                .token_ancestors_with_macros(self.token.clone())
+                .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
+                .find_map(ast::Attr::cast);
+            if let Some(attr) = &attr {
+                self.existing_derives =
+                    self.sema.resolve_derive_macro(attr).into_iter().flatten().flatten().collect();
+            }
 
             if let Some(ast::NameLike::NameRef(name_ref)) =
                 find_node_at_offset(&file_with_fake_ident, offset)
@@ -840,12 +801,7 @@ impl<'a> CompletionContext<'a> {
                 }
             }
             ast::NameLike::Name(name) => {
-                if let Some((name_ctx, pat_ctx)) =
-                    Self::classify_name(&self.sema, original_file, name)
-                {
-                    self.pattern_ctx = pat_ctx;
-                    self.name_ctx = Some(name_ctx);
-                }
+                self.pattern_ctx = Self::classify_name(&self.sema, original_file, name);
             }
         }
     }
@@ -878,44 +834,17 @@ impl<'a> CompletionContext<'a> {
         _sema: &Semantics<RootDatabase>,
         original_file: &SyntaxNode,
         name: ast::Name,
-    ) -> Option<(NameContext, Option<PatternContext>)> {
-        let parent = name.syntax().parent()?;
-        let mut pat_ctx = None;
-        let name_ctx = match_ast! {
-            match parent {
-                ast::Const(_) => NameContext::Const,
-                ast::ConstParam(_) => NameContext::ConstParam,
-                ast::Enum(_) => NameContext::Enum,
-                ast::Fn(_) => NameContext::Function,
-                ast::IdentPat(bind_pat) => {
-                    let is_name_in_field_pat = bind_pat
-                        .syntax()
-                        .parent()
-                        .and_then(ast::RecordPatField::cast)
-                        .map_or(false, |pat_field| pat_field.name_ref().is_none());
-                    if !is_name_in_field_pat {
-                        pat_ctx = Some(pattern_context_for(original_file, bind_pat.into()));
-                    }
-
-                    NameContext::IdentPat
-                },
-                ast::MacroDef(_) => NameContext::MacroDef,
-                ast::MacroRules(_) => NameContext::MacroRules,
-                ast::Module(module) => NameContext::Module(module),
-                ast::RecordField(_) => NameContext::RecordField,
-                ast::Rename(_) => NameContext::Rename,
-                ast::SelfParam(_) => NameContext::SelfParam,
-                ast::Static(_) => NameContext::Static,
-                ast::Struct(_) => NameContext::Struct,
-                ast::Trait(_) => NameContext::Trait,
-                ast::TypeAlias(_) => NameContext::TypeAlias,
-                ast::TypeParam(_) => NameContext::TypeParam,
-                ast::Union(_) => NameContext::Union,
-                ast::Variant(_) => NameContext::Variant,
-                _ => return None,
-            }
-        };
-        Some((name_ctx, pat_ctx))
+    ) -> Option<PatternContext> {
+        let bind_pat = name.syntax().parent().and_then(ast::IdentPat::cast)?;
+        let is_name_in_field_pat = bind_pat
+            .syntax()
+            .parent()
+            .and_then(ast::RecordPatField::cast)
+            .map_or(false, |pat_field| pat_field.name_ref().is_none());
+        if is_name_in_field_pat {
+            return None;
+        }
+        Some(pattern_context_for(original_file, bind_pat.into()))
     }
 
     fn classify_name_ref(
@@ -1087,7 +1016,6 @@ fn pattern_context_for(original_file: &SyntaxNode, pat: ast::Pat) -> PatternCont
     }
 }
 
-/// Attempts to find `node` inside `syntax` via `node`'s text range.
 fn find_node_in_file<N: AstNode>(syntax: &SyntaxNode, node: &N) -> Option<N> {
     let syntax_range = syntax.text_range();
     let range = node.syntax().text_range();
@@ -1095,8 +1023,7 @@ fn find_node_in_file<N: AstNode>(syntax: &SyntaxNode, node: &N) -> Option<N> {
     syntax.covering_element(intersection).ancestors().find_map(N::cast)
 }
 
-/// Attempts to find `node` inside `syntax` via `node`'s text range while compensating
-/// for the offset introduced by the fake ident.
+/// Compensates for the offset introduced by the fake ident
 /// This is wrong if `node` comes before the insertion point! Use `find_node_in_file` instead.
 fn find_node_in_file_compensated<N: AstNode>(syntax: &SyntaxNode, node: &N) -> Option<N> {
     let syntax_range = syntax.text_range();
@@ -1165,7 +1092,6 @@ const OP_TRAIT_LANG_NAMES: &[&str] = &[
     "shr",
     "sub",
 ];
-
 #[cfg(test)]
 mod tests {
     use expect_test::{expect, Expect};
diff --git a/crates/ide_completion/src/patterns.rs b/crates/ide_completion/src/patterns.rs
index 6fdec78385..5fd9602b46 100644
--- a/crates/ide_completion/src/patterns.rs
+++ b/crates/ide_completion/src/patterns.rs
@@ -41,16 +41,21 @@ pub(crate) enum TypeAnnotation {
 /// from which file the nodes are.
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub(crate) enum ImmediateLocation {
+    Rename,
     Impl,
     Trait,
+    RecordField,
     TupleField,
     RefExpr,
     IdentPat,
     StmtList,
     ItemList,
     TypeBound,
+    Variant,
     /// Original file ast node
     TypeAnnotation(TypeAnnotation),
+    /// Fake file ast node
+    ModDeclaration(ast::Module),
     /// Original file ast node
     MethodCall {
         receiver: Option<ast::Expr>,
@@ -75,7 +80,6 @@ pub(crate) enum ImmediateLocation {
     /// The record pat of the field name we are completing
     ///
     /// Original file ast node
-    // FIXME: This should be moved to pattern_ctx
     RecordPat(ast::RecordPat),
 }
 
@@ -207,10 +211,17 @@ pub(crate) fn determine_location(
     let res = match_ast! {
         match parent {
             ast::IdentPat(_) => ImmediateLocation::IdentPat,
+            ast::Rename(_) => ImmediateLocation::Rename,
             ast::StmtList(_) => ImmediateLocation::StmtList,
             ast::SourceFile(_) => ImmediateLocation::ItemList,
             ast::ItemList(_) => ImmediateLocation::ItemList,
             ast::RefExpr(_) => ImmediateLocation::RefExpr,
+            ast::Variant(_) => ImmediateLocation::Variant,
+            ast::RecordField(it) => if it.ty().map_or(false, |it| it.syntax().text_range().contains(offset)) {
+                return None;
+            } else {
+                ImmediateLocation::RecordField
+            },
             ast::RecordExprFieldList(_) => sema
                 .find_node_at_offset_with_macros(original_file, offset)
                 .map(ImmediateLocation::RecordExprUpdate)?,
@@ -226,6 +237,13 @@ pub(crate) fn determine_location(
             ast::GenericArgList(_) => sema
                 .find_node_at_offset_with_macros(original_file, offset)
                 .map(ImmediateLocation::GenericArgList)?,
+            ast::Module(it) => {
+                if it.item_list().is_none() {
+                    ImmediateLocation::ModDeclaration(it)
+                } else {
+                    return None;
+                }
+            },
             ast::FieldExpr(it) => {
                 let receiver = find_in_original_file(it.expr(), original_file);
                 let receiver_is_ambiguous_float_literal = if let Some(ast::Expr::Literal(l)) = &receiver {
@@ -457,6 +475,13 @@ mod tests {
         check_location(r"impl A { fn f$0 }", None);
     }
 
+    #[test]
+    fn test_record_field_loc() {
+        check_location(r"struct Foo { f$0 }", ImmediateLocation::RecordField);
+        check_location(r"struct Foo { f$0 pub f: i32}", ImmediateLocation::RecordField);
+        check_location(r"struct Foo { pub f: i32, f$0 }", ImmediateLocation::RecordField);
+    }
+
     #[test]
     fn test_block_expr_loc() {
         check_location(r"fn my_fn() { let a = 2; f$0 }", ImmediateLocation::StmtList);
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 3ac64e66b5..e305fe408e 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -236,7 +236,7 @@ impl GlobalState {
                                     self.fetch_workspaces_queue.op_completed(workspaces);
 
                                     let old = Arc::clone(&self.workspaces);
-                                    self.switch_workspaces("fetched workspace".to_string());
+                                    self.switch_workspaces();
                                     let workspaces_updated = !Arc::ptr_eq(&old, &self.workspaces);
 
                                     if self.config.run_build_scripts() && workspaces_updated {
@@ -259,7 +259,7 @@ impl GlobalState {
                                 BuildDataProgress::End(build_data_result) => {
                                     self.fetch_build_data_queue.op_completed(build_data_result);
 
-                                    self.switch_workspaces("fetched build data".to_string());
+                                    self.switch_workspaces();
 
                                     (Some(Progress::End), None)
                                 }
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index b68f9d5f59..d4e40d807d 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -166,9 +166,9 @@ impl GlobalState {
         });
     }
 
-    pub(crate) fn switch_workspaces(&mut self, cause: Cause) {
+    pub(crate) fn switch_workspaces(&mut self) {
         let _p = profile::span("GlobalState::switch_workspaces");
-        tracing::info!(%cause, "will switch workspaces");
+        tracing::info!("will switch workspaces");
 
         if let Err(error_message) = self.fetch_workspace_error() {
             self.show_and_log_error(error_message, None);
@@ -224,8 +224,6 @@ impl GlobalState {
         if same_workspaces {
             let (workspaces, build_scripts) = self.fetch_build_data_queue.last_op_result();
             if Arc::ptr_eq(workspaces, &self.workspaces) {
-                tracing::debug!("set build scripts to workspaces");
-
                 let workspaces = workspaces
                     .iter()
                     .cloned()
@@ -239,14 +237,11 @@ impl GlobalState {
                 // Workspaces are the same, but we've updated build data.
                 self.workspaces = Arc::new(workspaces);
             } else {
-                tracing::info!("build scrips do not match the version of the active workspace");
                 // Current build scripts do not match the version of the active
                 // workspace, so there's nothing for us to update.
                 return;
             }
         } else {
-            tracing::debug!("abandon build scripts for workspaces");
-
             // Here, we completely changed the workspace (Cargo.toml edit), so
             // we don't care about build-script results, they are stale.
             self.workspaces = Arc::new(workspaces)
diff --git a/xtask/src/release/changelog.rs b/xtask/src/release/changelog.rs
index ceef9fac2d..2647f7794f 100644
--- a/xtask/src/release/changelog.rs
+++ b/xtask/src/release/changelog.rs
@@ -11,56 +11,53 @@ pub(crate) fn get_changelog(
     prev_tag: &str,
     today: &str,
 ) -> anyhow::Result<String> {
-    let git_log = cmd!(sh, "git log {prev_tag}..HEAD --merges --reverse").read()?;
+    let token = match env::var("GITHUB_TOKEN") {
+        Ok(token) => token,
+        Err(_) => bail!("Please obtain a personal access token from https://github.com/settings/tokens and set the `GITHUB_TOKEN` environment variable."),
+    };
+
+    let git_log = cmd!(sh, "git log {prev_tag}..HEAD --reverse").read()?;
     let mut features = String::new();
     let mut fixes = String::new();
     let mut internal = String::new();
     let mut others = String::new();
     for line in git_log.lines() {
         let line = line.trim_start();
-        if let Some(p) = line.find(':') {
-            let pr = &line[..p];
-            if let Ok(pr_num) = pr.parse::<u32>() {
-                let accept = "Accept: application/vnd.github.v3+json";
-                let token = match env::var("GITHUB_TOKEN") {
-                    Ok(token) => token,
-                    Err(_) => bail!("Please obtain a personal access token from https://github.com/settings/tokens and set the `GITHUB_TOKEN` environment variable."),
-                };
-                let authorization = format!("Authorization: token {}", token);
-                let pr_url = "https://api.github.com/repos/rust-analyzer/rust-analyzer/issues";
-
-                // we don't use an HTTPS client or JSON parser to keep the build times low
-                let pr_json =
-                    cmd!(sh, "curl -s -H {accept} -H {authorization} {pr_url}/{pr}").read()?;
-                let pr_title = cmd!(sh, "jq .title").stdin(&pr_json).read()?;
-                let pr_title = unescape(&pr_title[1..pr_title.len() - 1]);
-                let pr_comment = cmd!(sh, "jq .body").stdin(pr_json).read()?;
-
-                let comments_json =
-                    cmd!(sh, "curl -s -H {accept} -H {authorization} {pr_url}/{pr}/comments")
-                        .read()?;
-                let pr_comments = cmd!(sh, "jq .[].body").stdin(comments_json).read()?;
-
-                let l = iter::once(pr_comment.as_str())
-                    .chain(pr_comments.lines())
-                    .rev()
-                    .find_map(|it| {
-                        let it = unescape(&it[1..it.len() - 1]);
-                        it.lines().find_map(parse_changelog_line)
-                    })
-                    .into_iter()
-                    .next()
-                    .unwrap_or_else(|| parse_title_line(&pr_title));
-                let s = match l.kind {
-                    PrKind::Feature => &mut features,
-                    PrKind::Fix => &mut fixes,
-                    PrKind::Internal => &mut internal,
-                    PrKind::Other => &mut others,
-                    PrKind::Skip => continue,
-                };
-                writeln!(s, "* pr:{}[] {}", pr_num, l.message.as_deref().unwrap_or(&pr_title))
-                    .unwrap();
-            }
+        if let Some(pr_num) = parse_pr_number(&line) {
+            let accept = "Accept: application/vnd.github.v3+json";
+            let authorization = format!("Authorization: token {}", token);
+            let pr_url = "https://api.github.com/repos/rust-lang/rust-analyzer/issues";
+
+            // we don't use an HTTPS client or JSON parser to keep the build times low
+            let pr = pr_num.to_string();
+            let pr_json =
+                cmd!(sh, "curl -s -H {accept} -H {authorization} {pr_url}/{pr}").read()?;
+            let pr_title = cmd!(sh, "jq .title").stdin(&pr_json).read()?;
+            let pr_title = unescape(&pr_title[1..pr_title.len() - 1]);
+            let pr_comment = cmd!(sh, "jq .body").stdin(pr_json).read()?;
+
+            let comments_json =
+                cmd!(sh, "curl -s -H {accept} -H {authorization} {pr_url}/{pr}/comments").read()?;
+            let pr_comments = cmd!(sh, "jq .[].body").stdin(comments_json).read()?;
+
+            let l = iter::once(pr_comment.as_str())
+                .chain(pr_comments.lines())
+                .rev()
+                .find_map(|it| {
+                    let it = unescape(&it[1..it.len() - 1]);
+                    it.lines().find_map(parse_changelog_line)
+                })
+                .into_iter()
+                .next()
+                .unwrap_or_else(|| parse_title_line(&pr_title));
+            let s = match l.kind {
+                PrKind::Feature => &mut features,
+                PrKind::Fix => &mut fixes,
+                PrKind::Internal => &mut internal,
+                PrKind::Other => &mut others,
+                PrKind::Skip => continue,
+            };
+            writeln!(s, "* pr:{}[] {}", pr_num, l.message.as_deref().unwrap_or(&pr_title)).unwrap();
         }
     }
 
@@ -73,11 +70,6 @@ pub(crate) fn get_changelog(
 Commit: commit:{}[] +
 Release: release:{}[]
 
-== Sponsors
-
-**Become a sponsor:** On https://opencollective.com/rust-analyzer/[OpenCollective] or
-https://github.com/sponsors/rust-analyzer[GitHub Sponsors].
-
 == New Features
 
 {}
@@ -117,6 +109,24 @@ fn unescape(s: &str) -> String {
     s.replace(r#"\""#, "").replace(r#"\n"#, "\n").replace(r#"\r"#, "")
 }
 
+fn parse_pr_number(s: &str) -> Option<u32> {
+    const BORS_PREFIX: &str = "Merge #";
+    const HOMU_PREFIX: &str = "Auto merge of #";
+    if s.starts_with(BORS_PREFIX) {
+        let s = &s[BORS_PREFIX.len()..];
+        s.parse().ok()
+    } else if s.starts_with(HOMU_PREFIX) {
+        let s = &s[HOMU_PREFIX.len()..];
+        if let Some(space) = s.find(' ') {
+            s[..space].parse().ok()
+        } else {
+            None
+        }
+    } else {
+        None
+    }
+}
+
 fn parse_changelog_line(s: &str) -> Option<PrInfo> {
     let parts = s.splitn(3, ' ').collect::<Vec<_>>();
     if parts.len() < 2 || parts[0] != "changelog" {


Test patch:
diff --git a/crates/ide_completion/src/tests/attribute.rs b/crates/ide_completion/src/tests/attribute.rs
index b851bf6a84..4ee95e8928 100644
--- a/crates/ide_completion/src/tests/attribute.rs
+++ b/crates/ide_completion/src/tests/attribute.rs
@@ -747,27 +747,6 @@ mod derive {
         );
     }
 
-    #[test]
-    fn derive_with_existing_derives() {
-        check_derive(
-            r#"
-//- minicore: derive, copy, clone, ord, eq, default, fmt
-#[derive(PartialEq, Eq, Or$0)] struct Test;
-"#,
-            expect![[r#"
-                md core
-                de Default         macro Default
-                de Clone, Copy
-                de PartialOrd, Ord
-                de Clone           macro Clone
-                de PartialOrd
-                kw self::
-                kw super::
-                kw crate::
-            "#]],
-        );
-    }
-
     #[test]
     fn derive_flyimport() {
         check_derive(