Repository: rust-lang/rust-analyzer
Pull request #12377 (2022-05-24 20:52:48)

minor: log build script invocations

Veykril: @bors r+
bors: :pushpin: Commit 364e0e522957769201db33ff64de018a73f90183 has been approved by `Veykril`

<!-- @bors r=Veykril 364e0e522957769201db33ff64de018a73f90183 -->
<!-- homu: {"type":"Approved","sha":"364e0e522957769201db33ff64de018a73f90183","approver":"Veykril"} -->
bors: :hourglass: Testing commit 364e0e522957769201db33ff64de018a73f90183 with merge e4fcccc7b4dfef934bf0cc6126d87674f46e4072...
<!-- homu: {"type":"BuildStarted","head_sha":"364e0e522957769201db33ff64de018a73f90183","merge_sha":"e4fcccc7b4dfef934bf0cc6126d87674f46e4072"} -->

Patch:
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index a99894dc44..dce9542431 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -241,8 +241,6 @@ jobs:
           name: ${{ env.TAG }}
           token: ${{ secrets.GITHUB_TOKEN }}
 
-      - run: rm dist/rust-analyzer-no-server.vsix
-
       - run: npm ci
         working-directory: ./editors/code
 
diff --git a/crates/ide-completion/src/completions/dot.rs b/crates/ide-completion/src/completions/dot.rs
index 1e0b771166..9b5ececfd0 100644
--- a/crates/ide-completion/src/completions/dot.rs
+++ b/crates/ide-completion/src/completions/dot.rs
@@ -3,31 +3,30 @@
 use ide_db::FxHashSet;
 
 use crate::{
-    context::{
-        CompletionContext, DotAccess, DotAccessKind, NameRefContext, PathCompletionCtx, PathKind,
-    },
-    CompletionItem, CompletionItemKind, Completions,
+    context::{CompletionContext, DotAccess, NameRefContext, PathCompletionCtx, PathKind},
+    Completions,
 };
 
 /// Complete dot accesses, i.e. fields or methods.
 pub(crate) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) {
-    let (dot_access, receiver_ty) = match ctx.nameref_ctx() {
+    let (dot_access, dot_receiver) = match ctx.nameref_ctx() {
         Some(NameRefContext {
-            dot_access: Some(access @ DotAccess { receiver_ty: Some(receiver_ty), .. }),
+            dot_access:
+                Some(
+                    access @ (DotAccess::Method { receiver: Some(receiver), .. }
+                    | DotAccess::Field { receiver: Some(receiver), .. }),
+                ),
             ..
-        }) => (access, &receiver_ty.original),
+        }) => (access, receiver),
         _ => return complete_undotted_self(acc, ctx),
     };
 
-    // Suggest .await syntax for types that implement Future trait
-    if receiver_ty.impls_future(ctx.db) {
-        let mut item =
-            CompletionItem::new(CompletionItemKind::Keyword, ctx.source_range(), "await");
-        item.detail("expr.await");
-        item.add_to(acc);
-    }
+    let receiver_ty = match ctx.sema.type_of_expr(dot_receiver) {
+        Some(ty) => ty.original,
+        _ => return,
+    };
 
-    if let DotAccessKind::Method { .. } = dot_access.kind {
+    if let DotAccess::Method { .. } = dot_access {
         cov_mark::hit!(test_no_struct_field_completion_for_method_call);
     } else {
         complete_fields(
diff --git a/crates/ide-completion/src/completions/expr.rs b/crates/ide-completion/src/completions/expr.rs
index ae7b42e305..87fae18f4c 100644
--- a/crates/ide-completion/src/completions/expr.rs
+++ b/crates/ide-completion/src/completions/expr.rs
@@ -5,7 +5,7 @@ use ide_db::FxHashSet;
 use syntax::T;
 
 use crate::{
-    context::{NameRefContext, PathCompletionCtx, PathKind, PathQualifierCtx},
+    context::{PathCompletionCtx, PathKind, PathQualifierCtx},
     CompletionContext, Completions,
 };
 
@@ -15,25 +15,14 @@ pub(crate) fn complete_expr_path(acc: &mut Completions, ctx: &CompletionContext)
         return;
     }
 
-    let (is_absolute_path, qualifier, in_block_expr, in_loop_body, is_func_update) =
-        match ctx.nameref_ctx() {
-            Some(NameRefContext {
-                path_ctx:
-                    Some(PathCompletionCtx {
-                        kind: PathKind::Expr { in_block_expr, in_loop_body },
-                        is_absolute_path,
-                        qualifier,
-                        ..
-                    }),
-                record_expr,
+    let (is_absolute_path, qualifier, in_block_expr, in_loop_body, in_functional_update) =
+        match ctx.path_context() {
+            Some(&PathCompletionCtx {
+                kind: PathKind::Expr { in_block_expr, in_loop_body, in_functional_update },
+                is_absolute_path,
+                ref qualifier,
                 ..
-            }) => (
-                *is_absolute_path,
-                qualifier,
-                *in_block_expr,
-                *in_loop_body,
-                record_expr.as_ref().map_or(false, |&(_, it)| it),
-            ),
+            }) => (is_absolute_path, qualifier, in_block_expr, in_loop_body, in_functional_update),
             _ => return,
         };
 
@@ -176,7 +165,7 @@ pub(crate) fn complete_expr_path(acc: &mut Completions, ctx: &CompletionContext)
                 }
             });
 
