about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/tools/rust-analyzer/Cargo.lock1
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/metavar_expr.rs147
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs24
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs43
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/per_ns.rs20
-rw-r--r--src/tools/rust-analyzer/crates/hir-expand/src/cfg_process.rs21
-rw-r--r--src/tools/rust-analyzer/crates/hir-expand/src/lib.rs1
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/object_safety.rs2
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/object_safety/tests.rs13
-rw-r--r--src/tools/rust-analyzer/crates/hir/src/semantics.rs13
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute.rs5
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs53
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/context.rs64
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs70
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs22
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs22
-rw-r--r--src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs79
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/goto_definition.rs32
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/hover/render.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/hover/tests.rs28
-rw-r--r--src/tools/rust-analyzer/crates/mbe/Cargo.toml1
-rw-r--r--src/tools/rust-analyzer/crates/mbe/src/benchmark.rs6
-rw-r--r--src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs14
-rw-r--r--src/tools/rust-analyzer/crates/mbe/src/expander/transcriber.rs80
-rw-r--r--src/tools/rust-analyzer/crates/mbe/src/lib.rs5
-rw-r--r--src/tools/rust-analyzer/crates/mbe/src/parser.rs50
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs6
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs2
-rw-r--r--src/tools/rust-analyzer/crates/tt/src/iter.rs7
29 files changed, 756 insertions, 77 deletions
diff --git a/src/tools/rust-analyzer/Cargo.lock b/src/tools/rust-analyzer/Cargo.lock
index 6df73c2fd1b..5df48778e3d 100644
--- a/src/tools/rust-analyzer/Cargo.lock
+++ b/src/tools/rust-analyzer/Cargo.lock
@@ -1047,6 +1047,7 @@ dependencies = [
  "expect-test",
  "intern",
  "parser",
+ "ra-ap-rustc_lexer",
  "rustc-hash",
  "smallvec",
  "span",
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/metavar_expr.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/metavar_expr.rs
index bf701198387..1430e2a9a99 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/metavar_expr.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/metavar_expr.rs
@@ -311,3 +311,150 @@ fn test() {
 "#]],
     );
 }