-            if !is_func_update {
+            if !in_functional_update {
                 let mut add_keyword =
                     |kw, snippet| super::keyword::add_keyword(acc, ctx, kw, snippet);
 
diff --git a/crates/ide-completion/src/completions/keyword.rs b/crates/ide-completion/src/completions/keyword.rs
index 5917da9b7f..14211f86ba 100644
--- a/crates/ide-completion/src/completions/keyword.rs
+++ b/crates/ide-completion/src/completions/keyword.rs
@@ -5,12 +5,12 @@
 use syntax::T;
 
 use crate::{
-    context::{NameRefContext, PathKind},
-    CompletionContext, CompletionItem, CompletionItemKind, Completions,
+    context::PathKind, patterns::ImmediateLocation, CompletionContext, CompletionItem,
+    CompletionItemKind, Completions,
 };
 
 pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) {
-    if matches!(ctx.nameref_ctx(), Some(NameRefContext { record_expr: Some(_), .. })) {
+    if matches!(ctx.completion_location, Some(ImmediateLocation::RecordExpr(_))) {
         cov_mark::hit!(no_keyword_completion_in_record_lit);
         return;
     }
diff --git a/crates/ide-completion/src/completions/pattern.rs b/crates/ide-completion/src/completions/pattern.rs
index ab35dadf92..211ca4e531 100644
--- a/crates/ide-completion/src/completions/pattern.rs
+++ b/crates/ide-completion/src/completions/pattern.rs
@@ -15,6 +15,7 @@ pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) {
         Some(ctx) => ctx,
         _ => return,
     };
+    let refutable = patctx.refutability == PatternRefutability::Refutable;
 
     if let Some(path_ctx) = ctx.path_context() {
         pattern_path_completion(acc, ctx, path_ctx);
@@ -46,11 +47,6 @@ pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) {
         }
     }
 
-    if patctx.record_pat.is_some() {
-        return;
-    }
-
-    let refutable = patctx.refutability == PatternRefutability::Refutable;
     let single_variant_enum = |enum_: hir::Enum| ctx.db.enum_data(enum_.into()).variants.len() == 1;
 
     if let Some(hir::Adt::Enum(e)) =
diff --git a/crates/ide-completion/src/completions/postfix.rs b/crates/ide-completion/src/completions/postfix.rs
index 4868225ce3..28256bd704 100644
--- a/crates/ide-completion/src/completions/postfix.rs
+++ b/crates/ide-completion/src/completions/postfix.rs
@@ -13,7 +13,7 @@ use text_edit::TextEdit;
 
 use crate::{
     completions::postfix::format_like::add_format_like_completions,
-    context::{CompletionContext, DotAccess, DotAccessKind, NameRefContext},
+    context::{CompletionContext, DotAccess, NameRefContext},
     item::{Builder, CompletionRelevancePostfixMatch},
     CompletionItem, CompletionItemKind, CompletionRelevance, Completions, SnippetScope,
 };
@@ -23,25 +23,34 @@ pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
         return;
     }
 
-    let (dot_receiver, receiver_ty, receiver_is_ambiguous_float_literal) = match ctx.nameref_ctx() {
+    let (dot_receiver, receiver_is_ambiguous_float_literal) = match ctx.nameref_ctx() {
         Some(NameRefContext {
-            dot_access: Some(DotAccess { receiver_ty: Some(ty), receiver: Some(it), kind, .. }),
+            dot_access: Some(DotAccess::Method { receiver: Some(it), .. }),
             ..
-        }) => (
-            it,
-            &ty.original,
-            match *kind {
-                DotAccessKind::Field { receiver_is_ambiguous_float_literal } => {
-                    receiver_is_ambiguous_float_literal
-                }
-                DotAccessKind::Method { .. } => false,
-            },
-        ),
+        }) => (it, false),
+        Some(NameRefContext {
+            dot_access:
+                Some(DotAccess::Field { receiver: Some(it), receiver_is_ambiguous_float_literal }),
+            ..
+        }) => (it, *receiver_is_ambiguous_float_literal),
         _ => return,
     };
 
     let receiver_text = get_receiver_text(dot_receiver, receiver_is_ambiguous_float_literal);
 
+    let receiver_ty = match ctx.sema.type_of_expr(dot_receiver) {
+        Some(it) => it.original,
+        None => return,
+    };
+
+    // Suggest .await syntax for types that implement Future trait
+    if receiver_ty.impls_future(ctx.db) {
+        let mut item =
+            CompletionItem::new(CompletionItemKind::Keyword, ctx.source_range(), "await");
+        item.detail("expr.await");
+        item.add_to(acc);
+    }
+
     let cap = match ctx.config.snippet_cap {
         Some(it) => it,
         None => return,
diff --git a/crates/ide-completion/src/completions/record.rs b/crates/ide-completion/src/completions/record.rs
index 6717ca0a0e..05fbe8513e 100644
--- a/crates/ide-completion/src/completions/record.rs
+++ b/crates/ide-completion/src/completions/record.rs
@@ -3,65 +3,67 @@ use ide_db::SymbolKind;
 use syntax::{ast::Expr, T};
 
 use crate::{
-    context::{NameRefContext, PatternContext},
-    CompletionContext, CompletionItem, CompletionItemKind, CompletionRelevance,
-    CompletionRelevancePostfixMatch, Completions,
+    patterns::ImmediateLocation, CompletionContext, CompletionItem, CompletionItemKind,
+    CompletionRelevance, CompletionRelevancePostfixMatch, Completions,
 };
 
 pub(crate) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
-    let missing_fields = if let Some(PatternContext { record_pat: Some(record_pat), .. }) =
-        &ctx.pattern_ctx
-    {
-        ctx.sema.record_pattern_missing_fields(record_pat)
-    } else if let Some(NameRefContext { record_expr: Some((record_expr, _)), .. }) =
-        ctx.nameref_ctx()
-    {
-        let ty = ctx.sema.type_of_expr(&Expr::RecordExpr(record_expr.clone()));
-
-        if let Some(hir::Adt::Union(un)) = ty.as_ref().and_then(|t| t.original.as_adt()) {
-            // ctx.sema.record_literal_missing_fields will always return
-            // an empty Vec on a union literal. This is normally
-            // reasonable, but here we'd like to present the full list
-            // of fields if the literal is empty.
-            let were_fields_specified =
-                record_expr.record_expr_field_list().and_then(|fl| fl.fields().next()).is_some();
-
-            match were_fields_specified {
-                false => un.fields(ctx.db).into_iter().map(|f| (f, f.ty(ctx.db))).collect(),
-                true => vec![],
+    let missing_fields = match &ctx.completion_location {
+        Some(
+            ImmediateLocation::RecordExpr(record_expr)
+            | ImmediateLocation::RecordExprUpdate(record_expr),
+        ) => {
+            let ty = ctx.sema.type_of_expr(&Expr::RecordExpr(record_expr.clone()));
+
+            if let Some(hir::Adt::Union(un)) = ty.as_ref().and_then(|t| t.original.as_adt()) {
+                // ctx.sema.record_literal_missing_fields will always return
+                // an empty Vec on a union literal. This is normally
+                // reasonable, but here we'd like to present the full list
+                // of fields if the literal is empty.
+                let were_fields_specified = record_expr
+                    .record_expr_field_list()
+                    .and_then(|fl| fl.fields().next())
+                    .is_some();
+
+                match were_fields_specified {
+                    false => un.fields(ctx.db).into_iter().map(|f| (f, f.ty(ctx.db))).collect(),
+                    true => vec![],
+                }
+            } else {
+                let missing_fields = ctx.sema.record_literal_missing_fields(record_expr);
+
+                let default_trait = ctx.famous_defs().core_default_Default();
+                let impl_default_trait =
+                    default_trait.zip(ty.as_ref()).map_or(false, |(default_trait, ty)| {
+                        ty.original.impls_trait(ctx.db, default_trait, &[])
+                    });
+
+                if impl_default_trait && !missing_fields.is_empty() && ctx.path_qual().is_none() {
+                    let completion_text = "..Default::default()";
+                    let mut item =
+                        CompletionItem::new(SymbolKind::Field, ctx.source_range(), completion_text);
+                    let completion_text =
+                        completion_text.strip_prefix(ctx.token.text()).unwrap_or(completion_text);
+                    item.insert_text(completion_text).set_relevance(CompletionRelevance {
+                        postfix_match: Some(CompletionRelevancePostfixMatch::Exact),
+                        ..Default::default()
+                    });
+                    item.add_to(acc);
+                }
+                if ctx.previous_token_is(T![.]) {
+                    let mut item =
+                        CompletionItem::new(CompletionItemKind::Snippet, ctx.source_range(), "..");
+                    item.insert_text(".");
+                    item.add_to(acc);
+                    return None;
+                }
+                missing_fields
             }
-        } else {
-            let missing_fields = ctx.sema.record_literal_missing_fields(record_expr);
-
-            let default_trait = ctx.famous_defs().core_default_Default();
-            let impl_default_trait =
-                default_trait.zip(ty.as_ref()).map_or(false, |(default_trait, ty)| {
-                    ty.original.impls_trait(ctx.db, default_trait, &[])
-                });
-
-            if impl_default_trait && !missing_fields.is_empty() && ctx.path_qual().is_none() {
-                let completion_text = "..Default::default()";
-                let mut item =
-                    CompletionItem::new(SymbolKind::Field, ctx.source_range(), completion_text);
-                let completion_text =
-                    completion_text.strip_prefix(ctx.token.text()).unwrap_or(completion_text);
-                item.insert_text(completion_text).set_relevance(CompletionRelevance {
-                    postfix_match: Some(CompletionRelevancePostfixMatch::Exact),
-                    ..Default::default()
-                });
-                item.add_to(acc);
-            }
-            if ctx.previous_token_is(T![.]) {
-                let mut item =
-                    CompletionItem::new(CompletionItemKind::Snippet, ctx.source_range(), "..");
-                item.insert_text(".");
-                item.add_to(acc);
-                return None;
-            }
-            missing_fields
         }
-    } else {
-        return None;
+        Some(ImmediateLocation::RecordPat(record_pat)) => {
+            ctx.sema.record_pattern_missing_fields(record_pat)
+        }
+        _ => return None,
     };
 
     for (field, ty) in missing_fields {
diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs
index 4530d88af8..e51ae0dc3b 100644
--- a/crates/ide-completion/src/context.rs
+++ b/crates/ide-completion/src/context.rs
@@ -48,6 +48,7 @@ pub(super) enum PathKind {
     Expr {
         in_block_expr: bool,
         in_loop_body: bool,
+        in_functional_update: bool,
     },
     Type,
     Attr {
@@ -114,8 +115,6 @@ pub(super) struct PatternContext {
     pub(super) parent_pat: Option<ast::Pat>,
     pub(super) ref_token: Option<SyntaxToken>,
     pub(super) mut_token: Option<SyntaxToken>,
-    /// The record pattern this name or ref is a field of
-    pub(super) record_pat: Option<ast::RecordPat>,
 }
 
 #[derive(Debug)]
@@ -167,11 +166,8 @@ pub(super) enum NameKind {
 pub(super) struct NameRefContext {
     /// NameRef syntax in the original file
     pub(super) nameref: Option<ast::NameRef>,
-    // FIXME: these fields are actually disjoint -> enum
     pub(super) dot_access: Option<DotAccess>,
     pub(super) path_ctx: Option<PathCompletionCtx>,
-    /// The record expression this nameref is a field of
-    pub(super) record_expr: Option<(ast::RecordExpr, bool)>,
 }
 
 #[derive(Debug)]
@@ -190,20 +186,15 @@ pub(super) enum IdentContext {
 }
 
 #[derive(Debug)]
-pub(super) struct DotAccess {
-    pub(super) receiver: Option<ast::Expr>,
-    pub(super) receiver_ty: Option<TypeInfo>,
-    pub(super) kind: DotAccessKind,
-}
-
-#[derive(Debug)]
-pub(super) enum DotAccessKind {
+pub(super) enum DotAccess {
     Field {
+        receiver: Option<ast::Expr>,
         /// True if the receiver is an integer and there is no ident in the original file after it yet
         /// like `0.$0`
         receiver_is_ambiguous_float_literal: bool,
     },
     Method {
+        receiver: Option<ast::Expr>,
         has_parens: bool,
     },
 }
@@ -307,9 +298,11 @@ impl<'a> CompletionContext<'a> {
 
     pub(crate) fn dot_receiver(&self) -> Option<&ast::Expr> {
         match self.nameref_ctx() {
-            Some(NameRefContext { dot_access: Some(DotAccess { receiver, .. }), .. }) => {
-                receiver.as_ref()
-            }
+            Some(NameRefContext {
+                dot_access:
+                    Some(DotAccess::Method { receiver, .. } | DotAccess::Field { receiver, .. }),
+                ..
+            }) => receiver.as_ref(),
             _ => None,
         }
     }
@@ -378,15 +371,17 @@ impl<'a> CompletionContext<'a> {
     // FIXME: This shouldn't exist
     pub(crate) fn is_path_disallowed(&self) -> bool {
         self.previous_token_is(T![unsafe])
-            || matches!(self.prev_sibling, Some(ImmediatePrevSibling::Visibility))
             || matches!(
-                self.name_ctx(),
-                Some(NameContext { kind: NameKind::Module(_) | NameKind::Rename, .. })
+                self.prev_sibling,
+                Some(ImmediatePrevSibling::Attribute | ImmediatePrevSibling::Visibility)
             )
-            || matches!(self.pattern_ctx, Some(PatternContext { record_pat: Some(_), .. }))
             || matches!(
-                self.nameref_ctx(),
-                Some(NameRefContext { record_expr: Some((_, false)), .. })
+                self.completion_location,
+                Some(ImmediateLocation::RecordPat(_) | ImmediateLocation::RecordExpr(_))
+            )
+            || matches!(
+                self.name_ctx(),
+                Some(NameContext { kind: NameKind::Module(_) | NameKind::Rename, .. })
             )
     }
 
@@ -1028,13 +1023,14 @@ impl<'a> CompletionContext<'a> {
                 ast::Enum(_) => NameKind::Enum,
                 ast::Fn(_) => NameKind::Function,
                 ast::IdentPat(bind_pat) => {
-                    pat_ctx = Some({
-                        let mut pat_ctx = pattern_context_for(original_file, bind_pat.into());
-                        if let Some(record_field) = ast::RecordPatField::for_field_name(&name) {
-                            pat_ctx.record_pat = find_node_in_file_compensated(original_file, &record_field.parent_record_pat());
-                        }
-                        pat_ctx
-                    });
+                    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()));
+                    }
 
                     NameKind::IdentPat
                 },
@@ -1066,33 +1062,7 @@ impl<'a> CompletionContext<'a> {
     ) -> (NameRefContext, Option<PatternContext>) {
         let nameref = find_node_at_offset(&original_file, name_ref.syntax().text_range().start());
 
-        let mut nameref_ctx =
-            NameRefContext { dot_access: None, path_ctx: None, nameref, record_expr: None };
-
-        if let Some(record_field) = ast::RecordExprField::for_field_name(&name_ref) {
-            nameref_ctx.record_expr =
-                find_node_in_file_compensated(original_file, &record_field.parent_record_lit())
-                    .zip(Some(false));
-            return (nameref_ctx, None);
-        }
-        if let Some(record_field) = ast::RecordPatField::for_field_name_ref(&name_ref) {
-            let pat_ctx =
-                pattern_context_for(original_file, record_field.parent_record_pat().clone().into());
-            return (
-                nameref_ctx,
-                Some(PatternContext {
-                    param_ctx: None,
-                    has_type_ascription: false,
-                    ref_token: None,
-                    mut_token: None,
-                    record_pat: find_node_in_file_compensated(
-                        original_file,
-                        &record_field.parent_record_pat(),
-                    ),
-                    ..pat_ctx
-                }),
-            );
-        }
+        let mut nameref_ctx = NameRefContext { dot_access: None, path_ctx: None, nameref };
 
         let segment = match_ast! {
             match parent {
@@ -1106,20 +1076,16 @@ impl<'a> CompletionContext<'a> {
                         },
                         _ => false,
                     };
-                    nameref_ctx.dot_access = Some(DotAccess {
-                        receiver_ty: receiver.as_ref().and_then(|it| sema.type_of_expr(it)),
-                        kind: DotAccessKind::Field { receiver_is_ambiguous_float_literal },
-                        receiver
-                    });
+                    nameref_ctx.dot_access = Some(DotAccess::Field { receiver, receiver_is_ambiguous_float_literal });
                     return (nameref_ctx, None);
                 },
                 ast::MethodCallExpr(method) => {
-                    let receiver = find_in_original_file(method.receiver(), original_file);
-                    nameref_ctx.dot_access = Some(DotAccess {
-                        receiver_ty: receiver.as_ref().and_then(|it| sema.type_of_expr(it)),
-                        kind: DotAccessKind::Method { has_parens: method.arg_list().map_or(false, |it| it.l_paren_token().is_some()) },
-                        receiver
-                    });
+                    nameref_ctx.dot_access = Some(
+                        DotAccess::Method {
+                            receiver: find_in_original_file(method.receiver(), original_file),
+                            has_parens: method.arg_list().map_or(false, |it| it.l_paren_token().is_some())
+                        }
+                    );
                     return (nameref_ctx, None);
                 },
                 _ => return (nameref_ctx, None),
@@ -1145,11 +1111,8 @@ impl<'a> CompletionContext<'a> {
                 })
                 .unwrap_or(false)
         };
-        let mut fill_record_expr = |syn: &SyntaxNode| {
-            if let Some(record_expr) = syn.ancestors().nth(2).and_then(ast::RecordExpr::cast) {
-                nameref_ctx.record_expr =
-                    find_node_in_file_compensated(original_file, &record_expr).zip(Some(true));
-            }
+        let is_in_func_update = |it: &SyntaxNode| {
+            it.parent().map_or(false, |it| ast::RecordExprFieldList::can_cast(it.kind()))
         };
 
         let kind = path.syntax().ancestors().find_map(|it| {
@@ -1158,12 +1121,11 @@ impl<'a> CompletionContext<'a> {
                 match it {
                     ast::PathType(_) => Some(PathKind::Type),
                     ast::PathExpr(it) => {
-                        fill_record_expr(it.syntax());
-
                         path_ctx.has_call_parens = it.syntax().parent().map_or(false, |it| ast::CallExpr::can_cast(it.kind()));
                         let in_block_expr = is_in_block(it.syntax());
                         let in_loop_body = is_in_loop_body(it.syntax());
-                        Some(PathKind::Expr { in_block_expr, in_loop_body })
+                        let in_functional_update = is_in_func_update(it.syntax());
+                        Some(PathKind::Expr { in_block_expr, in_loop_body, in_functional_update })
                     },
                     ast::TupleStructPat(it) => {
                         path_ctx.has_call_parens = true;
@@ -1197,8 +1159,8 @@ impl<'a> CompletionContext<'a> {
                                return Some(parent.and_then(ast::MacroExpr::cast).map(|it| {
                                     let in_loop_body = is_in_loop_body(it.syntax());
                                     let in_block_expr = is_in_block(it.syntax());
-                                    fill_record_expr(it.syntax());
-                                    PathKind::Expr { in_block_expr, in_loop_body }
+                                    let in_functional_update = is_in_func_update(it.syntax());
+                                    PathKind::Expr { in_block_expr, in_loop_body, in_functional_update }
                                 }));
                             },
                         }
@@ -1333,7 +1295,6 @@ fn pattern_context_for(original_file: &SyntaxNode, pat: ast::Pat) -> PatternCont
         parent_pat: pat.syntax().parent().and_then(ast::Pat::cast),
         mut_token,
         ref_token,
-        record_pat: None,
     }
 }
 
diff --git a/crates/ide-completion/src/patterns.rs b/crates/ide-completion/src/patterns.rs
index 0e146df712..07b2ac4297 100644
--- a/crates/ide-completion/src/patterns.rs
+++ b/crates/ide-completion/src/patterns.rs
@@ -24,6 +24,7 @@ pub(crate) enum ImmediatePrevSibling {
     TraitDefName,
     ImplDefType,
     Visibility,
+    Attribute,
 }
 
 #[derive(Clone, Debug, PartialEq, Eq)]
@@ -53,6 +54,19 @@ pub(crate) enum ImmediateLocation {
     // Only set from a type arg
     /// Original file ast node
     GenericArgList(ast::GenericArgList),
+    /// The record expr of the field name we are completing
+    ///
+    /// Original file ast node
+    RecordExpr(ast::RecordExpr),
+    /// The record expr of the functional update syntax we are completing
+    ///
+    /// Original file ast node
+    RecordExprUpdate(ast::RecordExpr),
+    /// 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),
 }
 
 pub(crate) fn determine_prev_sibling(name_like: &ast::NameLike) -> Option<ImmediatePrevSibling> {
@@ -110,6 +124,7 @@ pub(crate) fn determine_prev_sibling(name_like: &ast::NameLike) -> Option<Immedi
                 } else {
                     return None
             },
+            ast::Attr(_) => ImmediatePrevSibling::Attribute,
             _ => return None,
         }
     };
@@ -123,8 +138,27 @@ pub(crate) fn determine_location(
     name_like: &ast::NameLike,
 ) -> Option<ImmediateLocation> {
     let node = match name_like {
-        ast::NameLike::NameRef(name_ref) => maximize_name_ref(name_ref),
-        ast::NameLike::Name(name) => name.syntax().clone(),
+        ast::NameLike::NameRef(name_ref) => {
+            if ast::RecordExprField::for_field_name(name_ref).is_some() {
+                return sema
+                    .find_node_at_offset_with_macros(original_file, offset)
+                    .map(ImmediateLocation::RecordExpr);
+            }
+            if ast::RecordPatField::for_field_name_ref(name_ref).is_some() {
+                return sema
+                    .find_node_at_offset_with_macros(original_file, offset)
+                    .map(ImmediateLocation::RecordPat);
+            }
+            maximize_name_ref(name_ref)
+        }
+        ast::NameLike::Name(name) => {
+            if ast::RecordPatField::for_field_name(name).is_some() {
+                return sema
+                    .find_node_at_offset_with_macros(original_file, offset)
+                    .map(ImmediateLocation::RecordPat);
+            }
+            name.syntax().clone()
+        }
         ast::NameLike::Lifetime(lt) => lt.syntax().clone(),
     };
 
@@ -167,6 +201,9 @@ pub(crate) fn determine_location(
             ast::SourceFile(_) => ImmediateLocation::ItemList,
             ast::ItemList(_) => ImmediateLocation::ItemList,
             ast::RefExpr(_) => ImmediateLocation::RefExpr,
+            ast::RecordExprFieldList(_) => sema
+                .find_node_at_offset_with_macros(original_file, offset)
+                .map(ImmediateLocation::RecordExprUpdate)?,
             ast::TupleField(_) => ImmediateLocation::TupleField,
             ast::TupleFieldList(_) => ImmediateLocation::TupleField,
             ast::TypeBound(_) => ImmediateLocation::TypeBound,
@@ -447,4 +484,9 @@ mod tests {
     fn test_vis_prev_sibling() {
         check_prev_sibling(r"pub w$0", ImmediatePrevSibling::Visibility);
     }
+
+    #[test]
+    fn test_attr_prev_sibling() {
+        check_prev_sibling(r"#[attr] w$0", ImmediatePrevSibling::Attribute);
+    }
 }
diff --git a/crates/ide-completion/src/render/function.rs b/crates/ide-completion/src/render/function.rs
index 5e1fbfa4a2..c47696bc41 100644
--- a/crates/ide-completion/src/render/function.rs
+++ b/crates/ide-completion/src/render/function.rs
@@ -7,9 +7,7 @@ use stdx::{format_to, to_lower_snake_case};
 use syntax::SmolStr;
 
 use crate::{
-    context::{
-        CompletionContext, DotAccess, DotAccessKind, NameRefContext, PathCompletionCtx, PathKind,
-    },
+    context::{CompletionContext, DotAccess, NameRefContext, PathCompletionCtx, PathKind},
     item::{Builder, CompletionItem, CompletionItemKind, CompletionRelevance},
     render::{compute_exact_name_match, compute_ref_match, compute_type_match, RenderContext},
     CallableSnippets,
@@ -211,10 +209,7 @@ fn should_add_parens(ctx: &CompletionContext) -> bool {
 
     if matches!(
         ctx.nameref_ctx(),
-        Some(NameRefContext {
-            dot_access: Some(DotAccess { kind: DotAccessKind::Method { has_parens: true }, .. }),
-            ..
-        })
+        Some(NameRefContext { dot_access: Some(DotAccess::Method { has_parens: true, .. }), .. })
     ) {
         return false;
     }
diff --git a/crates/ide-db/src/syntax_helpers/insert_whitespace_into_node.rs b/crates/ide-db/src/syntax_helpers/insert_whitespace_into_node.rs
index b5db8c1967..16e609b1a7 100644
--- a/crates/ide-db/src/syntax_helpers/insert_whitespace_into_node.rs
+++ b/crates/ide-db/src/syntax_helpers/insert_whitespace_into_node.rs
@@ -57,7 +57,7 @@ pub fn insert_ws_into(syn: SyntaxNode) -> SyntaxNode {
             |f: fn(SyntaxKind) -> bool, default| -> bool { last.map(f).unwrap_or(default) };
 
         match tok.kind() {
-            k if is_text(k) && is_next(|it| !it.is_punct() || it == UNDERSCORE, false) => {
+            k if is_text(k) && is_next(|it| !it.is_punct(), true) => {
                 mods.push(do_ws(after, tok));
             }
             L_CURLY if is_next(|it| it != R_CURLY, true) => {
@@ -118,5 +118,5 @@ pub fn insert_ws_into(syn: SyntaxNode) -> SyntaxNode {
 }
 
 fn is_text(k: SyntaxKind) -> bool {
-    k.is_keyword() || k.is_literal() || k == IDENT || k == UNDERSCORE
+    k.is_keyword() || k.is_literal() || k == IDENT
 }
diff --git a/crates/ide/src/expand_macro.rs b/crates/ide/src/expand_macro.rs
index a578aba01f..e4061016e6 100644
--- a/crates/ide/src/expand_macro.rs
+++ b/crates/ide/src/expand_macro.rs
@@ -237,24 +237,6 @@ fn main() {
         );
     }
 
-    #[test]
-    fn macro_expand_underscore() {
-        check(
-            r#"
-macro_rules! bar {
-    ($i:tt) => { for _ in 0..$i {} }
-}
-fn main() {
-    ba$0r!(42);
-}
-"#,
-            expect![[r#"
-                bar
-                for _ in 0..42{}
-                "#]],
-        );
-    }
-
     #[test]
     fn macro_expand_recursive_expansion() {
         check(
@@ -403,7 +385,7 @@ fn main() {
 "#,
             expect![[r#"
                 foo
-                0"#]],
+                0 "#]],
         );
     }
 
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs
index 1d22c43317..f6754e44f3 100644
--- a/crates/ide/src/syntax_highlighting.rs
+++ b/crates/ide/src/syntax_highlighting.rs
@@ -206,8 +206,9 @@ fn traverse(
     let is_unlinked = sema.to_module_def(file_id).is_none();
     let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default();
 
-    let mut tt_level = 0;
-    let mut attr_or_derive_item = None;
+    let mut current_macro_call: Option<ast::MacroCall> = None;
+    let mut current_attr_call = None;
+    let mut current_derive_call = None;
     let mut current_macro: Option<ast::Macro> = None;
     let mut macro_highlighter = MacroHighlighter::default();
     let mut inside_attribute = false;
@@ -215,10 +216,8 @@ fn traverse(
     // Walk all nodes, keeping track of whether we are inside a macro or not.
     // If in macro, expand it first and highlight the expanded code.
     for event in root.preorder_with_tokens() {
-        use WalkEvent::{Enter, Leave};
-
         let range = match &event {
-            Enter(it) | Leave(it) => it.text_range(),
+            WalkEvent::Enter(it) | WalkEvent::Leave(it) => it.text_range(),
         };
 
         // Element outside of the viewport, no need to highlight
@@ -228,84 +227,78 @@ fn traverse(
 
         // set macro and attribute highlighting states
         match event.clone() {
-            Enter(NodeOrToken::Node(node)) if ast::TokenTree::can_cast(node.kind()) => {
-                tt_level += 1;
-            }
-            Leave(NodeOrToken::Node(node)) if ast::TokenTree::can_cast(node.kind()) => {
-                tt_level -= 1;
-            }
-            Enter(NodeOrToken::Node(node)) if ast::Attr::can_cast(node.kind()) => {
-                inside_attribute = true
-            }
-            Leave(NodeOrToken::Node(node)) if ast::Attr::can_cast(node.kind()) => {
-                inside_attribute = false
-            }
-
-            Enter(NodeOrToken::Node(node)) if ast::Item::can_cast(node.kind()) => {
-                match ast::Item::cast(node.clone()) {
-                    Some(ast::Item::MacroRules(mac)) => {
-                        macro_highlighter.init();
-                        current_macro = Some(mac.into());
-                        continue;
-                    }
-                    Some(ast::Item::MacroDef(mac)) => {
-                        macro_highlighter.init();
-                        current_macro = Some(mac.into());
-                        continue;
+            WalkEvent::Enter(NodeOrToken::Node(node)) => match ast::Item::cast(node.clone()) {
+                Some(ast::Item::MacroCall(mcall)) => {
+                    current_macro_call = Some(mcall);
+                    continue;
+                }
+                Some(ast::Item::MacroRules(mac)) => {
+                    macro_highlighter.init();
+                    current_macro = Some(mac.into());
+                    continue;
+                }
+                Some(ast::Item::MacroDef(mac)) => {
+                    macro_highlighter.init();
+                    current_macro = Some(mac.into());
+                    continue;
+                }
+                Some(item) => {
+                    if matches!(node.kind(), FN | CONST | STATIC) {
+                        bindings_shadow_count.clear();
                     }
-                    Some(item) => {
-                        if matches!(node.kind(), FN | CONST | STATIC) {
-                            bindings_shadow_count.clear();
-                        }
 
-                        if attr_or_derive_item.is_none() {
-                            if sema.is_attr_macro_call(&item) {
-                                attr_or_derive_item = Some(item);
-                            } else {
-                                let adt = match item {
-                                    ast::Item::Enum(it) => Some(ast::Adt::Enum(it)),
-                                    ast::Item::Struct(it) => Some(ast::Adt::Struct(it)),
-                                    ast::Item::Union(it) => Some(ast::Adt::Union(it)),
-                                    _ => None,
-                                };
-                                match adt {
-                                    Some(adt) if sema.is_derive_annotated(&adt) => {
-                                        attr_or_derive_item = Some(ast::Item::from(adt));
-                                    }
-                                    _ => (),
-                                }
+                    if sema.is_attr_macro_call(&item) {
+                        current_attr_call = Some(item);
+                    } else if current_attr_call.is_none() {
+                        let adt = match item {
+                            ast::Item::Enum(it) => Some(ast::Adt::Enum(it)),
+                            ast::Item::Struct(it) => Some(ast::Adt::Struct(it)),
+                            ast::Item::Union(it) => Some(ast::Adt::Union(it)),
+                            _ => None,
+                        };
+                        match adt {
+                            Some(adt) if sema.is_derive_annotated(&adt) => {
+                                current_derive_call = Some(ast::Item::from(adt));
                             }
+                            _ => (),
                         }
                     }
-                    _ => (),
                 }
-            }
-            Leave(NodeOrToken::Node(node)) if ast::Item::can_cast(node.kind()) => {
-                match ast::Item::cast(node.clone()) {
-                    Some(ast::Item::MacroRules(mac)) => {
-                        assert_eq!(current_macro, Some(mac.into()));
-                        current_macro = None;
-                        macro_highlighter = MacroHighlighter::default();
-                    }
-                    Some(ast::Item::MacroDef(mac)) => {
-                        assert_eq!(current_macro, Some(mac.into()));
-                        current_macro = None;
-                        macro_highlighter = MacroHighlighter::default();
-                    }
-                    Some(item) if attr_or_derive_item.as_ref().map_or(false, |it| *it == item) => {
-                        attr_or_derive_item = None;
-                    }
-                    _ => (),
+                None if ast::Attr::can_cast(node.kind()) => inside_attribute = true,
+                _ => (),
+            },
+            WalkEvent::Leave(NodeOrToken::Node(node)) => match ast::Item::cast(node.clone()) {
+                Some(ast::Item::MacroCall(mcall)) => {
+                    assert_eq!(current_macro_call, Some(mcall));
+                    current_macro_call = None;
                 }
-            }
+                Some(ast::Item::MacroRules(mac)) => {
+                    assert_eq!(current_macro, Some(mac.into()));
+                    current_macro = None;
+                    macro_highlighter = MacroHighlighter::default();
+                }
+                Some(ast::Item::MacroDef(mac)) => {
+                    assert_eq!(current_macro, Some(mac.into()));
+                    current_macro = None;
+                    macro_highlighter = MacroHighlighter::default();
+                }
+                Some(item) if current_attr_call.as_ref().map_or(false, |it| *it == item) => {
+                    current_attr_call = None;
+                }
+                Some(item) if current_derive_call.as_ref().map_or(false, |it| *it == item) => {
+                    current_derive_call = None;
+                }
+                None if ast::Attr::can_cast(node.kind()) => inside_attribute = false,
+                _ => (),
+            },
             _ => (),
         }
 
         let element = match event {
-            Enter(NodeOrToken::Token(tok)) if tok.kind() == WHITESPACE => continue,
-            Enter(it) => it,
-            Leave(NodeOrToken::Token(_)) => continue,
-            Leave(NodeOrToken::Node(node)) => {
+            WalkEvent::Enter(NodeOrToken::Token(tok)) if tok.kind() == WHITESPACE => continue,
+            WalkEvent::Enter(it) => it,
+            WalkEvent::Leave(NodeOrToken::Token(_)) => continue,
+            WalkEvent::Leave(NodeOrToken::Node(node)) => {
                 // Doc comment highlighting injection, we do this when leaving the node
                 // so that we overwrite the highlighting of the doc comment itself.
                 inject::doc_comment(hl, sema, InFile::new(file_id.into(), &node));
@@ -330,25 +323,38 @@ fn traverse(
 
         // Descending tokens into macros is expensive even if no descending occurs, so make sure
         // that we actually are in a position where descending is possible.
-        let in_macro = tt_level > 0 || attr_or_derive_item.is_some();
+        let in_macro = current_macro_call.is_some()
+            || current_derive_call.is_some()
+            || current_attr_call.is_some();
         let descended_element = if in_macro {
             // Attempt to descend tokens into macro-calls.
             match element {
                 NodeOrToken::Token(token) if token.kind() != COMMENT => {
-                    let token = sema.descend_into_macros_single(token);
-                    match token.parent().and_then(ast::NameLike::cast) {
-                        // Remap the token into the wrapping single token nodes
-                        // FIXME: if the node doesn't resolve, we also won't do token based highlighting!
-                        Some(parent) => match (token.kind(), parent.syntax().kind()) {
-                            (T![self] | T![ident], NAME | NAME_REF) => NodeOrToken::Node(parent),
-                            (T![self] | T![super] | T![crate] | T![Self], NAME_REF) => {
-                                NodeOrToken::Node(parent)
-                            }
-                            (INT_NUMBER, NAME_REF) => NodeOrToken::Node(parent),
-                            (LIFETIME_IDENT, LIFETIME) => NodeOrToken::Node(parent),
-                            _ => NodeOrToken::Token(token),
-                        },
-                        None => NodeOrToken::Token(token),
+                    // For function-like macro calls and derive attributes, only attempt to descend if
+                    // we are inside their token-trees.
+                    let in_tt = current_attr_call.is_some()
+                        || token.parent().as_ref().map(SyntaxNode::kind) == Some(TOKEN_TREE);
+
+                    if in_tt {
+                        let token = sema.descend_into_macros_single(token);
+                        match token.parent().and_then(ast::NameLike::cast) {
+                            // Remap the token into the wrapping single token nodes
+                            // FIXME: if the node doesn't resolve, we also won't do token based highlighting!
+                            Some(parent) => match (token.kind(), parent.syntax().kind()) {
+                                (T![self] | T![ident], NAME | NAME_REF) => {
+                                    NodeOrToken::Node(parent)
+                                }
+                                (T![self] | T![super] | T![crate] | T![Self], NAME_REF) => {
+                                    NodeOrToken::Node(parent)
+                                }
+                                (INT_NUMBER, NAME_REF) => NodeOrToken::Node(parent),
+                                (LIFETIME_IDENT, LIFETIME) => NodeOrToken::Node(parent),
+                                _ => NodeOrToken::Token(token),
+                            },
+                            None => NodeOrToken::Token(token),
+                        }
+                    } else {
+                        NodeOrToken::Token(token)
                     }
                 }
                 e => e,
diff --git a/crates/project-model/src/build_scripts.rs b/crates/project-model/src/build_scripts.rs
index d4792633ef..e0e29552ac 100644
--- a/crates/project-model/src/build_scripts.rs
+++ b/crates/project-model/src/build_scripts.rs
@@ -6,12 +6,7 @@
 //! This module implements this second part. We use "build script" terminology
 //! here, but it covers procedural macros as well.
 
-use std::{
-    cell::RefCell,
-    io,
-    path::PathBuf,
-    process::{Command, Stdio},
-};
+use std::{cell::RefCell, io, path::PathBuf, process::Command};
 
 use cargo_metadata::{camino::Utf8Path, Message};
 use la_arena::ArenaMap;
@@ -95,8 +90,6 @@ impl WorkspaceBuildScripts {
 
         cmd.current_dir(workspace.workspace_root());
 
-        cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null());
-
         let mut res = WorkspaceBuildScripts::default();
         // NB: Cargo.toml could have been modified between `cargo metadata` and
         // `cargo check`. We shouldn't assume that package ids we see here are
@@ -114,6 +107,8 @@ impl WorkspaceBuildScripts {
             e.push_str(err);
             e.push('\n');
         };
+
+        tracing::info!("Running build scripts: {:?}", cmd);
         let output = stdx::process::streaming_output(
             cmd,
             &mut |line| {
diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs
index 2a41d0a701..b7bf45c04c 100644
--- a/crates/syntax/src/ast/node_ext.rs
+++ b/crates/syntax/src/ast/node_ext.rs
@@ -510,10 +510,6 @@ impl ast::RecordPatField {
         }
     }
 
-    pub fn parent_record_pat(&self) -> ast::RecordPat {
-        self.syntax().ancestors().find_map(ast::RecordPat::cast).unwrap()
-    }
-
     /// Deals with field init shorthand
     pub fn field_name(&self) -> Option<NameOrNameRef> {
         if let Some(name_ref) = self.name_ref() {
diff --git a/editors/code/package.json b/editors/code/package.json
index 18fa7f2f4c..f46c7ea92d 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -308,12 +308,6 @@
                         "null",
                         "object"
                     ],
-                    "additionalProperties": {
-                        "type": [
-                            "string",
-                            "number"
-                        ]
-                    },
                     "default": null,
                     "markdownDescription": "Extra environment variables that will be passed to the rust-analyzer executable. Useful for passing e.g. `RA_LOG` for debugging."
                 },
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index d07e47c8de..592ebe0ce3 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -100,12 +100,8 @@ export class Config {
     get serverPath() {
         return this.get<null | string>("server.path") ?? this.get<null | string>("serverPath");
     }
-    get serverExtraEnv(): Env {
-        const extraEnv =
-            this.get<{ [key: string]: string | number } | null>("server.extraEnv") ?? {};
-        return Object.fromEntries(
-            Object.entries(extraEnv).map(([k, v]) => [k, typeof v !== "string" ? v.toString() : v])
-        );
+    get serverExtraEnv() {
+        return this.get<Env | null>("server.extraEnv") ?? {};
     }
     get traceExtension() {
         return this.get<boolean>("trace.extension");


Test patch:
diff --git a/crates/ide-completion/src/tests/item_list.rs b/crates/ide-completion/src/tests/item_list.rs
index d03a4fd5cd..f320812936 100644
--- a/crates/ide-completion/src/tests/item_list.rs
+++ b/crates/ide-completion/src/tests/item_list.rs
@@ -78,10 +78,7 @@ fn in_item_list_after_attr() {
     check(
         r#"#[attr] $0"#,
         expect![[r#"
-            ma makro!(…)           macro_rules! makro
-            md module
             kw const
-            kw crate::
             kw enum
             kw extern
             kw fn
@@ -90,10 +87,8 @@ fn in_item_list_after_attr() {
             kw pub
             kw pub(crate)
             kw pub(super)
-            kw self::
             kw static
             kw struct
-            kw super::
             kw trait
             kw type
             kw union
@@ -189,16 +184,11 @@ fn in_impl_assoc_item_list_after_attr() {
     check(
         r#"impl Struct { #[attr] $0 }"#,
         expect![[r#"
-            ma makro!(…)  macro_rules! makro
-            md module
             kw const
-            kw crate::
             kw fn
             kw pub
             kw pub(crate)
             kw pub(super)
-            kw self::
-            kw super::
             kw type
             kw unsafe
         "#]],
diff --git a/crates/ide-completion/src/tests/pattern.rs b/crates/ide-completion/src/tests/pattern.rs
index bf88070769..3ecb2f1637 100644
--- a/crates/ide-completion/src/tests/pattern.rs
+++ b/crates/ide-completion/src/tests/pattern.rs
@@ -338,10 +338,7 @@ struct Foo { bar: Bar }
 struct Bar(u32);
 fn outer(Foo { bar$0 }: Foo) {}
 "#,
-        expect![[r#"
-            kw mut
-            kw ref
-        "#]],
+        expect![[r#""#]],
     )
 }
 
diff --git a/crates/ide-completion/src/tests/record.rs b/crates/ide-completion/src/tests/record.rs
index 9e442dbbc5..2c49761748 100644
--- a/crates/ide-completion/src/tests/record.rs
+++ b/crates/ide-completion/src/tests/record.rs
@@ -41,8 +41,6 @@ fn foo(s: Struct) {
 "#,
         expect![[r#"
             fd bar u32
-            kw mut
-            kw ref
         "#]],
     );
 }
@@ -60,8 +58,6 @@ fn foo(e: Enum) {
 "#,
         expect![[r#"
             fd bar u32
-            kw mut
-            kw ref
         "#]],
     );
 }
@@ -97,8 +93,6 @@ fn foo(f: Struct) {
 ",
         expect![[r#"
             fd field u32
-            kw mut
-            kw ref
         "#]],
     );
 }