+
+#[test]
+fn concat() {
+    // FIXME: Should this error? rustc currently accepts it.
+    check(
+        r#"
+macro_rules! m {
+    ( $a:ident, $b:literal ) => {
+        let ${concat($a, _, "123", _foo, $b, _, 123)};
+    };
+}
+
+fn test() {
+    m!( abc, 456 );
+    m!( def, "hello" );
+}
+"#,
+        expect![[r#"
+macro_rules! m {
+    ( $a:ident, $b:literal ) => {
+        let ${concat($a, _, "123", _foo, $b, _, 123)};
+    };
+}
+
+fn test() {
+    let abc_123_foo456_123;;
+    let def_123_foohello_123;;
+}
+"#]],
+    );
+}
+
+#[test]
+fn concat_less_than_two_elements() {
+    // FIXME: Should this error? rustc currently accepts it.
+    check(
+        r#"
+macro_rules! m {
+    () => {
+        let ${concat(abc)};
+    };
+}
+
+fn test() {
+    m!()
+}
+"#,
+        expect![[r#"
+macro_rules! m {
+    () => {
+        let ${concat(abc)};
+    };
+}
+
+fn test() {
+    /* error: macro definition has parse errors */
+}
+"#]],
+    );
+}
+
+#[test]
+fn concat_invalid_ident() {
+    // FIXME: Should this error? rustc currently accepts it.
+    check(
+        r#"
+macro_rules! m {
+    () => {
+        let ${concat(abc, '"')};
+    };
+}
+
+fn test() {
+    m!()
+}
+"#,
+        expect![[r#"
+macro_rules! m {
+    () => {
+        let ${concat(abc, '"')};
+    };
+}
+
+fn test() {
+    /* error: `${concat(..)}` is not generating a valid identifier */let __ra_concat_dummy;
+}
+"#]],
+    );
+}
+
+#[test]
+fn concat_invalid_fragment() {
+    // FIXME: Should this error? rustc currently accepts it.
+    check(
+        r#"
+macro_rules! m {
+    ( $e:expr ) => {
+        let ${concat(abc, $e)};
+    };
+}
+
+fn test() {
+    m!(())
+}
+"#,
+        expect![[r#"
+macro_rules! m {
+    ( $e:expr ) => {
+        let ${concat(abc, $e)};
+    };
+}
+
+fn test() {
+    /* error: metavariables of `${concat(..)}` must be of type `ident`, `literal` or `tt` */let abc;
+}
+"#]],
+    );
+}
+
+#[test]
+fn concat_repetition() {
+    // FIXME: Should this error? rustc currently accepts it.
+    check(
+        r#"
+macro_rules! m {
+    ( $($i:ident)* ) => {
+        let ${concat(abc, $i)};
+    };
+}
+
+fn test() {
+    m!(a b c)
+}
+"#,
+        expect![[r#"
+macro_rules! m {
+    ( $($i:ident)* ) => {
+        let ${concat(abc, $i)};
+    };
+}
+
+fn test() {
+    /* error: expected simple binding, found nested binding `i` */let abc;
+}
+"#]],
+    );
+}
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs
index 894ef1d73e2..1bbed01443d 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs
@@ -1144,3 +1144,27 @@ mod any {
 "#]],
     );
 }
+
+#[test]
+fn regression_18148() {
+    check(
+        r#"
+macro_rules! m {
+    ( $e:expr ) => {};
+}
+
+fn foo() {
+    m!(r#const);
+}
+"#,
+        expect![[r#"
+macro_rules! m {
+    ( $e:expr ) => {};
+}
+
+fn foo() {
+    ;
+}
+"#]],
+    );
+}
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs
index 96db3db8f0d..e09ef4f205d 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs
@@ -221,7 +221,7 @@ struct DefCollector<'a> {
     deps: FxHashMap<Name, Dependency>,
     glob_imports: FxHashMap<LocalModuleId, Vec<(LocalModuleId, Visibility, UseId)>>,
     unresolved_imports: Vec<ImportDirective>,
-    indeterminate_imports: Vec<ImportDirective>,
+    indeterminate_imports: Vec<(ImportDirective, PerNs)>,
     unresolved_macros: Vec<MacroDirective>,
     mod_dirs: FxHashMap<LocalModuleId, ModDir>,
     cfg_options: &'a CfgOptions,
@@ -415,16 +415,6 @@ impl DefCollector<'_> {
 
         self.resolution_loop();
 
-        // Resolve all indeterminate resolved imports again
-        // As some of the macros will expand newly import shadowing partial resolved imports
-        // FIXME: We maybe could skip this, if we handle the indeterminate imports in `resolve_imports`
-        // correctly
-        let partial_resolved = self.indeterminate_imports.drain(..).map(|directive| {
-            ImportDirective { status: PartialResolvedImport::Unresolved, ..directive }
-        });
-        self.unresolved_imports.extend(partial_resolved);
-        self.resolve_imports();
-
         let unresolved_imports = mem::take(&mut self.unresolved_imports);
         // show unresolved imports in completion, etc
         for directive in &unresolved_imports {
@@ -749,9 +739,9 @@ impl DefCollector<'_> {
             .filter_map(|mut directive| {
                 directive.status = self.resolve_import(directive.module_id, &directive.import);
                 match directive.status {
-                    PartialResolvedImport::Indeterminate(_) => {
+                    PartialResolvedImport::Indeterminate(resolved) => {
                         self.record_resolved_import(&directive);
-                        self.indeterminate_imports.push(directive);
+                        self.indeterminate_imports.push((directive, resolved));
                         res = ReachedFixedPoint::No;
                         None
                     }
@@ -764,6 +754,33 @@ impl DefCollector<'_> {
                 }
             })
             .collect();
+
+        // Resolve all indeterminate resolved imports again
+        // As some of the macros will expand newly import shadowing partial resolved imports
+        // FIXME: We maybe could skip this, if we handle the indeterminate imports in `resolve_imports`
+        // correctly
+        let mut indeterminate_imports = std::mem::take(&mut self.indeterminate_imports);
+        indeterminate_imports.retain_mut(|(directive, partially_resolved)| {
+            let partially_resolved = partially_resolved.availability();
+            directive.status = self.resolve_import(directive.module_id, &directive.import);
+            match directive.status {
+                PartialResolvedImport::Indeterminate(import)
+                    if partially_resolved != import.availability() =>
+                {
+                    self.record_resolved_import(directive);
+                    res = ReachedFixedPoint::No;
+                    false
+                }
+                PartialResolvedImport::Resolved(_) => {
+                    self.record_resolved_import(directive);
+                    res = ReachedFixedPoint::No;
+                    false
+                }
+                _ => true,
+            }
+        });
+        self.indeterminate_imports = indeterminate_imports;
+
         res
     }
 
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/per_ns.rs b/src/tools/rust-analyzer/crates/hir-def/src/per_ns.rs
index 19485c476f7..3f3b98c6b5b 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/per_ns.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/per_ns.rs
@@ -3,6 +3,8 @@
 //!
 //! `PerNs` (per namespace) captures this.
 
+use bitflags::bitflags;
+
 use crate::{
     item_scope::{ImportId, ImportOrExternCrate, ItemInNs},
     visibility::Visibility,
@@ -16,6 +18,16 @@ pub enum Namespace {
     Macros,
 }
 
+bitflags! {
+    /// Describes only the presence/absence of each namespace, without its value.
+    #[derive(Debug, PartialEq, Eq)]
+    pub(crate) struct NsAvailability : u32 {
+        const TYPES = 1 << 0;
+        const VALUES = 1 << 1;
+        const MACROS = 1 << 2;
+    }
+}
+
 #[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
 pub struct PerNs {
     pub types: Option<(ModuleDefId, Visibility, Option<ImportOrExternCrate>)>,
@@ -24,6 +36,14 @@ pub struct PerNs {
 }
 
 impl PerNs {
+    pub(crate) fn availability(&self) -> NsAvailability {
+        let mut result = NsAvailability::empty();
+        result.set(NsAvailability::TYPES, self.types.is_some());
+        result.set(NsAvailability::VALUES, self.values.is_some());
+        result.set(NsAvailability::MACROS, self.macros.is_some());
+        result
+    }
+
     pub fn none() -> PerNs {
         PerNs { types: None, values: None, macros: None }
     }
diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/cfg_process.rs b/src/tools/rust-analyzer/crates/hir-expand/src/cfg_process.rs
index 147cf912da1..01a3103af82 100644
--- a/src/tools/rust-analyzer/crates/hir-expand/src/cfg_process.rs
+++ b/src/tools/rust-analyzer/crates/hir-expand/src/cfg_process.rs
@@ -6,7 +6,7 @@ use cfg::{CfgAtom, CfgExpr};
 use intern::{sym, Symbol};
 use rustc_hash::FxHashSet;
 use syntax::{
-    ast::{self, Attr, HasAttrs, Meta, VariantList},
+    ast::{self, Attr, HasAttrs, Meta, TokenTree, VariantList},
     AstNode, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, T,
 };
 use tracing::{debug, warn};
@@ -17,7 +17,7 @@ fn check_cfg(db: &dyn ExpandDatabase, attr: &Attr, krate: CrateId) -> Option<boo
     if !attr.simple_name().as_deref().map(|v| v == "cfg")? {
         return None;
     }
-    let cfg = parse_from_attr_meta(attr.meta()?)?;
+    let cfg = parse_from_attr_token_tree(&attr.meta()?.token_tree()?)?;
     let enabled = db.crate_graph()[krate].cfg_options.check(&cfg) != Some(false);
     Some(enabled)
 }
@@ -26,7 +26,15 @@ fn check_cfg_attr(db: &dyn ExpandDatabase, attr: &Attr, krate: CrateId) -> Optio
     if !attr.simple_name().as_deref().map(|v| v == "cfg_attr")? {
         return None;
     }
-    let cfg_expr = parse_from_attr_meta(attr.meta()?)?;
+    check_cfg_attr_value(db, &attr.token_tree()?, krate)
+}
+
+pub fn check_cfg_attr_value(
+    db: &dyn ExpandDatabase,
+    attr: &TokenTree,
+    krate: CrateId,
+) -> Option<bool> {
+    let cfg_expr = parse_from_attr_token_tree(attr)?;
     let enabled = db.crate_graph()[krate].cfg_options.check(&cfg_expr) != Some(false);
     Some(enabled)
 }
@@ -238,8 +246,7 @@ pub(crate) fn process_cfg_attrs(
     Some(remove)
 }
 /// Parses a `cfg` attribute from the meta
-fn parse_from_attr_meta(meta: Meta) -> Option<CfgExpr> {
-    let tt = meta.token_tree()?;
+fn parse_from_attr_token_tree(tt: &TokenTree) -> Option<CfgExpr> {
     let mut iter = tt
         .token_trees_and_tokens()
         .filter(is_not_whitespace)
@@ -328,7 +335,7 @@ mod tests {
     use expect_test::{expect, Expect};
     use syntax::{ast::Attr, AstNode, SourceFile};
 
-    use crate::cfg_process::parse_from_attr_meta;
+    use crate::cfg_process::parse_from_attr_token_tree;
 
     fn check_dnf_from_syntax(input: &str, expect: Expect) {
         let parse = SourceFile::parse(input, span::Edition::CURRENT);
@@ -342,7 +349,7 @@ mod tests {
         let node = node.clone_subtree();
         assert_eq!(node.syntax().text_range().start(), 0.into());
 
-        let cfg = parse_from_attr_meta(node.meta().unwrap()).unwrap();
+        let cfg = parse_from_attr_token_tree(&node.meta().unwrap().token_tree().unwrap()).unwrap();
         let actual = format!("#![cfg({})]", DnfExpr::new(&cfg));
         expect.assert_eq(&actual);
     }
diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs
index 7cf4fb5cef2..95380979492 100644
--- a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs
@@ -53,6 +53,7 @@ use crate::{
 };
 
 pub use crate::{
+    cfg_process::check_cfg_attr_value,
     files::{AstId, ErasedAstId, FileRange, InFile, InMacroFile, InRealFile},
     prettify_macro_expansion_::prettify_macro_expansion,
 };
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/object_safety.rs b/src/tools/rust-analyzer/crates/hir-ty/src/object_safety.rs
index 89bf3619a0c..a4c66268555 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/object_safety.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/object_safety.rs
@@ -349,7 +349,7 @@ where
                 ControlFlow::Continue(())
             } else {
                 let generic_params = db.generic_params(item.into());
-                if generic_params.len_type_or_consts() > 0 {
+                if !generic_params.is_empty() {
                     cb(ObjectSafetyViolation::GAT(it))
                 } else {
                     ControlFlow::Continue(())
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/object_safety/tests.rs b/src/tools/rust-analyzer/crates/hir-ty/src/object_safety/tests.rs
index 3dc08c4619e..c2a9117c5be 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/object_safety/tests.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/object_safety/tests.rs
@@ -378,3 +378,16 @@ pub trait Error: core::fmt::Debug + core::fmt::Display {
         [("Error", vec![])],
     );
 }
+
+#[test]
+fn lifetime_gat_is_object_unsafe() {
+    check_object_safety(
+        r#"
+//- minicore: dispatch_from_dyn
+trait Foo {
+    type Bar<'a>;
+}
+"#,
+        [("Foo", vec![ObjectSafetyViolationKind::GAT])],
+    );
+}
diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs
index dcaff044420..fa14b53dbc3 100644
--- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs
+++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs
@@ -386,6 +386,19 @@ impl<'db> SemanticsImpl<'db> {
         Some(node)
     }
 
+    pub fn check_cfg_attr(&self, attr: &ast::TokenTree) -> Option<bool> {
+        let file_id = self.find_file(attr.syntax()).file_id;
+        let krate = match file_id.repr() {
+            HirFileIdRepr::FileId(file_id) => {
+                self.file_to_module_defs(file_id.file_id()).next()?.krate().id
+            }
+            HirFileIdRepr::MacroFile(macro_file) => {
+                self.db.lookup_intern_macro_call(macro_file.macro_call_id).krate
+            }
+        };
+        hir_expand::check_cfg_attr_value(self.db.upcast(), attr, krate)
+    }
+
     /// Expands the macro if it isn't one of the built-in ones that expand to custom syntax or dummy
     /// expansions.
     pub fn expand_allowed_builtins(&self, macro_call: &ast::MacroCall) -> Option<SyntaxNode> {
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute.rs
index 9821fb4a2fa..d0b489c4e83 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute.rs
@@ -56,7 +56,7 @@ pub(crate) fn complete_known_attribute_input(
             &parse_tt_as_comma_sep_paths(tt, ctx.edition)?,
             FEATURES,
         ),
-        "allow" | "warn" | "deny" | "forbid" => {
+        "allow" | "expect" | "deny" | "forbid" | "warn" => {
             let existing_lints = parse_tt_as_comma_sep_paths(tt, ctx.edition)?;
 
             let lints: Vec<Lint> = CLIPPY_LINT_GROUPS
@@ -222,7 +222,7 @@ macro_rules! attrs {
     [@ {} {$($tt:tt)*}] => { &[$($tt)*] as _ };
     // starting matcher
     [$($tt:tt),*] => {
-        attrs!(@ { $($tt)* } { "allow", "cfg", "cfg_attr", "deny", "forbid", "warn" })
+        attrs!(@ { $($tt)* } { "allow", "cfg", "cfg_attr", "deny", "expect", "forbid", "warn" })
     };
 }
 
@@ -303,6 +303,7 @@ const ATTRIBUTES: &[AttrCompletion] = &[
     attr(r#"doc = "…""#, Some("doc"), Some(r#"doc = "${0:docs}""#)),
     attr(r#"doc(alias = "…")"#, Some("docalias"), Some(r#"doc(alias = "${0:docs}")"#)),
     attr(r#"doc(hidden)"#, Some("dochidden"), Some(r#"doc(hidden)"#)),
+    attr("expect(…)", Some("expect"), Some("expect(${0:lint})")),
     attr(
         r#"export_name = "…""#,
         Some("export_name"),
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs
index a632f148936..d3579fd8cc6 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs
@@ -294,6 +294,18 @@ fn include_references(initial_element: &ast::Expr) -> (ast::Expr, ast::Expr) {
 
     let mut new_element_opt = initial_element.clone();
 
+    while let Some(parent_deref_element) =
+        resulting_element.syntax().parent().and_then(ast::PrefixExpr::cast)
+    {
+        if parent_deref_element.op_kind() != Some(ast::UnaryOp::Deref) {
+            break;
+        }
+
+        resulting_element = ast::Expr::from(parent_deref_element);
+
+        new_element_opt = make::expr_prefix(syntax::T![*], new_element_opt);
+    }
+
     if let Some(first_ref_expr) = resulting_element.syntax().parent().and_then(ast::RefExpr::cast) {
         if let Some(expr) = first_ref_expr.expr() {
             resulting_element = expr;
@@ -302,9 +314,10 @@ fn include_references(initial_element: &ast::Expr) -> (ast::Expr, ast::Expr) {
         while let Some(parent_ref_element) =
             resulting_element.syntax().parent().and_then(ast::RefExpr::cast)
         {
+            let exclusive = parent_ref_element.mut_token().is_some();
             resulting_element = ast::Expr::from(parent_ref_element);
 
-            new_element_opt = make::expr_ref(new_element_opt, false);
+            new_element_opt = make::expr_ref(new_element_opt, exclusive);
         }
     } else {
         // If we do not find any ref expressions, restore
@@ -855,4 +868,42 @@ fn test() {
             expect![[r#""#]],
         );
     }
+
+    #[test]
+    fn mut_ref_consuming() {
+        check_edit(
+            "call",
+            r#"
+fn main() {
+    let mut x = &mut 2;
+    &mut x.$0;
+}
+"#,
+            r#"
+fn main() {
+    let mut x = &mut 2;
+    ${1}(&mut x);
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn deref_consuming() {
+        check_edit(
+            "call",
+            r#"
+fn main() {
+    let mut x = &mut 2;
+    &mut *x.$0;
+}
+"#,
+            r#"
+fn main() {
+    let mut x = &mut 2;
+    ${1}(&mut *x);
+}
+"#,
+        );
+    }
 }
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context.rs
index d457ba32bf0..192f1b43fac 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/context.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/context.rs
@@ -4,7 +4,7 @@ mod analysis;
 #[cfg(test)]
 mod tests;
 
-use std::iter;
+use std::{iter, ops::ControlFlow};
 
 use hir::{
     HasAttrs, Local, Name, PathResolution, ScopeDef, Semantics, SemanticsScope, Type, TypeInfo,
@@ -15,7 +15,7 @@ use ide_db::{
 };
 use syntax::{
     ast::{self, AttrKind, NameOrNameRef},
-    AstNode, Edition, SmolStr,
+    match_ast, AstNode, Edition, SmolStr,
     SyntaxKind::{self, *},
     SyntaxToken, TextRange, TextSize, T,
 };
@@ -26,7 +26,7 @@ use crate::{
     CompletionConfig,
 };
 
-const COMPLETION_MARKER: &str = "intellijRulezz";
+const COMPLETION_MARKER: &str = "raCompletionMarker";
 
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
 pub(crate) enum PatternRefutability {
@@ -457,6 +457,16 @@ pub(crate) struct CompletionContext<'a> {
     ///
     /// Here depth will be 2
     pub(crate) depth_from_crate_root: usize,
+
+    /// Whether and how to complete semicolon for unit-returning functions.
+    pub(crate) complete_semicolon: CompleteSemicolon,
+}
+
+#[derive(Debug)]
+pub(crate) enum CompleteSemicolon {
+    DoNotComplete,
+    CompleteSemi,
+    CompleteComma,
 }
 
 impl CompletionContext<'_> {
@@ -735,6 +745,53 @@ impl<'a> CompletionContext<'a> {
 
         let depth_from_crate_root = iter::successors(module.parent(db), |m| m.parent(db)).count();
 
+        let complete_semicolon = if config.add_semicolon_to_unit {
+            let inside_closure_ret = token.parent_ancestors().try_for_each(|ancestor| {
+                match_ast! {
+                    match ancestor {
+                        ast::BlockExpr(_) => ControlFlow::Break(false),
+                        ast::ClosureExpr(_) => ControlFlow::Break(true),
+                        _ => ControlFlow::Continue(())
+                    }
+                }
+            });
+
+            if inside_closure_ret == ControlFlow::Break(true) {
+                CompleteSemicolon::DoNotComplete
+            } else {
+                let next_non_trivia_token =
+                    std::iter::successors(token.next_token(), |it| it.next_token())
+                        .find(|it| !it.kind().is_trivia());
+                let in_match_arm = token.parent_ancestors().try_for_each(|ancestor| {
+                    if ast::MatchArm::can_cast(ancestor.kind()) {
+                        ControlFlow::Break(true)
+                    } else if matches!(
+                        ancestor.kind(),
+                        SyntaxKind::EXPR_STMT | SyntaxKind::BLOCK_EXPR
+                    ) {
+                        ControlFlow::Break(false)
+                    } else {
+                        ControlFlow::Continue(())
+                    }
+                });
+                // FIXME: This will assume expr macros are not inside match, we need to somehow go to the "parent" of the root node.
+                let in_match_arm = match in_match_arm {
+                    ControlFlow::Continue(()) => false,
+                    ControlFlow::Break(it) => it,
+                };
+                let complete_token = if in_match_arm { T![,] } else { T![;] };
+                if next_non_trivia_token.map(|it| it.kind()) == Some(complete_token) {
+                    CompleteSemicolon::DoNotComplete
+                } else if in_match_arm {
+                    CompleteSemicolon::CompleteComma
+                } else {
+                    CompleteSemicolon::CompleteSemi
+                }
+            }
+        } else {
+            CompleteSemicolon::DoNotComplete
+        };
+
         let ctx = CompletionContext {
             sema,
             scope,
@@ -752,6 +809,7 @@ impl<'a> CompletionContext<'a> {
             qualifier_ctx,
             locals,
             depth_from_crate_root,
+            complete_semicolon,
         };
         Some((ctx, analysis))
     }
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs
index 7e5e69665f1..a859d79e243 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs
@@ -1,15 +1,15 @@
 //! Renderer for function calls.
 
-use std::ops::ControlFlow;
-
 use hir::{db::HirDatabase, AsAssocItem, HirDisplay};
 use ide_db::{SnippetCap, SymbolKind};
 use itertools::Itertools;
 use stdx::{format_to, to_lower_snake_case};
-use syntax::{ast, format_smolstr, AstNode, Edition, SmolStr, SyntaxKind, ToSmolStr, T};
+use syntax::{format_smolstr, AstNode, Edition, SmolStr, ToSmolStr};
 
 use crate::{
-    context::{CompletionContext, DotAccess, DotAccessKind, PathCompletionCtx, PathKind},
+    context::{
+        CompleteSemicolon, CompletionContext, DotAccess, DotAccessKind, PathCompletionCtx, PathKind,
+    },
     item::{
         Builder, CompletionItem, CompletionItemKind, CompletionRelevance, CompletionRelevanceFn,
         CompletionRelevanceReturnType, CompletionRelevanceTraitInfo,
@@ -277,32 +277,21 @@ pub(super) fn add_call_parens<'b>(
 
         (snippet, "(…)")
     };
-    if ret_type.is_unit() && ctx.config.add_semicolon_to_unit {
-        let next_non_trivia_token =
-            std::iter::successors(ctx.token.next_token(), |it| it.next_token())
-                .find(|it| !it.kind().is_trivia());
-        let in_match_arm = ctx.token.parent_ancestors().try_for_each(|ancestor| {
-            if ast::MatchArm::can_cast(ancestor.kind()) {
-                ControlFlow::Break(true)
-            } else if matches!(ancestor.kind(), SyntaxKind::EXPR_STMT | SyntaxKind::BLOCK_EXPR) {
-                ControlFlow::Break(false)
-            } else {
-                ControlFlow::Continue(())
-            }
-        });
-        // FIXME: This will assume expr macros are not inside match, we need to somehow go to the "parent" of the root node.
-        let in_match_arm = match in_match_arm {
-            ControlFlow::Continue(()) => false,
-            ControlFlow::Break(it) => it,
-        };
-        let complete_token = if in_match_arm { T![,] } else { T![;] };
-        if next_non_trivia_token.map(|it| it.kind()) != Some(complete_token) {
-            cov_mark::hit!(complete_semicolon);
-            let ch = if in_match_arm { ',' } else { ';' };
-            if snippet.ends_with("$0") {
-                snippet.insert(snippet.len() - "$0".len(), ch);
-            } else {
-                snippet.push(ch);
+    if ret_type.is_unit() {
+        match ctx.complete_semicolon {
+            CompleteSemicolon::DoNotComplete => {}
+            CompleteSemicolon::CompleteSemi | CompleteSemicolon::CompleteComma => {
+                cov_mark::hit!(complete_semicolon);
+                let ch = if matches!(ctx.complete_semicolon, CompleteSemicolon::CompleteComma) {
+                    ','
+                } else {
+                    ';'
+                };
+                if snippet.ends_with("$0") {
+                    snippet.insert(snippet.len() - "$0".len(), ch);
+                } else {
+                    snippet.push(ch);
+                }
             }
         }
     }
@@ -889,4 +878,25 @@ fn bar() {
 "#,
         );
     }
+
+    #[test]
+    fn no_semicolon_in_closure_ret() {
+        check_edit(
+            r#"foo"#,
+            r#"
+fn foo() {}
+fn baz(_: impl FnOnce()) {}
+fn bar() {
+    baz(|| fo$0);
+}
+"#,
+            r#"
+fn foo() {}
+fn baz(_: impl FnOnce()) {}
+fn bar() {
+    baz(|| foo()$0);
+}
+"#,
+        );
+    }
 }
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs
index 351abe9850b..1bbe097cc6c 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs
@@ -26,6 +26,7 @@ struct Foo;
             at doc = "…"
             at doc(alias = "…")
             at doc(hidden)
+            at expect(…)
             at forbid(…)
             at must_use
             at no_mangle
@@ -75,6 +76,7 @@ fn with_existing_attr() {
             at cfg(…)
             at cfg_attr(…)
             at deny(…)
+            at expect(…)
             at forbid(…)
             at warn(…)
             kw crate::
@@ -97,6 +99,7 @@ fn attr_on_source_file() {
             at doc = "…"
             at doc(alias = "…")
             at doc(hidden)
+            at expect(…)
             at feature(…)
             at forbid(…)
             at must_use
@@ -127,6 +130,7 @@ fn attr_on_module() {
             at doc = "…"
             at doc(alias = "…")
             at doc(hidden)
+            at expect(…)
             at forbid(…)
             at macro_use
             at must_use
@@ -149,6 +153,7 @@ fn attr_on_module() {
             at doc = "…"
             at doc(alias = "…")
             at doc(hidden)
+            at expect(…)
             at forbid(…)
             at must_use
             at no_implicit_prelude
@@ -174,6 +179,7 @@ fn attr_on_macro_rules() {
             at doc = "…"
             at doc(alias = "…")
             at doc(hidden)
+            at expect(…)
             at forbid(…)
             at macro_export
             at macro_use
@@ -199,6 +205,7 @@ fn attr_on_macro_def() {
             at doc = "…"
             at doc(alias = "…")
             at doc(hidden)
+            at expect(…)
             at forbid(…)
             at must_use
             at no_mangle
@@ -222,6 +229,7 @@ fn attr_on_extern_crate() {
             at doc = "…"
             at doc(alias = "…")
             at doc(hidden)
+            at expect(…)
             at forbid(…)
             at macro_use
             at must_use
@@ -246,6 +254,7 @@ fn attr_on_use() {
             at doc = "…"
             at doc(alias = "…")
             at doc(hidden)
+            at expect(…)
             at forbid(…)
             at must_use
             at no_mangle
@@ -269,6 +278,7 @@ fn attr_on_type_alias() {
             at doc = "…"
             at doc(alias = "…")
             at doc(hidden)
+            at expect(…)
             at forbid(…)
             at must_use
             at no_mangle
@@ -299,6 +309,7 @@ struct Foo;
             at doc = "…"
             at doc(alias = "…")
             at doc(hidden)
+            at expect(…)
             at forbid(…)
             at must_use
             at no_mangle
@@ -326,6 +337,7 @@ fn attr_on_enum() {
             at doc = "…"
             at doc(alias = "…")
             at doc(hidden)
+            at expect(…)
             at forbid(…)
             at must_use
             at no_mangle
@@ -351,6 +363,7 @@ fn attr_on_const() {
             at doc = "…"
             at doc(alias = "…")
             at doc(hidden)
+            at expect(…)
             at forbid(…)
             at must_use
             at no_mangle
@@ -374,6 +387,7 @@ fn attr_on_static() {
             at doc = "…"
             at doc(alias = "…")
             at doc(hidden)
+            at expect(…)
             at export_name = "…"
             at forbid(…)
             at global_allocator
@@ -402,6 +416,7 @@ fn attr_on_trait() {
             at doc = "…"
             at doc(alias = "…")
             at doc(hidden)
+            at expect(…)
             at forbid(…)
             at must_use
             at must_use
@@ -427,6 +442,7 @@ fn attr_on_impl() {
             at doc = "…"
             at doc(alias = "…")
             at doc(hidden)
+            at expect(…)
             at forbid(…)
             at must_use
             at no_mangle
@@ -446,6 +462,7 @@ fn attr_on_impl() {
             at doc = "…"
             at doc(alias = "…")
             at doc(hidden)
+            at expect(…)
             at forbid(…)
             at must_use
             at no_mangle
@@ -469,6 +486,7 @@ fn attr_on_extern_block() {
             at doc = "…"
             at doc(alias = "…")
             at doc(hidden)
+            at expect(…)
             at forbid(…)
             at link
             at must_use
@@ -489,6 +507,7 @@ fn attr_on_extern_block() {
             at doc = "…"
             at doc(alias = "…")
             at doc(hidden)
+            at expect(…)
             at forbid(…)
             at link
             at must_use
@@ -509,6 +528,7 @@ fn attr_on_variant() {
             at cfg(…)
             at cfg_attr(…)
             at deny(…)
+            at expect(…)
             at forbid(…)
             at non_exhaustive
             at warn(…)
@@ -532,6 +552,7 @@ fn attr_on_fn() {
             at doc = "…"
             at doc(alias = "…")
             at doc(hidden)
+            at expect(…)
             at export_name = "…"
             at forbid(…)
             at ignore = "…"
@@ -572,6 +593,7 @@ fn attr_in_source_file_end() {
             at doc = "…"
             at doc(alias = "…")
             at doc(hidden)
+            at expect(…)
             at export_name = "…"
             at forbid(…)
             at global_allocator
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs
index c4c28953175..83a1eb44a61 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs
@@ -998,6 +998,28 @@ fn BAR() {
     }
 
     #[test]
+    fn cfged_lint_attrs() {
+        check_diagnostics(
+            r#"
+//- /lib.rs cfg:feature=cool_feature
+#[cfg_attr(any(), allow(non_snake_case))]
+fn FOO() {}
+// ^^^ 💡 warn: Function `FOO` should have snake_case name, e.g. `foo`
+
+#[cfg_attr(non_existent, allow(non_snake_case))]
+fn BAR() {}
+// ^^^ 💡 warn: Function `BAR` should have snake_case name, e.g. `bar`
+
+#[cfg_attr(feature = "cool_feature", allow(non_snake_case))]
+fn BAZ() {}
+
+#[cfg_attr(feature = "cool_feature", cfg_attr ( all ( ) , allow ( non_snake_case ) ) ) ]
+fn QUX() {}
+        "#,
+        );
+    }
+
+    #[test]
     fn allow_with_comment() {
         check_diagnostics(
             r#"
diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs
index d46d099f6fe..45c723d09d4 100644
--- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs
@@ -76,8 +76,9 @@ mod handlers {
 #[cfg(test)]
 mod tests;
 
-use std::{collections::hash_map, sync::LazyLock};
+use std::{collections::hash_map, iter, sync::LazyLock};
 
+use either::Either;
 use hir::{db::ExpandDatabase, diagnostics::AnyDiagnostic, Crate, HirFileId, InFile, Semantics};
 use ide_db::{
     assists::{Assist, AssistId, AssistKind, AssistResolveStrategy},
@@ -92,7 +93,7 @@ use ide_db::{
 use itertools::Itertools;
 use syntax::{
     ast::{self, AstNode, HasAttrs},
-    AstPtr, Edition, SmolStr, SyntaxNode, SyntaxNodePtr, TextRange,
+    AstPtr, Edition, NodeOrToken, SmolStr, SyntaxKind, SyntaxNode, SyntaxNodePtr, TextRange, T,
 };
 
 // FIXME: Make this an enum
@@ -647,6 +648,7 @@ fn find_outline_mod_lint_severity(
     let mut result = None;
     let lint_groups = lint_groups(&diag.code);
     lint_attrs(
+        sema,
         ast::AnyHasAttrs::cast(module_source_file.value).expect("SourceFile always has attrs"),
         edition,
     )
@@ -763,7 +765,7 @@ fn fill_lint_attrs(
 
             if let Some(ancestor) = ast::AnyHasAttrs::cast(ancestor) {
                 // Insert this node's attributes into any outline descendant, including this node.
-                lint_attrs(ancestor, edition).for_each(|(lint, severity)| {
+                lint_attrs(sema, ancestor, edition).for_each(|(lint, severity)| {
                     if diag_severity.is_none() && lint_groups(&diag.code).contains(&&*lint) {
                         diag_severity = Some(severity);
                     }
@@ -793,7 +795,7 @@ fn fill_lint_attrs(
             cache_stack.pop();
             return diag_severity;
         } else if let Some(ancestor) = ast::AnyHasAttrs::cast(ancestor) {
-            lint_attrs(ancestor, edition).for_each(|(lint, severity)| {
+            lint_attrs(sema, ancestor, edition).for_each(|(lint, severity)| {
                 if diag_severity.is_none() && lint_groups(&diag.code).contains(&&*lint) {
                     diag_severity = Some(severity);
                 }
@@ -809,20 +811,27 @@ fn fill_lint_attrs(
     }
 }
 
-fn lint_attrs(
+fn lint_attrs<'a>(
+    sema: &'a Semantics<'a, RootDatabase>,
     ancestor: ast::AnyHasAttrs,
     edition: Edition,
-) -> impl Iterator<Item = (SmolStr, Severity)> {
+) -> impl Iterator<Item = (SmolStr, Severity)> + 'a {
     ancestor
         .attrs_including_inner()
         .filter_map(|attr| {
             attr.as_simple_call().and_then(|(name, value)| match &*name {
-                "allow" | "expect" => Some((Severity::Allow, value)),
-                "warn" => Some((Severity::Warning, value)),
-                "forbid" | "deny" => Some((Severity::Error, value)),
+                "allow" | "expect" => Some(Either::Left(iter::once((Severity::Allow, value)))),
+                "warn" => Some(Either::Left(iter::once((Severity::Warning, value)))),
+                "forbid" | "deny" => Some(Either::Left(iter::once((Severity::Error, value)))),
+                "cfg_attr" => {
+                    let mut lint_attrs = Vec::new();
+                    cfg_attr_lint_attrs(sema, &value, &mut lint_attrs);
+                    Some(Either::Right(lint_attrs.into_iter()))
+                }
                 _ => None,
             })
         })
+        .flatten()
         .flat_map(move |(severity, lints)| {
             parse_tt_as_comma_sep_paths(lints, edition).into_iter().flat_map(move |lints| {
                 // Rejoin the idents with `::`, so we have no spaces in between.
@@ -836,6 +845,58 @@ fn lint_attrs(
         })
 }
 
+fn cfg_attr_lint_attrs(
+    sema: &Semantics<'_, RootDatabase>,
+    value: &ast::TokenTree,
+    lint_attrs: &mut Vec<(Severity, ast::TokenTree)>,
+) {
+    let prev_len = lint_attrs.len();
+
+    let mut iter = value.token_trees_and_tokens().filter(|it| match it {
+        NodeOrToken::Node(_) => true,
+        NodeOrToken::Token(it) => !it.kind().is_trivia(),
+    });
+
+    // Skip the condition.
+    for value in &mut iter {
+        if value.as_token().is_some_and(|it| it.kind() == T![,]) {
+            break;
+        }
+    }
+
+    while let Some(value) = iter.next() {
+        if let Some(token) = value.as_token() {
+            if token.kind() == SyntaxKind::IDENT {
+                let severity = match token.text() {
+                    "allow" | "expect" => Some(Severity::Allow),
+                    "warn" => Some(Severity::Warning),
+                    "forbid" | "deny" => Some(Severity::Error),
+                    "cfg_attr" => {
+                        if let Some(NodeOrToken::Node(value)) = iter.next() {
+                            cfg_attr_lint_attrs(sema, &value, lint_attrs);
+                        }
+                        None
+                    }
+                    _ => None,
+                };
+                if let Some(severity) = severity {
+                    let lints = iter.next();
+                    if let Some(NodeOrToken::Node(lints)) = lints {
+                        lint_attrs.push((severity, lints));
+                    }
+                }
+            }
+        }
+    }
+
+    if prev_len != lint_attrs.len() {
+        if let Some(false) | None = sema.check_cfg_attr(value) {
+            // Discard the attributes when the condition is false.
+            lint_attrs.truncate(prev_len);
+        }
+    }
+}
+
 fn lint_groups(lint: &DiagnosticCode) -> &'static [&'static str] {
     match lint {
         DiagnosticCode::RustcLint(name) => {
diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs b/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs
index 971cd3ef585..8836166d969 100644
--- a/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs
@@ -2750,4 +2750,36 @@ fn foo() {
         "#,
         );
     }
+
+    #[test]
+    fn issue_18138() {
+        check(
+            r#"
+mod foo {
+    macro_rules! x {
+        () => {
+            pub struct Foo;
+                    // ^^^
+        };
+    }
+    pub(crate) use x as m;
+}
+
+mod bar {
+    use crate::m;
+
+    m!();
+ // ^^^^^
+
+    fn qux() {
+        Foo$0;
+    }
+}
+
+mod m {}
+
+use foo::m;
+"#,
+        );
+    }
 }
diff --git a/src/tools/rust-analyzer/crates/ide/src/hover/render.rs b/src/tools/rust-analyzer/crates/ide/src/hover/render.rs
index b02e9b0f073..83adf6548a8 100644
--- a/src/tools/rust-analyzer/crates/ide/src/hover/render.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/hover/render.rs
@@ -329,7 +329,7 @@ pub(super) fn try_for_lint(attr: &ast::Attr, token: &SyntaxToken) -> Option<Hove
     }
     let (is_clippy, lints) = match &*path {
         "feature" => (false, FEATURES),
-        "allow" | "deny" | "forbid" | "warn" => {
+        "allow" | "deny" | "expect" | "forbid" | "warn" => {
             let is_clippy = algo::non_trivia_sibling(token.clone().into(), Direction::Prev)
                 .filter(|t| t.kind() == T![:])
                 .and_then(|t| algo::non_trivia_sibling(t, Direction::Prev))
diff --git a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs
index 8805ead818a..cca62d2181f 100644
--- a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs
@@ -6336,7 +6336,19 @@ fn hover_lint() {
 
                 arithmetic operation overflows
             "#]],
-    )
+    );
+    check(
+        r#"#![expect(arithmetic_overflow$0)]"#,
+        expect![[r#"
+                *arithmetic_overflow*
+                ```
+                arithmetic_overflow
+                ```
+                ___
+
+                arithmetic operation overflows
+            "#]],
+    );
 }
 
 #[test]
@@ -6352,7 +6364,19 @@ fn hover_clippy_lint() {
 
                 Checks for `foo = bar; bar = foo` sequences.
             "#]],
-    )
+    );
+    check(
+        r#"#![expect(clippy::almost_swapped$0)]"#,
+        expect![[r#"
+                *almost_swapped*
+                ```
+                clippy::almost_swapped
+                ```
+                ___
+
+                Checks for `foo = bar; bar = foo` sequences.
+            "#]],
+    );
 }
 
 #[test]
diff --git a/src/tools/rust-analyzer/crates/mbe/Cargo.toml b/src/tools/rust-analyzer/crates/mbe/Cargo.toml
index 413a0254a61..b37d57f2801 100644
--- a/src/tools/rust-analyzer/crates/mbe/Cargo.toml
+++ b/src/tools/rust-analyzer/crates/mbe/Cargo.toml
@@ -18,6 +18,7 @@ rustc-hash.workspace = true
 smallvec.workspace = true
 tracing.workspace = true
 arrayvec.workspace = true
+ra-ap-rustc_lexer.workspace = true
 
 # local deps
 syntax.workspace = true
diff --git a/src/tools/rust-analyzer/crates/mbe/src/benchmark.rs b/src/tools/rust-analyzer/crates/mbe/src/benchmark.rs
index f8c1d027c60..9e4ef140740 100644
--- a/src/tools/rust-analyzer/crates/mbe/src/benchmark.rs
+++ b/src/tools/rust-analyzer/crates/mbe/src/benchmark.rs
@@ -216,7 +216,11 @@ fn invocation_fixtures(
 
                 token_trees.push(subtree.into());
             }
-            Op::Ignore { .. } | Op::Index { .. } | Op::Count { .. } | Op::Len { .. } => {}
+            Op::Ignore { .. }
+            | Op::Index { .. }
+            | Op::Count { .. }
+            | Op::Len { .. }
+            | Op::Concat { .. } => {}
         };
 
         // Simple linear congruential generator for deterministic result
diff --git a/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs b/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs
index 90f56cc31da..95641abc0f3 100644
--- a/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs
+++ b/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs
@@ -584,7 +584,11 @@ fn match_loop_inner<'t>(
                 error_items.push(item);
             }
             OpDelimited::Op(
-                Op::Ignore { .. } | Op::Index { .. } | Op::Count { .. } | Op::Len { .. },
+                Op::Ignore { .. }
+                | Op::Index { .. }
+                | Op::Count { .. }
+                | Op::Len { .. }
+                | Op::Concat { .. },
             ) => {
                 stdx::never!("metavariable expression in lhs found");
             }
@@ -780,7 +784,7 @@ fn match_meta_var(
             // [1]: https://github.com/rust-lang/rust/blob/f0c4da499/compiler/rustc_expand/src/mbe/macro_parser.rs#L576
             match input.peek_n(0) {
                 Some(tt::TokenTree::Leaf(tt::Leaf::Ident(it))) => {
-                    let is_err = if matches!(expr, ExprKind::Expr2021) {
+                    let is_err = if it.is_raw.no() && matches!(expr, ExprKind::Expr2021) {
                         it.sym == sym::underscore || it.sym == sym::let_ || it.sym == sym::const_
                     } else {
                         it.sym == sym::let_
@@ -879,7 +883,11 @@ fn collect_vars(collector_fun: &mut impl FnMut(Symbol), pattern: &MetaTemplate)
             Op::Subtree { tokens, .. } => collect_vars(collector_fun, tokens),
             Op::Repeat { tokens, .. } => collect_vars(collector_fun, tokens),
             Op::Literal(_) | Op::Ident(_) | Op::Punct(_) => {}
-            Op::Ignore { .. } | Op::Index { .. } | Op::Count { .. } | Op::Len { .. } => {
+            Op::Ignore { .. }
+            | Op::Index { .. }
+            | Op::Count { .. }
+            | Op::Len { .. }
+            | Op::Concat { .. } => {
                 stdx::never!("metavariable expression in lhs found");
             }
         }
diff --git a/src/tools/rust-analyzer/crates/mbe/src/expander/transcriber.rs b/src/tools/rust-analyzer/crates/mbe/src/expander/transcriber.rs
index 00844115355..1db2f35d262 100644
--- a/src/tools/rust-analyzer/crates/mbe/src/expander/transcriber.rs
+++ b/src/tools/rust-analyzer/crates/mbe/src/expander/transcriber.rs
@@ -2,12 +2,12 @@
 //! `$ident => foo`, interpolates variables in the template, to get `fn foo() {}`
 
 use intern::{sym, Symbol};
-use span::Span;
+use span::{Edition, Span};
 use tt::Delimiter;
 
 use crate::{
     expander::{Binding, Bindings, Fragment},
-    parser::{MetaVarKind, Op, RepeatKind, Separator},
+    parser::{ConcatMetaVarExprElem, MetaVarKind, Op, RepeatKind, Separator},
     ExpandError, ExpandErrorKind, ExpandResult, MetaTemplate,
 };
 
@@ -312,6 +312,82 @@ fn expand_subtree(
                     .into(),
                 );
             }
+            Op::Concat { elements, span: concat_span } => {
+                let mut concatenated = String::new();
+                for element in elements {
+                    match element {
+                        ConcatMetaVarExprElem::Ident(ident) => {
+                            concatenated.push_str(ident.sym.as_str())
+                        }
+                        ConcatMetaVarExprElem::Literal(lit) => {
+                            // FIXME: This isn't really correct wrt. escaping, but that's what rustc does and anyway
+                            // escaping is used most of the times for characters that are invalid in identifiers.
+                            concatenated.push_str(lit.symbol.as_str())
+                        }
+                        ConcatMetaVarExprElem::Var(var) => {
+                            // Handling of repetitions in `${concat}` isn't fleshed out in rustc, so we currently
+                            // err at it.
+                            // FIXME: Do what rustc does for repetitions.
+                            let var_value = match ctx.bindings.get_fragment(
+                                &var.sym,
+                                var.span,
+                                &mut ctx.nesting,
+                                marker,
+                            ) {
+                                Ok(var) => var,
+                                Err(e) => {
+                                    if err.is_none() {
+                                        err = Some(e);
+                                    };
+                                    continue;
+                                }
+                            };
+                            let value = match &var_value {
+                                Fragment::Tokens(tt::TokenTree::Leaf(tt::Leaf::Ident(ident))) => {
+                                    ident.sym.as_str()
+                                }
+                                Fragment::Tokens(tt::TokenTree::Leaf(tt::Leaf::Literal(lit))) => {
+                                    lit.symbol.as_str()
+                                }
+                                _ => {
+                                    if err.is_none() {
+                                        err = Some(ExpandError::binding_error(var.span, "metavariables of `${concat(..)}` must be of type `ident`, `literal` or `tt`"))
+                                    }
+                                    continue;
+                                }
+                            };
+                            concatenated.push_str(value);
+                        }
+                    }
+                }
+
+                // `${concat}` span comes from the macro (at least for now).
+                // See https://github.com/rust-lang/rust/blob/b0af276da341/compiler/rustc_expand/src/mbe/transcribe.rs#L724-L726.
+                let mut result_span = *concat_span;
+                marker(&mut result_span);
+
+                // FIXME: NFC normalize the result.
+                if !rustc_lexer::is_ident(&concatenated) {
+                    if err.is_none() {
+                        err = Some(ExpandError::binding_error(
+                            *concat_span,
+                            "`${concat(..)}` is not generating a valid identifier",
+                        ));
+                    }
+                    // Insert a dummy identifier for better parsing.
+                    concatenated.clear();
+                    concatenated.push_str("__ra_concat_dummy");
+                }
+
+                let needs_raw =
+                    parser::SyntaxKind::from_keyword(&concatenated, Edition::LATEST).is_some();
+                let is_raw = if needs_raw { tt::IdentIsRaw::Yes } else { tt::IdentIsRaw::No };
+                arena.push(tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident {
+                    is_raw,
+                    span: result_span,
+                    sym: Symbol::intern(&concatenated),
+                })));
+            }
         }
     }
     // drain the elements added in this instance of expand_subtree
diff --git a/src/tools/rust-analyzer/crates/mbe/src/lib.rs b/src/tools/rust-analyzer/crates/mbe/src/lib.rs
index f7b6e8f54b5..dd71e46db36 100644
--- a/src/tools/rust-analyzer/crates/mbe/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/mbe/src/lib.rs
@@ -6,6 +6,11 @@
 //! The tests for this functionality live in another crate:
 //! `hir_def::macro_expansion_tests::mbe`.
 
+#[cfg(not(feature = "in-rust-tree"))]
+extern crate ra_ap_rustc_lexer as rustc_lexer;
+#[cfg(feature = "in-rust-tree")]
+extern crate rustc_lexer;
+
 mod expander;
 mod parser;
 
diff --git a/src/tools/rust-analyzer/crates/mbe/src/parser.rs b/src/tools/rust-analyzer/crates/mbe/src/parser.rs
index 2efe318f614..b55edf4a5e0 100644
--- a/src/tools/rust-analyzer/crates/mbe/src/parser.rs
+++ b/src/tools/rust-analyzer/crates/mbe/src/parser.rs
@@ -84,6 +84,10 @@ pub(crate) enum Op {
         // FIXME: `usize`` once we drop support for 1.76
         depth: Option<usize>,
     },
+    Concat {
+        elements: Box<[ConcatMetaVarExprElem]>,
+        span: Span,
+    },
     Repeat {
         tokens: MetaTemplate,
         kind: RepeatKind,
@@ -98,6 +102,18 @@ pub(crate) enum Op {
     Ident(tt::Ident<Span>),
 }
 
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub(crate) enum ConcatMetaVarExprElem {
+    /// There is NO preceding dollar sign, which means that this identifier should be interpreted
+    /// as a literal.
+    Ident(tt::Ident<Span>),
+    /// There is a preceding dollar sign, which means that this identifier should be expanded
+    /// and interpreted as a variable.
+    Var(tt::Ident<Span>),
+    /// For example, a number or a string.
+    Literal(tt::Literal<Span>),
+}
+
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
 pub(crate) enum RepeatKind {
     ZeroOrMore,
@@ -384,6 +400,32 @@ fn parse_metavar_expr(src: &mut TtIter<'_, Span>) -> Result<Op, ()> {
             let depth = if try_eat_comma(&mut args) { Some(parse_depth(&mut args)?) } else { None };
             Op::Count { name: ident.sym.clone(), depth }
         }
+        s if sym::concat == *s => {
+            let mut elements = Vec::new();
+            while let Some(next) = args.peek_n(0) {
+                let element = if let tt::TokenTree::Leaf(tt::Leaf::Literal(lit)) = next {
+                    args.next().expect("already peeked");
+                    ConcatMetaVarExprElem::Literal(lit.clone())
+                } else {
+                    let is_var = try_eat_dollar(&mut args);
+                    let ident = args.expect_ident_or_underscore()?.clone();
+
+                    if is_var {
+                        ConcatMetaVarExprElem::Var(ident)
+                    } else {
+                        ConcatMetaVarExprElem::Ident(ident)
+                    }
+                };
+                elements.push(element);
+                if args.peek_n(0).is_some() {
+                    args.expect_comma()?;
+                }
+            }
+            if elements.len() < 2 {
+                return Err(());
+            }
+            Op::Concat { elements: elements.into_boxed_slice(), span: func.span }
+        }
         _ => return Err(()),
     };
 
@@ -414,3 +456,11 @@ fn try_eat_comma(src: &mut TtIter<'_, Span>) -> bool {
     }
     false
 }
+
+fn try_eat_dollar(src: &mut TtIter<'_, Span>) -> bool {
+    if let Some(tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { char: '$', .. }))) = src.peek_n(0) {
+        let _ = src.next();
+        return true;
+    }
+    false
+}
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs
index 44e56645ba3..2134b033f3f 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs
@@ -77,7 +77,11 @@ impl flags::AnalysisStats {
         let metadata_time = db_load_sw.elapsed();
         let load_cargo_config = LoadCargoConfig {
             load_out_dirs_from_check: !self.disable_build_scripts,
-            with_proc_macro_server: ProcMacroServerChoice::Sysroot,
+            with_proc_macro_server: if self.disable_proc_macros {
+                ProcMacroServerChoice::None
+            } else {
+                ProcMacroServerChoice::Sysroot
+            },
             prefill_caches: false,
         };
 
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs
index e8d1a7e4df6..49b1ba32a79 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/notification.rs
@@ -357,7 +357,7 @@ fn run_flycheck(state: &mut GlobalState, vfs_path: VfsPath) -> bool {
                             .targets
                             .iter()
                             .any(|&it| crate_root_paths.contains(&cargo[it].root.as_path()));
-                        has_target_with_root.then(|| cargo[pkg].name.clone())
+                        has_target_with_root.then(|| cargo.package_flag(&cargo[pkg]))
                     }),
                     project_model::ProjectWorkspaceKind::Json(project) => {
                         if !project.crates().any(|(_, krate)| {
diff --git a/src/tools/rust-analyzer/crates/tt/src/iter.rs b/src/tools/rust-analyzer/crates/tt/src/iter.rs
index e96bed0319e..587b903aa97 100644
--- a/src/tools/rust-analyzer/crates/tt/src/iter.rs
+++ b/src/tools/rust-analyzer/crates/tt/src/iter.rs
@@ -57,6 +57,13 @@ impl<'a, S: Copy> TtIter<'a, S> {
         }
     }
 
+    pub fn expect_comma(&mut self) -> Result<(), ()> {
+        match self.expect_leaf()? {
+            Leaf::Punct(Punct { char: ',', .. }) => Ok(()),
+            _ => Err(()),
+        }
+    }
+
     pub fn expect_ident(&mut self) -> Result<&'a Ident<S>, ()> {
         match self.expect_leaf()? {
             Leaf::Ident(it) if it.sym != sym::underscore => Ok(it),