From 1d2dbb64693a486104b2f0ef45c99158d406ada9 Mon Sep 17 00:00:00 2001 From: Asuka Minato Date: Sun, 21 Dec 2025 05:42:12 +0900 Subject: [PATCH 001/144] impl fix test clippy --- .../crates/ide/src/inlay_hints.rs | 13 ++- .../crates/ide/src/inlay_hints/bind_pat.rs | 98 +++++++++++++++- .../crates/ide/src/inlay_hints/chaining.rs | 107 ++++++++++++++++-- src/tools/rust-analyzer/crates/ide/src/lib.rs | 2 +- .../crates/ide/src/static_index.rs | 3 +- .../rust-analyzer/src/cli/analysis_stats.rs | 1 + .../crates/rust-analyzer/src/config.rs | 22 ++++ .../docs/book/src/configuration_generated.md | 7 ++ .../rust-analyzer/editors/code/package.json | 18 +++ 9 files changed, 252 insertions(+), 19 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs index f57f2883b1c38..69b3db644633f 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs @@ -302,6 +302,7 @@ fn hints( pub struct InlayHintsConfig<'a> { pub render_colons: bool, pub type_hints: bool, + pub type_hints_placement: TypeHintsPlacement, pub sized_bound: bool, pub discriminant_hints: DiscriminantHints, pub parameter_hints: bool, @@ -331,6 +332,12 @@ pub struct InlayHintsConfig<'a> { pub minicore: MiniCore<'a>, } +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum TypeHintsPlacement { + Inline, + EndOfLine, +} + impl InlayHintsConfig<'_> { fn lazy_text_edit(&self, finish: impl FnOnce() -> TextEdit) -> LazyProperty { if self.fields_to_resolve.resolve_text_edits { @@ -876,12 +883,15 @@ mod tests { use crate::inlay_hints::{AdjustmentHints, AdjustmentHintsMode}; use crate::{LifetimeElisionHints, fixture, inlay_hints::InlayHintsConfig}; - use super::{ClosureReturnTypeHints, GenericParameterHints, InlayFieldsToResolve}; + use super::{ + ClosureReturnTypeHints, GenericParameterHints, InlayFieldsToResolve, TypeHintsPlacement, + }; pub(super) const DISABLED_CONFIG: InlayHintsConfig<'_> = InlayHintsConfig { discriminant_hints: DiscriminantHints::Never, render_colons: false, type_hints: false, + type_hints_placement: TypeHintsPlacement::Inline, parameter_hints: false, parameter_hints_for_missing_arguments: false, sized_bound: false, @@ -915,6 +925,7 @@ mod tests { }; pub(super) const TEST_CONFIG: InlayHintsConfig<'_> = InlayHintsConfig { type_hints: true, + type_hints_placement: TypeHintsPlacement::Inline, parameter_hints: true, chaining_hints: true, closure_return_type_hints: ClosureReturnTypeHints::WithBlock, diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs index de207c7821da0..5e16be332e8e4 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs @@ -8,10 +8,12 @@ use ide_db::{RootDatabase, famous_defs::FamousDefs}; use itertools::Itertools; use syntax::{ + TextRange, ast::{self, AstNode, HasGenericArgs, HasName}, match_ast, }; +use super::TypeHintsPlacement; use crate::{ InlayHint, InlayHintPosition, InlayHintsConfig, InlayKind, inlay_hints::{closure_has_block_body, label_of_ty, ty_to_text_edit}, @@ -29,6 +31,7 @@ pub(super) fn hints( } let parent = pat.syntax().parent()?; + let mut enclosing_let_stmt = None; let type_ascriptable = match_ast! { match parent { ast::Param(it) => { @@ -41,6 +44,7 @@ pub(super) fn hints( Some(it.colon_token()) }, ast::LetStmt(it) => { + enclosing_let_stmt = Some(it.clone()); if config.hide_closure_initialization_hints && let Some(ast::Expr::ClosureExpr(closure)) = it.initializer() && closure_has_block_body(&closure) { @@ -101,16 +105,26 @@ pub(super) fn hints( Some(name) => name.syntax().text_range(), None => pat.syntax().text_range(), }; + let mut range = match type_ascriptable { + Some(Some(t)) => text_range.cover(t.text_range()), + _ => text_range, + }; + + let mut pad_left = !render_colons; + if matches!(config.type_hints_placement, TypeHintsPlacement::EndOfLine) + && let Some(let_stmt) = enclosing_let_stmt + { + let stmt_range = let_stmt.syntax().text_range(); + range = TextRange::new(range.start(), stmt_range.end()); + pad_left = true; + } acc.push(InlayHint { - range: match type_ascriptable { - Some(Some(t)) => text_range.cover(t.text_range()), - _ => text_range, - }, + range, kind: InlayKind::Type, label, text_edit, position: InlayHintPosition::After, - pad_left: !render_colons, + pad_left, pad_right: false, resolve_parent: Some(pat.syntax().text_range()), }); @@ -182,8 +196,10 @@ mod tests { use crate::{ClosureReturnTypeHints, fixture, inlay_hints::InlayHintsConfig}; + use super::TypeHintsPlacement; use crate::inlay_hints::tests::{ - DISABLED_CONFIG, TEST_CONFIG, check, check_edit, check_no_edit, check_with_config, + DISABLED_CONFIG, TEST_CONFIG, check, check_edit, check_expect, check_no_edit, + check_with_config, }; #[track_caller] @@ -203,6 +219,76 @@ fn main() { ); } + #[test] + fn type_hints_end_of_line_placement() { + let mut config = InlayHintsConfig { type_hints: true, ..DISABLED_CONFIG }; + config.type_hints_placement = TypeHintsPlacement::EndOfLine; + check_expect( + config, + r#" +fn main() { + let foo = 92_i32; +} + "#, + expect![[r#" + [ + ( + 20..33, + [ + "i32", + ], + ), + ] + "#]], + ); + } + + #[test] + fn type_hints_end_of_line_placement_chain_expr() { + let mut config = InlayHintsConfig { type_hints: true, ..DISABLED_CONFIG }; + config.type_hints_placement = TypeHintsPlacement::EndOfLine; + check_expect( + config, + r#" +fn main() { + struct Builder; + impl Builder { + fn iter(self) -> Builder { Builder } + fn map(self) -> Builder { Builder } + } + fn make() -> Builder { Builder } + + let foo = make() + .iter() + .map(); +} +"#, + expect![[r#" + [ + ( + 192..236, + [ + InlayHintLabelPart { + text: "Builder", + linked_location: Some( + Computed( + FileRangeWrapper { + file_id: FileId( + 0, + ), + range: 23..30, + }, + ), + ), + tooltip: "", + }, + ], + ), + ] + "#]], + ); + } + #[test] fn type_hints_bindings_after_at() { check_types( diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs index cf3149c9461b8..4b06f83971b25 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs @@ -2,13 +2,13 @@ use hir::DisplayTarget; use ide_db::famous_defs::FamousDefs; use syntax::{ - Direction, NodeOrToken, SyntaxKind, T, + Direction, NodeOrToken, SyntaxKind, T, TextRange, ast::{self, AstNode}, }; use crate::{InlayHint, InlayHintPosition, InlayHintsConfig, InlayKind}; -use super::label_of_ty; +use super::{TypeHintsPlacement, label_of_ty}; pub(super) fn hints( acc: &mut Vec, @@ -40,13 +40,14 @@ pub(super) fn hints( // Chaining can be defined as an expression whose next sibling tokens are newline and dot // Ignoring extra whitespace and comments - let next = tokens.next()?.kind(); - if next == SyntaxKind::WHITESPACE { - let mut next_next = tokens.next()?.kind(); - while next_next == SyntaxKind::WHITESPACE { - next_next = tokens.next()?.kind(); + let next_token = tokens.next()?; + if next_token.kind() == SyntaxKind::WHITESPACE { + let newline_token = next_token; + let mut next_next = tokens.next()?; + while next_next.kind() == SyntaxKind::WHITESPACE { + next_next = tokens.next()?; } - if next_next == T![.] { + if next_next.kind() == T![.] { let ty = sema.type_of_expr(desc_expr)?.original; if ty.is_unknown() { return None; @@ -58,8 +59,18 @@ pub(super) fn hints( return None; } let label = label_of_ty(famous_defs, config, &ty, display_target)?; + let range = { + let mut range = expr.syntax().text_range(); + if config.type_hints_placement == TypeHintsPlacement::EndOfLine { + range = TextRange::new( + range.start(), + newline_token.text_range().start().max(range.end()), + ); + } + range + }; acc.push(InlayHint { - range: expr.syntax().text_range(), + range, kind: InlayKind::Chaining, label, text_edit: None, @@ -79,7 +90,7 @@ mod tests { use ide_db::text_edit::{TextRange, TextSize}; use crate::{ - InlayHintsConfig, fixture, + InlayHintsConfig, TypeHintsPlacement, fixture, inlay_hints::{ LazyProperty, tests::{DISABLED_CONFIG, TEST_CONFIG, check_expect, check_with_config}, @@ -686,4 +697,80 @@ fn main() { "#]], ); } + + #[test] + fn chaining_hints_end_of_line_placement() { + check_expect( + InlayHintsConfig { + chaining_hints: true, + type_hints_placement: TypeHintsPlacement::EndOfLine, + ..DISABLED_CONFIG + }, + r#" +fn main() { + let baz = make() + .into_bar() + .into_baz(); +} + +struct Foo; +struct Bar; +struct Baz; + +impl Foo { + fn into_bar(self) -> Bar { Bar } +} + +impl Bar { + fn into_baz(self) -> Baz { Baz } +} + +fn make() -> Foo { + Foo +} +"#, + expect![[r#" + [ + ( + 26..52, + [ + InlayHintLabelPart { + text: "Bar", + linked_location: Some( + Computed( + FileRangeWrapper { + file_id: FileId( + 0, + ), + range: 96..99, + }, + ), + ), + tooltip: "", + }, + ], + ), + ( + 26..32, + [ + InlayHintLabelPart { + text: "Foo", + linked_location: Some( + Computed( + FileRangeWrapper { + file_id: FileId( + 0, + ), + range: 84..87, + }, + ), + ), + tooltip: "", + }, + ], + ), + ] + "#]], + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide/src/lib.rs b/src/tools/rust-analyzer/crates/ide/src/lib.rs index 5e4d930393af1..da2a0aa1f2661 100644 --- a/src/tools/rust-analyzer/crates/ide/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide/src/lib.rs @@ -96,7 +96,7 @@ pub use crate::{ AdjustmentHints, AdjustmentHintsMode, ClosureReturnTypeHints, DiscriminantHints, GenericParameterHints, InlayFieldsToResolve, InlayHint, InlayHintLabel, InlayHintLabelPart, InlayHintPosition, InlayHintsConfig, InlayKind, InlayTooltip, LazyProperty, - LifetimeElisionHints, + LifetimeElisionHints, TypeHintsPlacement, }, join_lines::JoinLinesConfig, markup::Markup, diff --git a/src/tools/rust-analyzer/crates/ide/src/static_index.rs b/src/tools/rust-analyzer/crates/ide/src/static_index.rs index aba6b64f977a5..14af86f050303 100644 --- a/src/tools/rust-analyzer/crates/ide/src/static_index.rs +++ b/src/tools/rust-analyzer/crates/ide/src/static_index.rs @@ -16,7 +16,7 @@ use crate::navigation_target::UpmappingResult; use crate::{ Analysis, Fold, HoverConfig, HoverResult, InlayHint, InlayHintsConfig, TryToNav, hover::{SubstTyLen, hover_for_definition}, - inlay_hints::{AdjustmentHintsMode, InlayFieldsToResolve}, + inlay_hints::{AdjustmentHintsMode, InlayFieldsToResolve, TypeHintsPlacement}, moniker::{MonikerResult, SymbolInformationKind, def_to_kind, def_to_moniker}, parent_module::crates_for, }; @@ -167,6 +167,7 @@ impl StaticIndex<'_> { render_colons: true, discriminant_hints: crate::DiscriminantHints::Fieldless, type_hints: true, + type_hints_placement: TypeHintsPlacement::Inline, sized_bound: false, parameter_hints: true, parameter_hints_for_missing_arguments: 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 76256b0a22530..75d6710b5b538 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 @@ -1204,6 +1204,7 @@ impl flags::AnalysisStats { &InlayHintsConfig { render_colons: false, type_hints: true, + type_hints_placement: ide::TypeHintsPlacement::Inline, sized_bound: false, discriminant_hints: ide::DiscriminantHints::Always, parameter_hints: true, diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs index 2371f7a65649e..010cbd9e4c19c 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs @@ -320,6 +320,9 @@ config_data! { /// Hide inlay type hints for constructors. inlayHints_typeHints_hideNamedConstructor: bool = false, + /// Where to render type hints relative to their binding pattern. + inlayHints_typeHints_location: TypeHintsLocation = TypeHintsLocation::Inline, + /// Enable the experimental support for interpreting tests. interpret_tests: bool = false, @@ -1926,6 +1929,10 @@ impl Config { InlayHintsConfig { render_colons: self.inlayHints_renderColons().to_owned(), type_hints: self.inlayHints_typeHints_enable().to_owned(), + type_hints_placement: match self.inlayHints_typeHints_location() { + TypeHintsLocation::Inline => ide::TypeHintsPlacement::Inline, + TypeHintsLocation::EndOfLine => ide::TypeHintsPlacement::EndOfLine, + }, sized_bound: self.inlayHints_implicitSizedBoundHints_enable().to_owned(), parameter_hints: self.inlayHints_parameterHints_enable().to_owned(), parameter_hints_for_missing_arguments: self @@ -2908,6 +2915,13 @@ enum ClosureStyle { Hide, } +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "snake_case")] +enum TypeHintsLocation { + Inline, + EndOfLine, +} + #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "snake_case")] enum ReborrowHintsDef { @@ -3811,6 +3825,14 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json "`hide`: Shows `...` for every closure type", ], }, + "TypeHintsLocation" => set! { + "type": "string", + "enum": ["inline", "end_of_line"], + "enumDescriptions": [ + "Render type hints directly after the binding identifier.", + "Render type hints after the end of the containing `let` statement when possible.", + ], + }, "Option" => set! { "anyOf": [ { diff --git a/src/tools/rust-analyzer/docs/book/src/configuration_generated.md b/src/tools/rust-analyzer/docs/book/src/configuration_generated.md index 6b7ef049645c8..78281ec6359f3 100644 --- a/src/tools/rust-analyzer/docs/book/src/configuration_generated.md +++ b/src/tools/rust-analyzer/docs/book/src/configuration_generated.md @@ -1150,6 +1150,13 @@ Default: `false` Hide inlay type hints for constructors. +## rust-analyzer.inlayHints.typeHints.location {#inlayHints.typeHints.location} + +Default: `"inline"` + +Where to render type hints relative to their binding pattern. + + ## rust-analyzer.interpret.tests {#interpret.tests} Default: `false` diff --git a/src/tools/rust-analyzer/editors/code/package.json b/src/tools/rust-analyzer/editors/code/package.json index d0410c70da675..d3d2ea8d19383 100644 --- a/src/tools/rust-analyzer/editors/code/package.json +++ b/src/tools/rust-analyzer/editors/code/package.json @@ -2512,6 +2512,24 @@ } } }, + { + "title": "Inlay Hints", + "properties": { + "rust-analyzer.inlayHints.typeHints.location": { + "markdownDescription": "Where to render type hints relative to their binding pattern.", + "default": "inline", + "type": "string", + "enum": [ + "inline", + "end_of_line" + ], + "enumDescriptions": [ + "Render type hints directly after the binding identifier.", + "Render type hints after the end of the containing `let` statement when possible." + ] + } + } + }, { "title": "Interpret", "properties": { From e0abac49cc4b561f9bfd6a6b83f191a0d6732380 Mon Sep 17 00:00:00 2001 From: roifewu Date: Mon, 30 Jun 2025 11:39:25 +0800 Subject: [PATCH 002/144] feat: folding ranges for chained expressions --- .../crates/ide/src/folding_ranges.rs | 358 ++++++++++++++++-- src/tools/rust-analyzer/crates/ide/src/lib.rs | 7 +- .../crates/ide/src/static_index.rs | 2 +- .../rust-analyzer/src/handlers/request.rs | 6 +- .../rust-analyzer/src/lsp/capabilities.rs | 14 + .../crates/rust-analyzer/src/lsp/to_proto.rs | 22 +- 6 files changed, 353 insertions(+), 56 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide/src/folding_ranges.rs b/src/tools/rust-analyzer/crates/ide/src/folding_ranges.rs index 3969490e8dcf5..ebd6c274a2ecd 100644 --- a/src/tools/rust-analyzer/crates/ide/src/folding_ranges.rs +++ b/src/tools/rust-analyzer/crates/ide/src/folding_ranges.rs @@ -1,10 +1,12 @@ use ide_db::{FxHashSet, syntax_helpers::node_ext::vis_eq}; +use itertools::Itertools; use syntax::{ - Direction, NodeOrToken, SourceFile, - SyntaxKind::{self, *}, + Direction, NodeOrToken, SourceFile, SyntaxElement, + SyntaxKind::*, SyntaxNode, TextRange, TextSize, - ast::{self, AstNode, AstToken}, + ast::{self, AstNode, AstToken, HasArgList, edit::AstNodeEdit}, match_ast, + syntax_editor::Element, }; use std::hash::Hash; @@ -12,7 +14,7 @@ use std::hash::Hash; const REGION_START: &str = "// region:"; const REGION_END: &str = "// endregion"; -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum FoldKind { Comment, Imports, @@ -29,21 +31,36 @@ pub enum FoldKind { Consts, Statics, TypeAliases, + TraitAliases, ExternCrates, // endregion: item runs + Stmt, + TailExpr, } #[derive(Debug)] pub struct Fold { pub range: TextRange, pub kind: FoldKind, + pub collapsed_text: Option, +} + +impl Fold { + pub fn new(range: TextRange, kind: FoldKind) -> Self { + Self { range, kind, collapsed_text: None } + } + + pub fn with_text(mut self, text: String) -> Self { + self.collapsed_text = Some(text); + self + } } // Feature: Folding // // Defines folding regions for curly braced blocks, runs of consecutive use, mod, const or static // items, and `region` / `endregion` comment markers. -pub(crate) fn folding_ranges(file: &SourceFile) -> Vec { +pub(crate) fn folding_ranges(file: &SourceFile, collapsed_text: bool) -> Vec { let mut res = vec![]; let mut visited_comments = FxHashSet::default(); let mut visited_nodes = FxHashSet::default(); @@ -53,39 +70,36 @@ pub(crate) fn folding_ranges(file: &SourceFile) -> Vec { for element in file.syntax().descendants_with_tokens() { // Fold items that span multiple lines - if let Some(kind) = fold_kind(element.kind()) { + if let Some(kind) = fold_kind(element.clone()) { let is_multiline = match &element { NodeOrToken::Node(node) => node.text().contains_char('\n'), NodeOrToken::Token(token) => token.text().contains('\n'), }; + if is_multiline { - // for the func with multiline param list - if matches!(element.kind(), FN) - && let NodeOrToken::Node(node) = &element - && let Some(fn_node) = ast::Fn::cast(node.clone()) + if let NodeOrToken::Node(node) = &element + && let Some(fn_) = ast::Fn::cast(node.clone()) { - if !fn_node + if !fn_ .param_list() .map(|param_list| param_list.syntax().text().contains_char('\n')) - .unwrap_or(false) + .unwrap_or_default() { continue; } - if fn_node.body().is_some() { + if let Some(body) = fn_.body() { // Get the actual start of the function (excluding doc comments) - let fn_start = fn_node + let fn_start = fn_ .fn_token() .map(|token| token.text_range().start()) .unwrap_or(node.text_range().start()); - res.push(Fold { - range: TextRange::new(fn_start, node.text_range().end()), - kind: FoldKind::Function, - }); + res.push(build_fold(&element, kind, collapsed_text)); continue; } } - res.push(Fold { range: element.text_range(), kind }); + + res.push(build_fold(&element, kind, collapsed_text)); continue; } } @@ -102,15 +116,15 @@ pub(crate) fn folding_ranges(file: &SourceFile) -> Vec { region_starts.push(comment.syntax().text_range().start()); } else if text.starts_with(REGION_END) { if let Some(region) = region_starts.pop() { - res.push(Fold { - range: TextRange::new(region, comment.syntax().text_range().end()), - kind: FoldKind::Region, - }) + res.push(Fold::new( + TextRange::new(region, comment.syntax().text_range().end()), + FoldKind::Region, + )); } } else if let Some(range) = contiguous_range_for_comment(comment, &mut visited_comments) { - res.push(Fold { range, kind: FoldKind::Comment }) + res.push(Fold::new(range, FoldKind::Comment)); } } } @@ -123,37 +137,42 @@ pub(crate) fn folding_ranges(file: &SourceFile) -> Vec { module, &mut visited_nodes, ) { - res.push(Fold { range, kind: FoldKind::Modules }) + res.push(Fold::new(range, FoldKind::Modules)); } }, ast::Use(use_) => { if let Some(range) = contiguous_range_for_item_group(use_, &mut visited_nodes) { - res.push(Fold { range, kind: FoldKind::Imports }) + res.push(Fold::new(range, FoldKind::Imports)); } }, ast::Const(konst) => { if let Some(range) = contiguous_range_for_item_group(konst, &mut visited_nodes) { - res.push(Fold { range, kind: FoldKind::Consts }) + res.push(Fold::new(range, FoldKind::Consts)); } }, ast::Static(statik) => { if let Some(range) = contiguous_range_for_item_group(statik, &mut visited_nodes) { - res.push(Fold { range, kind: FoldKind::Statics }) + res.push(Fold::new(range, FoldKind::Statics)); } }, ast::TypeAlias(alias) => { if let Some(range) = contiguous_range_for_item_group(alias, &mut visited_nodes) { - res.push(Fold { range, kind: FoldKind::TypeAliases }) + res.push(Fold::new(range, FoldKind::TypeAliases)); + } + }, + ast::TraitAlias(alias) => { + if let Some(range) = contiguous_range_for_item_group(alias, &mut visited_nodes) { + res.push(Fold::new(range, FoldKind::TraitAliases)); } }, ast::ExternCrate(extern_crate) => { if let Some(range) = contiguous_range_for_item_group(extern_crate, &mut visited_nodes) { - res.push(Fold { range, kind: FoldKind::ExternCrates }) + res.push(Fold::new(range, FoldKind::ExternCrates)); } }, ast::MatchArm(match_arm) => { if let Some(range) = fold_range_for_multiline_match_arm(match_arm) { - res.push(Fold {range, kind: FoldKind::MatchArm}) + res.push(Fold::new(range, FoldKind::MatchArm)); } }, _ => (), @@ -166,8 +185,93 @@ pub(crate) fn folding_ranges(file: &SourceFile) -> Vec { res } -fn fold_kind(kind: SyntaxKind) -> Option { - match kind { +/// Builds a fold for the given syntax element. +/// +/// This function creates a `Fold` object that represents a collapsible region in the code. +/// If `collapsed_text` is enabled, it generates a preview text for certain fold kinds that +/// shows a summarized version of the folded content. +fn build_fold(element: &SyntaxElement, kind: FoldKind, collapsed_text: bool) -> Fold { + if !collapsed_text { + return Fold::new(element.text_range(), kind); + } + + let fold_with_collapsed_text = match kind { + FoldKind::TailExpr => { + let expr = ast::Expr::cast(element.as_node().unwrap().clone()).unwrap(); + + let indent_level = expr.indent_level().0; + let indents = " ".repeat(indent_level as usize); + + let mut fold = Fold::new(element.text_range(), kind); + if let Some(collapsed_expr) = collapsed_text_from_expr(expr) { + fold = fold.with_text(format!("{indents}{collapsed_expr}")); + } + Some(fold) + } + FoldKind::Stmt => 'blk: { + let node = element.as_node().unwrap(); + + match_ast! { + match node { + ast::ExprStmt(expr) => { + let Some(expr) = expr.expr() else { + break 'blk None; + }; + + let indent_level = expr.indent_level().0; + let indents = " ".repeat(indent_level as usize); + + let mut fold = Fold::new(element.text_range(), kind); + if let Some(collapsed_expr) = collapsed_text_from_expr(expr) { + fold = fold.with_text(format!("{indents}{collapsed_expr};")); + } + Some(fold) + }, + ast::LetStmt(let_stmt) => { + if let_stmt.let_else().is_some() { + break 'blk None; + } + + let Some(expr) = let_stmt.initializer() else { + break 'blk None; + }; + + let expr_offset = + expr.syntax().text_range().start() - let_stmt.syntax().text_range().start(); + let text_before_expr = let_stmt.syntax().text().slice(..expr_offset); + if text_before_expr.contains_char('\n') { + break 'blk None; + } + + let indent_level = let_stmt.indent_level().0; + let indents = " ".repeat(indent_level as usize); + + let mut fold = Fold::new(element.text_range(), kind); + if let Some(collapsed_expr) = collapsed_text_from_expr(expr) { + fold = fold.with_text(format!("{indents}{text_before_expr}{collapsed_expr};")); + } + Some(fold) + }, + _ => None, + } + } + } + _ => None, + }; + + fold_with_collapsed_text.unwrap_or_else(|| Fold::new(element.text_range(), kind)) +} + +fn fold_kind(element: SyntaxElement) -> Option { + // handle tail_expr + if let Some(node) = element.as_node() + && let Some(block) = node.parent().and_then(|it| it.parent()).and_then(ast::BlockExpr::cast) // tail_expr -> stmt_list -> block + && block.tail_expr().is_some_and(|tail| tail.syntax() == node) + { + return Some(FoldKind::TailExpr); + } + + match element.kind() { COMMENT => Some(FoldKind::Comment), ARG_LIST | PARAM_LIST | GENERIC_ARG_LIST | GENERIC_PARAM_LIST => Some(FoldKind::ArgList), ARRAY_EXPR => Some(FoldKind::Array), @@ -185,10 +289,105 @@ fn fold_kind(kind: SyntaxKind) -> Option { | MATCH_ARM_LIST | VARIANT_LIST | TOKEN_TREE => Some(FoldKind::Block), + EXPR_STMT | LET_STMT => Some(FoldKind::Stmt), _ => None, } } +/// Generates a collapsed text representation of a chained expression. +/// +/// This function analyzes an expression and creates a concise string representation +/// that shows the structure of method chains, field accesses, and function calls. +/// It's particularly useful for folding long chained expressions like: +/// `obj.method1()?.field.method2(args)` -> `obj.method1()?.field.method2(…)` +/// +/// The function traverses the expression tree from the outermost expression inward, +/// collecting method names, field names, and call signatures. It accumulates try +/// operators (`?`) and applies them to the appropriate parts of the chain. +/// +/// # Parameters +/// - `expr`: The expression to generate collapsed text for +/// +/// # Returns +/// - `Some(String)`: A dot-separated chain representation if the expression is chainable +/// - `None`: If the expression is not suitable for collapsing (e.g., simple literals) +/// +/// # Examples +/// - `foo.bar().baz?` -> `"foo.bar().baz?"` +/// - `obj.method(arg1, arg2)` -> `"obj.method(…)"` +/// - `value?.field` -> `"value?.field"` +fn collapsed_text_from_expr(mut expr: ast::Expr) -> Option { + let mut names = Vec::new(); + let mut try_marks = String::with_capacity(1); + + let fold_general_expr = |expr: ast::Expr, try_marks: &mut String| { + let text = expr.syntax().text(); + let name = if text.contains_char('\n') { + format!("{try_marks}") + } else { + format!("{text}{try_marks}") + }; + try_marks.clear(); + name + }; + + loop { + let receiver = match expr { + ast::Expr::MethodCallExpr(call) => { + let name = call + .name_ref() + .map(|name| name.text().to_owned()) + .unwrap_or_else(|| "�".into()); + if call.arg_list().and_then(|arg_list| arg_list.args().next()).is_some() { + names.push(format!("{name}(…){try_marks}")); + } else { + names.push(format!("{name}(){try_marks}")); + } + try_marks.clear(); + call.receiver() + } + ast::Expr::FieldExpr(field) => { + let name = match field.field_access() { + Some(ast::FieldKind::Name(name)) => format!("{name}{try_marks}"), + Some(ast::FieldKind::Index(index)) => format!("{index}{try_marks}"), + None => format!("�{try_marks}"), + }; + names.push(name); + try_marks.clear(); + field.expr() + } + ast::Expr::TryExpr(try_expr) => { + try_marks.push('?'); + try_expr.expr() + } + ast::Expr::CallExpr(call) => { + let name = fold_general_expr(call.expr().unwrap(), &mut try_marks); + if call.arg_list().and_then(|arg_list| arg_list.args().next()).is_some() { + names.push(format!("{name}(…){try_marks}")); + } else { + names.push(format!("{name}(){try_marks}")); + } + try_marks.clear(); + None + } + e => { + if names.is_empty() { + return None; + } + names.push(fold_general_expr(e, &mut try_marks)); + None + } + }; + if let Some(receiver) = receiver { + expr = receiver; + } else { + break; + } + } + + Some(names.iter().rev().join(".")) +} + fn contiguous_range_for_item_group( first: N, visited: &mut FxHashSet, @@ -297,7 +496,7 @@ fn contiguous_range_for_comment( } fn fold_range_for_multiline_match_arm(match_arm: ast::MatchArm) -> Option { - if fold_kind(match_arm.expr()?.syntax().kind()).is_some() { + if fold_kind(match_arm.expr()?.syntax().syntax_element()).is_some() { None } else if match_arm.expr()?.syntax().text().contains_char('\n') { Some(match_arm.expr()?.syntax().text_range()) @@ -314,10 +513,33 @@ mod tests { #[track_caller] fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str) { + check_inner(ra_fixture, true); + } + + fn check_without_collapsed_text(#[rust_analyzer::rust_fixture] ra_fixture: &str) { + check_inner(ra_fixture, false); + } + + fn check_inner(ra_fixture: &str, enable_collapsed_text: bool) { let (ranges, text) = extract_tags(ra_fixture, "fold"); + let ranges = ranges + .into_iter() + .map(|(range, text)| { + let (attr, collapsed_text) = match text { + Some(text) => match text.split_once(':') { + Some((attr, collapsed_text)) => { + (Some(attr.to_owned()), Some(collapsed_text.to_owned())) + } + None => (Some(text), None), + }, + None => (None, None), + }; + (range, attr, collapsed_text) + }) + .collect_vec(); let parse = SourceFile::parse(&text, span::Edition::CURRENT); - let mut folds = folding_ranges(&parse.tree()); + let mut folds = folding_ranges(&parse.tree(), enable_collapsed_text); folds.sort_by_key(|fold| (fold.range.start(), fold.range.end())); assert_eq!( @@ -326,7 +548,7 @@ mod tests { "The amount of folds is different than the expected amount" ); - for (fold, (range, attr)) in folds.iter().zip(ranges.into_iter()) { + for (fold, (range, attr, collapsed_text)) in folds.iter().zip(ranges.into_iter()) { assert_eq!(fold.range.start(), range.start(), "mismatched start of folding ranges"); assert_eq!(fold.range.end(), range.end(), "mismatched end of folding ranges"); @@ -346,8 +568,15 @@ mod tests { FoldKind::MatchArm => "matcharm", FoldKind::Function => "function", FoldKind::ExternCrates => "externcrates", + FoldKind::Stmt => "stmt", + FoldKind::TailExpr => "tailexpr", }; assert_eq!(kind, &attr.unwrap()); + if enable_collapsed_text { + assert_eq!(fold.collapsed_text, collapsed_text); + } else { + assert_eq!(fold.collapsed_text, None); + } } } @@ -511,10 +740,10 @@ macro_rules! foo { check( r#" fn main() { - match 0 { + match 0 { 0 => 0, _ => 1, - } + } } "#, ); @@ -525,7 +754,7 @@ fn main() { check( r#" fn main() { - match foo { + match foo { block => { }, matcharm => some. @@ -544,7 +773,7 @@ fn main() { structS => StructS { a: 31, }, - } + } } "#, ) @@ -555,11 +784,11 @@ fn main() { check( r#" fn main() { - frobnicate( + frobnicate( 1, 2, 3, - ) + ) } "#, ) @@ -698,4 +927,49 @@ type Foo = foo< "#, ); } + fn test_fold_tail_expr() { + check( + r#" +fn f() { + let x = 1; + + some_function() + .chain() + .method() +} +"#, + ) + } + + #[test] + fn test_fold_let_stmt_with_chained_methods() { + check( + r#" +fn main() { + let result = some_value + .method1() + .method2()? + .method3(); + + println!("{}", result); +} +"#, + ) + } + + #[test] + fn test_fold_let_stmt_with_chained_methods_without_collapsed_text() { + check_without_collapsed_text( + r#" +fn main() { + let result = some_value + .method1() + .method2()? + .method3(); + + println!("{}", result); +} +"#, + ) + } } diff --git a/src/tools/rust-analyzer/crates/ide/src/lib.rs b/src/tools/rust-analyzer/crates/ide/src/lib.rs index 930eaf2262d93..be0b96d7832de 100644 --- a/src/tools/rust-analyzer/crates/ide/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide/src/lib.rs @@ -501,12 +501,15 @@ impl Analysis { } /// Returns the set of folding ranges. - pub fn folding_ranges(&self, file_id: FileId) -> Cancellable> { + pub fn folding_ranges(&self, file_id: FileId, collapsed_text: bool) -> Cancellable> { self.with_db(|db| { let editioned_file_id_wrapper = EditionedFileId::current_edition_guess_origin(&self.db, file_id); - folding_ranges::folding_ranges(&db.parse(editioned_file_id_wrapper).tree()) + folding_ranges::folding_ranges( + &db.parse(editioned_file_id_wrapper).tree(), + collapsed_text, + ) }) } diff --git a/src/tools/rust-analyzer/crates/ide/src/static_index.rs b/src/tools/rust-analyzer/crates/ide/src/static_index.rs index aba6b64f977a5..6dd73e4e26c50 100644 --- a/src/tools/rust-analyzer/crates/ide/src/static_index.rs +++ b/src/tools/rust-analyzer/crates/ide/src/static_index.rs @@ -159,7 +159,7 @@ pub enum VendoredLibrariesConfig<'a> { impl StaticIndex<'_> { fn add_file(&mut self, file_id: FileId) { let current_crate = crates_for(self.db, file_id).pop().map(Into::into); - let folds = self.analysis.folding_ranges(file_id).unwrap(); + let folds = self.analysis.folding_ranges(file_id, true).unwrap(); let inlay_hints = self .analysis .inlay_hints( diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs index ad07da77597de..2cb7825f8ef3b 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs @@ -1264,11 +1264,15 @@ pub(crate) fn handle_folding_range( params: FoldingRangeParams, ) -> anyhow::Result>> { let _p = tracing::info_span!("handle_folding_range").entered(); + let file_id = try_default!(from_proto::file_id(&snap, ¶ms.text_document.uri)?); - let folds = snap.analysis.folding_ranges(file_id)?; + let collapsed_text = snap.config.folding_range_collapsed_text(); + let folds = snap.analysis.folding_ranges(file_id, collapsed_text)?; + let text = snap.analysis.file_text(file_id)?; let line_index = snap.file_line_index(file_id)?; let line_folding_only = snap.config.line_folding_only(); + let res = folds .into_iter() .map(|it| to_proto::folding_range(&text, &line_index, line_folding_only, it)) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/capabilities.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/capabilities.rs index d6a694be9121e..3ad4cb70b419b 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/capabilities.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/capabilities.rs @@ -335,6 +335,20 @@ impl ClientCapabilities { .unwrap_or_default() } + pub fn folding_range_collapsed_text(&self) -> bool { + (|| -> _ { + self.0 + .text_document + .as_ref()? + .folding_range + .as_ref()? + .folding_range + .as_ref()? + .collapsed_text + })() + .unwrap_or_default() + } + pub fn hierarchical_symbols(&self) -> bool { (|| -> _ { self.0 diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs index 6f0f57725fc7a..ea613ec656601 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs @@ -907,9 +907,9 @@ pub(crate) fn folding_range( text: &str, line_index: &LineIndex, line_folding_only: bool, - fold: Fold, + Fold { range: text_range, kind, collapsed_text }: Fold, ) -> lsp_types::FoldingRange { - let kind = match fold.kind { + let kind = match kind { FoldKind::Comment => Some(lsp_types::FoldingRangeKind::Comment), FoldKind::Imports => Some(lsp_types::FoldingRangeKind::Imports), FoldKind::Region => Some(lsp_types::FoldingRangeKind::Region), @@ -924,17 +924,19 @@ pub(crate) fn folding_range( | FoldKind::Array | FoldKind::ExternCrates | FoldKind::MatchArm - | FoldKind::Function => None, + | FoldKind::Function + | FoldKind::Stmt + | FoldKind::TailExpr => None, }; - let range = range(line_index, fold.range); + let range = range(line_index, text_range); if line_folding_only { // Clients with line_folding_only == true (such as VSCode) will fold the whole end line // even if it contains text not in the folding range. To prevent that we exclude // range.end.line from the folding region if there is more text after range.end // on the same line. - let has_more_text_on_end_line = text[TextRange::new(fold.range.end(), TextSize::of(text))] + let has_more_text_on_end_line = text[TextRange::new(text_range.end(), TextSize::of(text))] .chars() .take_while(|it| *it != '\n') .any(|it| !it.is_whitespace()); @@ -951,7 +953,7 @@ pub(crate) fn folding_range( end_line, end_character: None, kind, - collapsed_text: None, + collapsed_text, } } else { lsp_types::FoldingRange { @@ -960,7 +962,7 @@ pub(crate) fn folding_range( end_line: range.end.line, end_character: Some(range.end.character), kind, - collapsed_text: None, + collapsed_text, } } } @@ -2031,8 +2033,8 @@ fn main() { }"#; let (analysis, file_id) = Analysis::from_single_file(text.to_owned()); - let folds = analysis.folding_ranges(file_id).unwrap(); - assert_eq!(folds.len(), 4); + let folds = analysis.folding_ranges(file_id, true).unwrap(); + assert_eq!(folds.len(), 5); let line_index = LineIndex { index: Arc::new(ide::LineIndex::new(text)), @@ -2042,7 +2044,7 @@ fn main() { let converted: Vec = folds.into_iter().map(|it| folding_range(text, &line_index, true, it)).collect(); - let expected_lines = [(0, 2), (4, 10), (5, 6), (7, 9)]; + let expected_lines = [(0, 2), (4, 10), (5, 9), (5, 6), (7, 9)]; assert_eq!(converted.len(), expected_lines.len()); for (folding_range, (start_line, end_line)) in converted.iter().zip(expected_lines.iter()) { assert_eq!(folding_range.start_line, *start_line); From d2634dcfa7b5317ebdfe60ceb897d5d662e0b789 Mon Sep 17 00:00:00 2001 From: roifewu Date: Tue, 1 Jul 2025 05:03:53 +0800 Subject: [PATCH 003/144] refactor: enhance folding range handling for statements and tail expressions --- .../crates/ide/src/folding_ranges.rs | 305 +++++++----------- .../crates/rust-analyzer/src/lsp/to_proto.rs | 11 +- 2 files changed, 132 insertions(+), 184 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide/src/folding_ranges.rs b/src/tools/rust-analyzer/crates/ide/src/folding_ranges.rs index ebd6c274a2ecd..375e42cc833ee 100644 --- a/src/tools/rust-analyzer/crates/ide/src/folding_ranges.rs +++ b/src/tools/rust-analyzer/crates/ide/src/folding_ranges.rs @@ -1,10 +1,9 @@ use ide_db::{FxHashSet, syntax_helpers::node_ext::vis_eq}; -use itertools::Itertools; use syntax::{ Direction, NodeOrToken, SourceFile, SyntaxElement, SyntaxKind::*, SyntaxNode, TextRange, TextSize, - ast::{self, AstNode, AstToken, HasArgList, edit::AstNodeEdit}, + ast::{self, AstNode, AstToken}, match_ast, syntax_editor::Element, }; @@ -14,7 +13,7 @@ use std::hash::Hash; const REGION_START: &str = "// region:"; const REGION_END: &str = "// endregion"; -#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[derive(Debug, PartialEq, Eq)] pub enum FoldKind { Comment, Imports, @@ -31,11 +30,10 @@ pub enum FoldKind { Consts, Statics, TypeAliases, - TraitAliases, ExternCrates, // endregion: item runs - Stmt, - TailExpr, + Stmt(ast::Stmt), + TailExpr(ast::Expr), } #[derive(Debug)] @@ -50,8 +48,8 @@ impl Fold { Self { range, kind, collapsed_text: None } } - pub fn with_text(mut self, text: String) -> Self { - self.collapsed_text = Some(text); + pub fn with_text(mut self, text: Option) -> Self { + self.collapsed_text = text; self } } @@ -60,7 +58,7 @@ impl Fold { // // Defines folding regions for curly braced blocks, runs of consecutive use, mod, const or static // items, and `region` / `endregion` comment markers. -pub(crate) fn folding_ranges(file: &SourceFile, collapsed_text: bool) -> Vec { +pub(crate) fn folding_ranges(file: &SourceFile, add_collapsed_text: bool) -> Vec { let mut res = vec![]; let mut visited_comments = FxHashSet::default(); let mut visited_nodes = FxHashSet::default(); @@ -94,12 +92,17 @@ pub(crate) fn folding_ranges(file: &SourceFile, collapsed_text: bool) -> Vec Vec { - if let Some(range) = contiguous_range_for_item_group(alias, &mut visited_nodes) { - res.push(Fold::new(range, FoldKind::TraitAliases)); - } - }, ast::ExternCrate(extern_crate) => { if let Some(range) = contiguous_range_for_item_group(extern_crate, &mut visited_nodes) { res.push(Fold::new(range, FoldKind::ExternCrates)); @@ -185,90 +183,63 @@ pub(crate) fn folding_ranges(file: &SourceFile, collapsed_text: bool) -> Vec Fold { - if !collapsed_text { - return Fold::new(element.text_range(), kind); - } - - let fold_with_collapsed_text = match kind { - FoldKind::TailExpr => { - let expr = ast::Expr::cast(element.as_node().unwrap().clone()).unwrap(); - - let indent_level = expr.indent_level().0; - let indents = " ".repeat(indent_level as usize); - - let mut fold = Fold::new(element.text_range(), kind); - if let Some(collapsed_expr) = collapsed_text_from_expr(expr) { - fold = fold.with_text(format!("{indents}{collapsed_expr}")); - } - Some(fold) - } - FoldKind::Stmt => 'blk: { - let node = element.as_node().unwrap(); - - match_ast! { - match node { - ast::ExprStmt(expr) => { - let Some(expr) = expr.expr() else { - break 'blk None; - }; - - let indent_level = expr.indent_level().0; - let indents = " ".repeat(indent_level as usize); - - let mut fold = Fold::new(element.text_range(), kind); - if let Some(collapsed_expr) = collapsed_text_from_expr(expr) { - fold = fold.with_text(format!("{indents}{collapsed_expr};")); - } - Some(fold) - }, - ast::LetStmt(let_stmt) => { - if let_stmt.let_else().is_some() { - break 'blk None; - } - - let Some(expr) = let_stmt.initializer() else { - break 'blk None; - }; - - let expr_offset = - expr.syntax().text_range().start() - let_stmt.syntax().text_range().start(); - let text_before_expr = let_stmt.syntax().text().slice(..expr_offset); - if text_before_expr.contains_char('\n') { - break 'blk None; - } +fn collapsed_text(kind: &FoldKind) -> Option { + match kind { + FoldKind::TailExpr(expr) => collapse_expr(expr.clone()), + FoldKind::Stmt(stmt) => { + match stmt { + ast::Stmt::ExprStmt(expr_stmt) => { + expr_stmt.expr().and_then(collapse_expr).map(|text| format!("{text};")) + } + ast::Stmt::LetStmt(let_stmt) => 'blk: { + if let_stmt.let_else().is_some() { + break 'blk None; + } - let indent_level = let_stmt.indent_level().0; - let indents = " ".repeat(indent_level as usize); + let Some(expr) = let_stmt.initializer() else { + break 'blk None; + }; + + // If the `let` statement spans multiple lines, we do not collapse it. + // We use the `eq_token` to check whether the `let` statement is a single line, + // as the formatter may place the initializer on a new line for better readability. + // + // Example: + // ```rust + // let complex_pat = + // complex_expr; + // ``` + // + // In this case, we should generate the collapsed text. + let Some(eq_token) = let_stmt.eq_token() else { + break 'blk None; + }; + let eq_token_offset = + eq_token.text_range().end() - let_stmt.syntax().text_range().start(); + let text_until_eq_token = let_stmt.syntax().text().slice(..eq_token_offset); + if text_until_eq_token.contains_char('\n') { + break 'blk None; + } - let mut fold = Fold::new(element.text_range(), kind); - if let Some(collapsed_expr) = collapsed_text_from_expr(expr) { - fold = fold.with_text(format!("{indents}{text_before_expr}{collapsed_expr};")); - } - Some(fold) - }, - _ => None, + collapse_expr(expr).map(|text| format!("{text_until_eq_token} {text};")) } + // handling `items` in external matches. + ast::Stmt::Item(_) => None, } } _ => None, - }; - - fold_with_collapsed_text.unwrap_or_else(|| Fold::new(element.text_range(), kind)) + } } fn fold_kind(element: SyntaxElement) -> Option { // handle tail_expr if let Some(node) = element.as_node() - && let Some(block) = node.parent().and_then(|it| it.parent()).and_then(ast::BlockExpr::cast) // tail_expr -> stmt_list -> block - && block.tail_expr().is_some_and(|tail| tail.syntax() == node) + // tail_expr -> stmt_list -> block + && let Some(block) = node.parent().and_then(|it| it.parent()).and_then(ast::BlockExpr::cast) + && let Some(tail_expr) = block.tail_expr() + && tail_expr.syntax() == node { - return Some(FoldKind::TailExpr); + return Some(FoldKind::TailExpr(tail_expr)); } match element.kind() { @@ -289,103 +260,71 @@ fn fold_kind(element: SyntaxElement) -> Option { | MATCH_ARM_LIST | VARIANT_LIST | TOKEN_TREE => Some(FoldKind::Block), - EXPR_STMT | LET_STMT => Some(FoldKind::Stmt), + EXPR_STMT | LET_STMT => Some(FoldKind::Stmt(ast::Stmt::cast(element.as_node()?.clone())?)), _ => None, } } -/// Generates a collapsed text representation of a chained expression. -/// -/// This function analyzes an expression and creates a concise string representation -/// that shows the structure of method chains, field accesses, and function calls. -/// It's particularly useful for folding long chained expressions like: -/// `obj.method1()?.field.method2(args)` -> `obj.method1()?.field.method2(…)` -/// -/// The function traverses the expression tree from the outermost expression inward, -/// collecting method names, field names, and call signatures. It accumulates try -/// operators (`?`) and applies them to the appropriate parts of the chain. -/// -/// # Parameters -/// - `expr`: The expression to generate collapsed text for -/// -/// # Returns -/// - `Some(String)`: A dot-separated chain representation if the expression is chainable -/// - `None`: If the expression is not suitable for collapsing (e.g., simple literals) -/// -/// # Examples -/// - `foo.bar().baz?` -> `"foo.bar().baz?"` -/// - `obj.method(arg1, arg2)` -> `"obj.method(…)"` -/// - `value?.field` -> `"value?.field"` -fn collapsed_text_from_expr(mut expr: ast::Expr) -> Option { - let mut names = Vec::new(); - let mut try_marks = String::with_capacity(1); - - let fold_general_expr = |expr: ast::Expr, try_marks: &mut String| { - let text = expr.syntax().text(); - let name = if text.contains_char('\n') { - format!("{try_marks}") - } else { - format!("{text}{try_marks}") - }; - try_marks.clear(); - name - }; - - loop { - let receiver = match expr { - ast::Expr::MethodCallExpr(call) => { - let name = call - .name_ref() - .map(|name| name.text().to_owned()) - .unwrap_or_else(|| "�".into()); - if call.arg_list().and_then(|arg_list| arg_list.args().next()).is_some() { - names.push(format!("{name}(…){try_marks}")); - } else { - names.push(format!("{name}(){try_marks}")); - } - try_marks.clear(); - call.receiver() - } - ast::Expr::FieldExpr(field) => { - let name = match field.field_access() { - Some(ast::FieldKind::Name(name)) => format!("{name}{try_marks}"), - Some(ast::FieldKind::Index(index)) => format!("{index}{try_marks}"), - None => format!("�{try_marks}"), - }; - names.push(name); - try_marks.clear(); - field.expr() - } - ast::Expr::TryExpr(try_expr) => { - try_marks.push('?'); - try_expr.expr() - } - ast::Expr::CallExpr(call) => { - let name = fold_general_expr(call.expr().unwrap(), &mut try_marks); - if call.arg_list().and_then(|arg_list| arg_list.args().next()).is_some() { - names.push(format!("{name}(…){try_marks}")); - } else { - names.push(format!("{name}(){try_marks}")); +const COLLAPSE_EXPR_MAX_LEN: usize = 100; + +fn collapse_expr(expr: ast::Expr) -> Option { + let mut text = String::with_capacity(COLLAPSE_EXPR_MAX_LEN * 2); + + let mut preorder = expr.syntax().preorder_with_tokens(); + while let Some(element) = preorder.next() { + match element { + syntax::WalkEvent::Enter(NodeOrToken::Node(node)) => { + if let Some(arg_list) = ast::ArgList::cast(node.clone()) { + let content = if arg_list.args().next().is_some() { "(…)" } else { "()" }; + text.push_str(content); + preorder.skip_subtree(); + } else if let Some(expr) = ast::Expr::cast(node) { + match expr { + ast::Expr::AwaitExpr(_) + | ast::Expr::BecomeExpr(_) + | ast::Expr::BinExpr(_) + | ast::Expr::BreakExpr(_) + | ast::Expr::CallExpr(_) + | ast::Expr::CastExpr(_) + | ast::Expr::ContinueExpr(_) + | ast::Expr::FieldExpr(_) + | ast::Expr::IndexExpr(_) + | ast::Expr::LetExpr(_) + | ast::Expr::Literal(_) + | ast::Expr::MethodCallExpr(_) + | ast::Expr::OffsetOfExpr(_) + | ast::Expr::ParenExpr(_) + | ast::Expr::PathExpr(_) + | ast::Expr::PrefixExpr(_) + | ast::Expr::RangeExpr(_) + | ast::Expr::RefExpr(_) + | ast::Expr::ReturnExpr(_) + | ast::Expr::TryExpr(_) + | ast::Expr::UnderscoreExpr(_) + | ast::Expr::YeetExpr(_) + | ast::Expr::YieldExpr(_) => {} + + // Some other exprs (e.g. `while` loop) are too complex to have a collapsed text + _ => return None, + } } - try_marks.clear(); - None } - e => { - if names.is_empty() { - return None; + syntax::WalkEvent::Enter(NodeOrToken::Token(token)) => { + if !token.kind().is_trivia() { + text.push_str(token.text()); } - names.push(fold_general_expr(e, &mut try_marks)); - None } - }; - if let Some(receiver) = receiver { - expr = receiver; - } else { - break; + syntax::WalkEvent::Leave(_) => {} + } + + if text.len() > COLLAPSE_EXPR_MAX_LEN { + return None; } } - Some(names.iter().rev().join(".")) + text.shrink_to_fit(); + + Some(text) } fn contiguous_range_for_item_group( @@ -522,7 +461,7 @@ mod tests { fn check_inner(ra_fixture: &str, enable_collapsed_text: bool) { let (ranges, text) = extract_tags(ra_fixture, "fold"); - let ranges = ranges + let ranges: Vec<_> = ranges .into_iter() .map(|(range, text)| { let (attr, collapsed_text) = match text { @@ -536,7 +475,7 @@ mod tests { }; (range, attr, collapsed_text) }) - .collect_vec(); + .collect(); let parse = SourceFile::parse(&text, span::Edition::CURRENT); let mut folds = folding_ranges(&parse.tree(), enable_collapsed_text); @@ -568,8 +507,8 @@ mod tests { FoldKind::MatchArm => "matcharm", FoldKind::Function => "function", FoldKind::ExternCrates => "externcrates", - FoldKind::Stmt => "stmt", - FoldKind::TailExpr => "tailexpr", + FoldKind::Stmt(_) => "stmt", + FoldKind::TailExpr(_) => "tailexpr", }; assert_eq!(kind, &attr.unwrap()); if enable_collapsed_text { @@ -784,7 +723,7 @@ fn main() { check( r#" fn main() { - frobnicate( + frobnicate( 1, 2, 3, @@ -927,13 +866,15 @@ type Foo = foo< "#, ); } + + #[test] fn test_fold_tail_expr() { check( r#" fn f() { let x = 1; - some_function() + some_function() .chain() .method() } @@ -946,7 +887,7 @@ fn f() { check( r#" fn main() { - let result = some_value + let result = some_value .method1() .method2()? .method3(); @@ -970,6 +911,6 @@ fn main() { println!("{}", result); } "#, - ) + ) } } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs index ea613ec656601..f6c16c8fde48a 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs @@ -925,8 +925,8 @@ pub(crate) fn folding_range( | FoldKind::ExternCrates | FoldKind::MatchArm | FoldKind::Function - | FoldKind::Stmt - | FoldKind::TailExpr => None, + | FoldKind::Stmt(_) + | FoldKind::TailExpr(_) => None, }; let range = range(line_index, text_range); @@ -947,6 +947,13 @@ pub(crate) fn folding_range( range.end.line }; + let collapsed_text = collapsed_text.map(|collapsed_text| { + let range_start = text_range.start(); + let line_start = range_start - TextSize::from(range.start.character); + let text_before_range = &text[TextRange::new(line_start, range_start)]; + format!("{text_before_range}{collapsed_text}") + }); + lsp_types::FoldingRange { start_line: range.start.line, start_character: None, From b6b3ebd59913302b59c23d876fcac96155b07ee3 Mon Sep 17 00:00:00 2001 From: Roberto Aloi Date: Wed, 21 Jan 2026 11:53:46 +0100 Subject: [PATCH 004/144] Bump perf-event from 0.4.7. to 0.4.8 --- src/tools/rust-analyzer/Cargo.lock | 8 ++--- .../rust-analyzer/crates/profile/Cargo.toml | 4 +-- .../crates/profile/src/stop_watch.rs | 32 +++++++++++++++---- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/tools/rust-analyzer/Cargo.lock b/src/tools/rust-analyzer/Cargo.lock index a2a18cf8eeea1..d7ddd1db3a971 100644 --- a/src/tools/rust-analyzer/Cargo.lock +++ b/src/tools/rust-analyzer/Cargo.lock @@ -1731,9 +1731,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "perf-event" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5396562cd2eaa828445d6d34258ae21ee1eb9d40fe626ca7f51c8dccb4af9d66" +checksum = "b4d6393d9238342159080d79b78cb59c67399a8e7ecfa5d410bd614169e4e823" dependencies = [ "libc", "perf-event-open-sys", @@ -1741,9 +1741,9 @@ dependencies = [ [[package]] name = "perf-event-open-sys" -version = "1.0.1" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce9bedf5da2c234fdf2391ede2b90fabf585355f33100689bc364a3ea558561a" +checksum = "7c44fb1c7651a45a3652c4afc6e754e40b3d6e6556f1487e2b230bfc4f33c2a8" dependencies = [ "libc", ] diff --git a/src/tools/rust-analyzer/crates/profile/Cargo.toml b/src/tools/rust-analyzer/crates/profile/Cargo.toml index 4828419003a60..8377e94c8d608 100644 --- a/src/tools/rust-analyzer/crates/profile/Cargo.toml +++ b/src/tools/rust-analyzer/crates/profile/Cargo.toml @@ -16,8 +16,8 @@ doctest = false cfg-if = "1.0.1" jemalloc-ctl = { version = "0.5.4", package = "tikv-jemalloc-ctl", optional = true } -[target.'cfg(all(target_os = "linux", not(target_env = "ohos")))'.dependencies] -perf-event = "=0.4.7" +[target.'cfg(all(target_os = "linux", not(target_env = "ohos"), any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))'.dependencies] +perf-event = "=0.4.8" [target.'cfg(all(target_os = "linux", target_env = "gnu"))'.dependencies] libc.workspace = true diff --git a/src/tools/rust-analyzer/crates/profile/src/stop_watch.rs b/src/tools/rust-analyzer/crates/profile/src/stop_watch.rs index 00c37c01d25e2..a1c1383ad5395 100644 --- a/src/tools/rust-analyzer/crates/profile/src/stop_watch.rs +++ b/src/tools/rust-analyzer/crates/profile/src/stop_watch.rs @@ -11,7 +11,11 @@ use crate::MemoryUsage; pub struct StopWatch { time: Instant, - #[cfg(all(target_os = "linux", not(target_env = "ohos")))] + #[cfg(all( + target_os = "linux", + not(target_env = "ohos"), + any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64") + ))] counter: Option, memory: MemoryUsage, } @@ -24,7 +28,11 @@ pub struct StopWatchSpan { impl StopWatch { pub fn start() -> StopWatch { - #[cfg(all(target_os = "linux", not(target_env = "ohos")))] + #[cfg(all( + target_os = "linux", + not(target_env = "ohos"), + any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64") + ))] let counter = { // When debugging rust-analyzer using rr, the perf-related syscalls cause it to abort. // We allow disabling perf by setting the env var `RA_DISABLE_PERF`. @@ -51,7 +59,11 @@ impl StopWatch { let time = Instant::now(); StopWatch { time, - #[cfg(all(target_os = "linux", not(target_env = "ohos")))] + #[cfg(all( + target_os = "linux", + not(target_env = "ohos"), + any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64") + ))] counter, memory, } @@ -60,13 +72,19 @@ impl StopWatch { pub fn elapsed(&mut self) -> StopWatchSpan { let time = self.time.elapsed(); - #[cfg(all(target_os = "linux", not(target_env = "ohos")))] + #[cfg(all( + target_os = "linux", + not(target_env = "ohos"), + any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64") + ))] let instructions = self.counter.as_mut().and_then(|it| { it.read().map_err(|err| eprintln!("Failed to read perf counter: {err}")).ok() }); - #[cfg(all(target_os = "linux", target_env = "ohos"))] - let instructions = None; - #[cfg(not(target_os = "linux"))] + #[cfg(not(all( + target_os = "linux", + not(target_env = "ohos"), + any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64") + )))] let instructions = None; let memory = MemoryUsage::now() - self.memory; From 8655e5c00b9a059becac6d83c6e9de04a72bde64 Mon Sep 17 00:00:00 2001 From: Hash Date: Sun, 25 Jan 2026 01:01:02 +0800 Subject: [PATCH 005/144] Publish no-server to Code Marketplace and OpenVSX fix https://github.com/rust-lang/rust-analyzer/issues/18578 I believe it won't break anything. --- src/tools/rust-analyzer/.github/workflows/release.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/tools/rust-analyzer/.github/workflows/release.yaml b/src/tools/rust-analyzer/.github/workflows/release.yaml index 28914118de940..8fe07ce97573a 100644 --- a/src/tools/rust-analyzer/.github/workflows/release.yaml +++ b/src/tools/rust-analyzer/.github/workflows/release.yaml @@ -264,8 +264,6 @@ jobs: name: ${{ env.TAG }} token: ${{ secrets.GITHUB_TOKEN }} - - run: rm dist/rust-analyzer-no-server.vsix - - run: npm ci working-directory: ./editors/code From ab001b9b8912288f32d1b6c478f3b60f5d7041c5 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Mon, 2 Mar 2026 08:18:17 +0800 Subject: [PATCH 006/144] Fix extract function invalid self param Example --- ```rust trait Foo { fn f(&self) -> i32; fn foo(&self) -> i32 { $0self.f()+self.f()$0 } } ``` **Before this PR** ```rust trait Foo { fn f(&self) -> i32; fn foo(&self) -> i32 { fun_name(self) } } fn $0fun_name(&self) -> i32 { self.f()+self.f() } ``` **After this PR** ```rust trait Foo { fn f(&self) -> i32; fn foo(&self) -> i32 { fun_name(self) } } fn $0fun_name(this: &impl Foo) -> i32 { this.f()+this.f() } ``` --- .../src/handlers/extract_function.rs | 108 +++++++++++++++--- 1 file changed, 93 insertions(+), 15 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs index 124ef509fb895..14cb145ceac30 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs @@ -96,7 +96,8 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op let module = semantics_scope.module(); let edition = semantics_scope.krate().edition(ctx.db()); - let (container_info, contains_tail_expr) = body.analyze_container(&ctx.sema, edition)?; + let (container_info, contains_tail_expr) = + body.analyze_container(&ctx.sema, edition, &insert_after)?; let ret_ty = body.return_ty(ctx)?; let control_flow = body.external_control_flow(ctx, &container_info)?; @@ -181,6 +182,7 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op builder.add_tabstop_before(cap, name); } + // FIXME: wrap non-adt types let fn_def = match fun.self_param_adt(ctx) { Some(adt) if anchor == Anchor::Method && !has_impl_wrapper => { fn_def.indent(1.into()); @@ -377,6 +379,7 @@ struct ControlFlow<'db> { struct ContainerInfo<'db> { is_const: bool, parent_loop: Option, + trait_name: Option, /// The function's return type, const's type etc. ret_type: Option>, generic_param_lists: Vec, @@ -838,6 +841,7 @@ impl FunctionBody { &self, sema: &Semantics<'db, RootDatabase>, edition: Edition, + insert_after: &SyntaxNode, ) -> Option<(ContainerInfo<'db>, bool)> { let mut ancestors = self.parent()?.ancestors(); let infer_expr_opt = |expr| sema.type_of_expr(&expr?).map(TypeInfo::adjusted); @@ -924,6 +928,9 @@ impl FunctionBody { false }; + let trait_name = ast::Trait::cast(insert_after.clone()) + .and_then(|trait_| Some(make::ty_path(make::ext::ident_path(&trait_.name()?.text())))); + let parent = self.parent()?; let parents = generic_parents(&parent); let generic_param_lists = parents.iter().filter_map(|it| it.generic_param_list()).collect(); @@ -934,6 +941,7 @@ impl FunctionBody { ContainerInfo { is_const, parent_loop, + trait_name, ret_type: ty, generic_param_lists, where_clauses, @@ -1419,14 +1427,18 @@ fn fixup_call_site(builder: &mut SourceChangeBuilder, body: &FunctionBody) { fn make_call(ctx: &AssistContext<'_>, fun: &Function<'_>, indent: IndentLevel) -> SyntaxNode { let ret_ty = fun.return_type(ctx); - let args = make::arg_list(fun.params.iter().map(|param| param.to_arg(ctx, fun.mods.edition))); let name = fun.name.clone(); - let mut call_expr = if fun.self_param.is_some() { + let args = fun.params.iter().map(|param| param.to_arg(ctx, fun.mods.edition)); + let mut call_expr = if fun.make_this_param().is_some() { + let self_arg = make::expr_path(make::ext::ident_path("self")); + let func = make::expr_path(make::path_unqualified(make::path_segment(name))); + make::expr_call(func, make::arg_list(Some(self_arg).into_iter().chain(args))).into() + } else if fun.self_param.is_some() { let self_arg = make::expr_path(make::ext::ident_path("self")); - make::expr_method_call(self_arg, name, args).into() + make::expr_method_call(self_arg, name, make::arg_list(args)).into() } else { let func = make::expr_path(make::path_unqualified(make::path_segment(name))); - make::expr_call(func, args).into() + make::expr_call(func, make::arg_list(args)).into() }; let handler = FlowHandler::from_ret_ty(fun, &ret_ty); @@ -1729,9 +1741,26 @@ impl<'db> Function<'db> { module: hir::Module, edition: Edition, ) -> ast::ParamList { - let self_param = self.self_param.clone(); + let this_param = self.make_this_param(); + let self_param = self.self_param.clone().filter(|_| this_param.is_none()); let params = self.params.iter().map(|param| param.to_param(ctx, module, edition)); - make::param_list(self_param, params) + make::param_list(self_param, this_param.into_iter().chain(params)) + } + + fn make_this_param(&self) -> Option { + if let Some(name) = self.mods.trait_name.clone() + && let Some(self_param) = &self.self_param + { + let bounds = make::type_bound_list([make::type_bound(name)]); + let pat = make::path_pat(make::ext::ident_path("this")); + let mut ty = make::impl_trait_type(bounds.unwrap()).into(); + if self_param.amp_token().is_some() { + ty = make::ty_ref(ty, self_param.mut_token().is_some()); + } + Some(make::param(pat, ty)) + } else { + None + } } fn make_ret_ty(&self, ctx: &AssistContext<'_>, module: hir::Module) -> Option { @@ -1806,10 +1835,12 @@ fn make_body( ) -> ast::BlockExpr { let ret_ty = fun.return_type(ctx); let handler = FlowHandler::from_ret_ty(fun, &ret_ty); + let to_this_param = fun.self_param.clone().filter(|_| fun.make_this_param().is_some()); let block = match &fun.body { FunctionBody::Expr(expr) => { - let expr = rewrite_body_segment(ctx, &fun.params, &handler, expr.syntax()); + let expr = + rewrite_body_segment(ctx, to_this_param, &fun.params, &handler, expr.syntax()); let expr = ast::Expr::cast(expr).expect("Body segment should be an expr"); match expr { ast::Expr::BlockExpr(block) => { @@ -1847,7 +1878,7 @@ fn make_body( .filter(|it| text_range.contains_range(it.text_range())) .map(|it| match &it { syntax::NodeOrToken::Node(n) => syntax::NodeOrToken::Node( - rewrite_body_segment(ctx, &fun.params, &handler, n), + rewrite_body_segment(ctx, to_this_param.clone(), &fun.params, &handler, n), ), _ => it, }) @@ -1997,11 +2028,13 @@ fn make_ty(ty: &hir::Type<'_>, ctx: &AssistContext<'_>, module: hir::Module) -> fn rewrite_body_segment( ctx: &AssistContext<'_>, + to_this_param: Option, params: &[Param<'_>], handler: &FlowHandler<'_>, syntax: &SyntaxNode, ) -> SyntaxNode { - let syntax = fix_param_usages(ctx, params, syntax); + let to_this_param = to_this_param.and_then(|it| ctx.sema.to_def(&it)); + let syntax = fix_param_usages(ctx, to_this_param, params, syntax); update_external_control_flow(handler, &syntax); syntax } @@ -2009,30 +2042,46 @@ fn rewrite_body_segment( /// change all usages to account for added `&`/`&mut` for some params fn fix_param_usages( ctx: &AssistContext<'_>, + to_this_param: Option, params: &[Param<'_>], syntax: &SyntaxNode, ) -> SyntaxNode { let mut usages_for_param: Vec<(&Param<'_>, Vec)> = Vec::new(); + let mut usages_for_self_param: Vec = Vec::new(); let tm = TreeMutator::new(syntax); + let reference_filter = |reference: &FileReference| { + syntax + .text_range() + .contains_range(reference.range) + .then_some(()) + .and_then(|_| path_element_of_reference(syntax, reference)) + .map(|expr| tm.make_mut(&expr)) + }; + if let Some(self_param) = to_this_param { + usages_for_self_param = LocalUsages::find_local_usages(ctx, self_param) + .iter() + .filter_map(reference_filter) + .collect(); + } for param in params { if !param.kind().is_ref() { continue; } let usages = LocalUsages::find_local_usages(ctx, param.var); - let usages = usages - .iter() - .filter(|reference| syntax.text_range().contains_range(reference.range)) - .filter_map(|reference| path_element_of_reference(syntax, reference)) - .map(|expr| tm.make_mut(&expr)); + let usages = usages.iter().filter_map(reference_filter); usages_for_param.push((param, usages.unique().collect())); } let res = tm.make_syntax_mut(syntax); + for self_usage in usages_for_self_param { + let this_expr = make::expr_path(make::ext::ident_path("this")).clone_for_update(); + ted::replace(self_usage.syntax(), this_expr.syntax()); + } for (param, usages) in usages_for_param { for usage in usages { match usage.syntax().ancestors().skip(1).find_map(ast::Expr::cast) { @@ -2939,6 +2988,35 @@ impl S { ); } + #[test] + fn method_in_trait() { + check_assist( + extract_function, + r#" +trait Foo { + fn f(&self) -> i32; + + fn foo(&self) -> i32 { + $0self.f()+self.f()$0 + } +} +"#, + r#" +trait Foo { + fn f(&self) -> i32; + + fn foo(&self) -> i32 { + fun_name(self) + } +} + +fn $0fun_name(this: &impl Foo) -> i32 { + this.f()+this.f() +} +"#, + ); + } + #[test] fn variable_defined_inside_and_used_after_no_ret() { check_assist( From b5739f152eeadc7a9cabfc350dc1319ea91d14be Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Mon, 2 Mar 2026 11:02:54 +0800 Subject: [PATCH 007/144] Add a fixme --- .../crates/ide-assists/src/handlers/extract_function.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs index 14cb145ceac30..549676aa266e1 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs @@ -928,6 +928,7 @@ impl FunctionBody { false }; + // FIXME: make trait arguments let trait_name = ast::Trait::cast(insert_after.clone()) .and_then(|trait_| Some(make::ty_path(make::ext::ident_path(&trait_.name()?.text())))); From f225909fc6722f187f5740b94cbd7b3037d0c534 Mon Sep 17 00:00:00 2001 From: Wilfred Hughes Date: Tue, 3 Mar 2026 13:29:28 +0000 Subject: [PATCH 008/144] fix: Stale diagnostics when a custom check command is configured We can't use the flycheck scope, because that value varies depending on how the flycheck was triggered. See also rust-lang/rust-analyzer#21571, which was reverted due to issues with scope. Instead, treat empty diagnostics as a flycheck for the entire workspace, add comments explaining the JSON diagnostic format, and add an integration test. --- .../crates/rust-analyzer/src/flycheck.rs | 46 ++++++++++--------- .../tests/slow-tests/flycheck.rs | 42 +++++++++++++++++ 2 files changed, 67 insertions(+), 21 deletions(-) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs index cdaf944bbad42..da28e1577194f 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs @@ -673,27 +673,31 @@ impl FlycheckActor { if self.diagnostics_received == DiagnosticsReceived::NotYet { tracing::trace!(flycheck_id = self.id, "clearing diagnostics"); // We finished without receiving any diagnostics. - // Clear everything for good measure - match &self.scope { - FlycheckScope::Workspace => { - self.send(FlycheckMessage::ClearDiagnostics { - id: self.id, - kind: ClearDiagnosticsKind::All(ClearScope::Workspace), - }); - } - FlycheckScope::Package { package, workspace_deps } => { - for pkg in - std::iter::once(package).chain(workspace_deps.iter().flatten()) - { - self.send(FlycheckMessage::ClearDiagnostics { - id: self.id, - kind: ClearDiagnosticsKind::All(ClearScope::Package( - pkg.clone(), - )), - }); - } - } - } + // + // `cargo check` generally outputs something, even if there are no + // warnings/errors, so we always know which package was checked. + // + // ```text + // $ cargo check --message-format=json 2>/dev/null + // {"reason":"compiler-artifact","package_id":"path+file:///Users/wilfred/tmp/scratch#0.1.0",...} + // ``` + // + // However, rustc only returns JSON if there are diagnostics present, so a + // build without warnings or errors has an empty output. + // + // ``` + // $ rustc --error-format=json bad.rs + // {"$message_type":"diagnostic","message":"mismatched types","...} + // + // $ rustc --error-format=json good.rs + // ``` + // + // So if we got zero diagnostics, it was almost certainly a check that + // wasn't specific to a package. + self.send(FlycheckMessage::ClearDiagnostics { + id: self.id, + kind: ClearDiagnosticsKind::All(ClearScope::Workspace), + }); } else if res.is_ok() { // We clear diagnostics for packages on // `[CargoCheckMessage::CompilerArtifact]` but there seem to be setups where diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/flycheck.rs b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/flycheck.rs index c1d53fb33ab60..c6f1f81139d28 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/flycheck.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/flycheck.rs @@ -110,3 +110,45 @@ fn main() {} diagnostics.diagnostics, ); } + +#[test] +fn test_flycheck_diagnostics_with_override_command_cleared_after_fix() { + if skip_slow_tests() { + return; + } + + // Start with a program that is lint clean. + let server = Project::with_fixture( + r#" +//- /Cargo.toml +[package] +name = "foo" +version = "0.0.0" + +//- /src/main.rs +fn main() {} +"#, + ) + .with_config(serde_json::json!({ + "checkOnSave": true, + "check": { + "overrideCommand": ["rustc", "--error-format=json", "$saved_file"] + } + })) + .server() + .wait_until_workspace_is_loaded(); + + // Introduce an unused variable. + server.write_file_and_save("src/main.rs", "fn main() {\n let x = 1;\n}\n".to_owned()); + + let diags = server.wait_for_diagnostics(); + assert!( + diags.diagnostics.iter().any(|d| d.message.contains("unused variable")), + "expected unused variable diagnostic, got: {:?}", + diags.diagnostics, + ); + + // Fix it and verify that diagnostics are cleared. + server.write_file_and_save("src/main.rs", "fn main() {\n let _x = 1;\n}\n".to_owned()); + server.wait_for_diagnostics_cleared(); +} From 9dfb3ac07d9ed46924d3a757b8c63a5da9bd06da Mon Sep 17 00:00:00 2001 From: bendn Date: Tue, 10 Mar 2026 17:33:52 +0700 Subject: [PATCH 009/144] make matching brace almost always proc --- .../crates/ide/src/matching_brace.rs | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide/src/matching_brace.rs b/src/tools/rust-analyzer/crates/ide/src/matching_brace.rs index b2b91d6e3cf34..defd8aae8a238 100644 --- a/src/tools/rust-analyzer/crates/ide/src/matching_brace.rs +++ b/src/tools/rust-analyzer/crates/ide/src/matching_brace.rs @@ -17,25 +17,40 @@ use syntax::{ pub(crate) fn matching_brace(file: &SourceFile, offset: TextSize) -> Option { const BRACES: &[SyntaxKind] = &[T!['{'], T!['}'], T!['['], T![']'], T!['('], T![')'], T![<], T![>], T![|], T![|]]; - let (brace_token, brace_idx) = file + + if let Some((brace_token, brace_idx)) = file .syntax() .token_at_offset(offset) .filter_map(|node| { let idx = BRACES.iter().position(|&brace| brace == node.kind())?; Some((node, idx)) }) - .last()?; - let parent = brace_token.parent()?; - if brace_token.kind() == T![|] && !ast::ParamList::can_cast(parent.kind()) { - cov_mark::hit!(pipes_not_braces); - return None; + .last() + { + let parent = brace_token.parent()?; + if brace_token.kind() == T![|] && !ast::ParamList::can_cast(parent.kind()) { + cov_mark::hit!(pipes_not_braces); + return None; + } + let matching_kind = BRACES[brace_idx ^ 1]; + let matching_node = parent + .children_with_tokens() + .filter_map(|it| it.into_token()) + .find(|node| node.kind() == matching_kind && node != &brace_token)?; + Some(matching_node.text_range().start()) + } else { + // when the offset is not at a brace + let thingy = file.syntax().token_at_offset(offset).last()?; + // find first parent + thingy.parent_ancestors().find_map(|x| { + x.children_with_tokens() + .filter_map(|it| it.into_token()) + // with ending brace + .filter(|node| BRACES.contains(&node.kind())) + .last() + .map(|x| x.text_range().start()) + }) } - let matching_kind = BRACES[brace_idx ^ 1]; - let matching_node = parent - .children_with_tokens() - .filter_map(|it| it.into_token()) - .find(|node| node.kind() == matching_kind && node != &brace_token)?; - Some(matching_node.text_range().start()) } #[cfg(test)] From c3870034e3e58fd9ca4af049e4bd783fd6289c2d Mon Sep 17 00:00:00 2001 From: bendn Date: Tue, 10 Mar 2026 17:44:04 +0700 Subject: [PATCH 010/144] test --- .../crates/ide/src/matching_brace.rs | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide/src/matching_brace.rs b/src/tools/rust-analyzer/crates/ide/src/matching_brace.rs index defd8aae8a238..5079b0c4f9171 100644 --- a/src/tools/rust-analyzer/crates/ide/src/matching_brace.rs +++ b/src/tools/rust-analyzer/crates/ide/src/matching_brace.rs @@ -17,10 +17,9 @@ use syntax::{ pub(crate) fn matching_brace(file: &SourceFile, offset: TextSize) -> Option { const BRACES: &[SyntaxKind] = &[T!['{'], T!['}'], T!['['], T![']'], T!['('], T![')'], T![<], T![>], T![|], T![|]]; - - if let Some((brace_token, brace_idx)) = file - .syntax() - .token_at_offset(offset) + let current = file.syntax().token_at_offset(offset); + if let Some((brace_token, brace_idx)) = current + .clone() .filter_map(|node| { let idx = BRACES.iter().position(|&brace| brace == node.kind())?; Some((node, idx)) @@ -39,10 +38,8 @@ pub(crate) fn matching_brace(file: &SourceFile, offset: TextSize) -> Option Date: Sun, 22 Feb 2026 17:55:34 +0800 Subject: [PATCH 011/144] fix: Improve inserted order for trait_impl_redundant_assoc_item Example --- ```rust trait Marker { fn foo(); fn baz(); } impl Marker for Foo { fn foo() {} fn missing() {}$0 fn baz() {} } ``` **Before this PR** ```rust trait Marker { fn missing(); fn foo(); fn baz(); } impl Marker for Foo { fn foo() {} fn missing() {} fn baz() {} } ``` **After this PR** ```rust trait Marker { fn foo(); fn missing(); fn baz(); } impl Marker for Foo { fn foo() {} fn missing() {} fn baz() {} } ``` --- .../trait_impl_redundant_assoc_item.rs | 112 +++++++++++++++++- 1 file changed, 107 insertions(+), 5 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/trait_impl_redundant_assoc_item.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/trait_impl_redundant_assoc_item.rs index f4054610f2bd1..6a380481d4c13 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/trait_impl_redundant_assoc_item.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/trait_impl_redundant_assoc_item.rs @@ -5,8 +5,10 @@ use ide_db::{ label::Label, source_change::SourceChangeBuilder, }; -use syntax::ToSmolStr; -use syntax::ast::edit::AstNodeEdit; +use syntax::{ + AstNode, ToSmolStr, + ast::{HasName, edit::AstNodeEdit}, +}; use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext}; @@ -82,16 +84,18 @@ fn quickfix_for_redundant_assoc_item( let db = ctx.sema.db; let root = db.parse_or_expand(d.file_id); // don't modify trait def in outer crate - let current_crate = ctx.sema.scope(&d.impl_.syntax_node_ptr().to_node(&root))?.krate(); + let impl_def = d.impl_.to_node(&root); + let current_crate = ctx.sema.scope(impl_def.syntax())?.krate(); let trait_def_crate = d.trait_.module(db).krate(db); if trait_def_crate != current_crate { return None; } let trait_def = d.trait_.source(db)?.value; - let l_curly = trait_def.assoc_item_list()?.l_curly_token()?.text_range(); + let insert_after = find_insert_after(range, &impl_def, &trait_def)?; + let where_to_insert = - hir::InFile::new(d.file_id, l_curly).original_node_file_range_rooted_opt(db)?; + hir::InFile::new(d.file_id, insert_after).original_node_file_range_rooted_opt(db)?; if where_to_insert.file_id != file_id { return None; } @@ -112,6 +116,41 @@ fn quickfix_for_redundant_assoc_item( }]) } +fn find_insert_after( + redundant_range: TextRange, + impl_def: &syntax::ast::Impl, + trait_def: &syntax::ast::Trait, +) -> Option { + let impl_items_before_redundant = impl_def + .assoc_item_list()? + .assoc_items() + .take_while(|it| it.syntax().text_range().start() < redundant_range.start()) + .filter_map(|it| name_of(&it)) + .collect::>(); + + let after_item = trait_def + .assoc_item_list()? + .assoc_items() + .filter(|it| { + name_of(it).is_some_and(|name| { + impl_items_before_redundant.iter().any(|it| it.text() == name.text()) + }) + }) + .last() + .map(|it| it.syntax().text_range()); + + return after_item.or_else(|| Some(trait_def.assoc_item_list()?.l_curly_token()?.text_range())); + + fn name_of(it: &syntax::ast::AssocItem) -> Option { + match it { + syntax::ast::AssocItem::Const(it) => it.name(), + syntax::ast::AssocItem::Fn(it) => it.name(), + syntax::ast::AssocItem::TypeAlias(it) => it.name(), + syntax::ast::AssocItem::MacroCall(_) => None, + } + } +} + #[cfg(test)] mod tests { use crate::tests::{check_diagnostics, check_fix, check_no_fix}; @@ -274,6 +313,69 @@ mod indent { ); } + #[test] + fn quickfix_order() { + check_fix( + r#" +trait Marker { + fn foo(); + fn baz(); +} +struct Foo; +impl Marker for Foo { + fn foo() {} + fn missing() {}$0 + fn baz() {} +} + "#, + r#" +trait Marker { + fn foo(); + fn missing(); + fn baz(); +} +struct Foo; +impl Marker for Foo { + fn foo() {} + fn missing() {} + fn baz() {} +} + "#, + ); + + check_fix( + r#" +trait Marker { + type Item; + fn bar(); + fn baz(); +} +struct Foo; +impl Marker for Foo { + type Item = Foo; + fn missing() {}$0 + fn bar() {} + fn baz() {} +} + "#, + r#" +trait Marker { + type Item; + fn missing(); + fn bar(); + fn baz(); +} +struct Foo; +impl Marker for Foo { + type Item = Foo; + fn missing() {} + fn bar() {} + fn baz() {} +} + "#, + ); + } + #[test] fn quickfix_dont_work() { check_no_fix( From 079303391b0a3aa2539c03b4544d237e31d841c3 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Thu, 26 Mar 2026 18:28:29 +0530 Subject: [PATCH 012/144] add path_from_idents and token_tree_from_node in SyntaxFactory --- .../src/ast/syntax_factory/constructors.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs index fa81dfad1f7f1..14bd66d79d771 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs @@ -2049,6 +2049,23 @@ impl SyntaxFactory { self.path_unqualified(self.path_segment(self.name_ref(ident))) } + pub fn path_from_idents<'a>( + &self, + parts: impl IntoIterator, + ) -> Option { + let mut iter = parts.into_iter(); + let base = self.ident_path(iter.next()?); + let path = iter.fold(base, |base, s| { + let segment = self.ident_path(s); + self.path_concat(base, segment) + }); + Some(path) + } + + pub fn token_tree_from_node(&self, node: &SyntaxNode) -> ast::TokenTree { + make::ext::token_tree_from_node(node).clone_for_update() + } + pub fn expr_unit(&self) -> ast::Expr { self.expr_tuple([]).into() } From e646424deca754e34bdc73a74ade84808529cc9f Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Thu, 26 Mar 2026 18:29:09 +0530 Subject: [PATCH 013/144] remove usage of make with SyntaxFactory in utils/gen_trait_fn_body --- .../src/utils/gen_trait_fn_body.rs | 497 +++++++++--------- 1 file changed, 258 insertions(+), 239 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/utils/gen_trait_fn_body.rs b/src/tools/rust-analyzer/crates/ide-assists/src/utils/gen_trait_fn_body.rs index 87e90e85193c0..f59e48a04f401 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/utils/gen_trait_fn_body.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils/gen_trait_fn_body.rs @@ -1,7 +1,7 @@ //! This module contains functions to generate default trait impl function bodies where possible. use hir::TraitRef; -use syntax::ast::{self, AstNode, BinaryOp, CmpOp, HasName, LogicOp, edit::AstNodeEdit, make}; +use syntax::ast::{self, AstNode, BinaryOp, CmpOp, HasName, LogicOp, edit::AstNodeEdit, syntax_factory::SyntaxFactory}; /// Generate custom trait bodies without default implementation where possible. /// @@ -11,6 +11,7 @@ use syntax::ast::{self, AstNode, BinaryOp, CmpOp, HasName, LogicOp, edit::AstNod /// `None` means that generating a custom trait body failed, and the body will remain /// as `todo!` instead. pub(crate) fn gen_trait_fn_body( + make: &SyntaxFactory, func: &ast::Fn, trait_path: &ast::Path, adt: &ast::Adt, @@ -20,32 +21,32 @@ pub(crate) fn gen_trait_fn_body( match trait_path.segment()?.name_ref()?.text().as_str() { "Clone" => { stdx::always!(func.name().is_some_and(|name| name.text() == "clone")); - gen_clone_impl(adt) + gen_clone_impl(make, adt) } - "Debug" => gen_debug_impl(adt), - "Default" => gen_default_impl(adt), + "Debug" => gen_debug_impl(make, adt), + "Default" => gen_default_impl(make, adt), "Hash" => { stdx::always!(func.name().is_some_and(|name| name.text() == "hash")); - gen_hash_impl(adt) + gen_hash_impl(make, adt) } "PartialEq" => { stdx::always!(func.name().is_some_and(|name| name.text() == "eq")); - gen_partial_eq(adt, trait_ref) + gen_partial_eq(make, adt, trait_ref) } "PartialOrd" => { stdx::always!(func.name().is_some_and(|name| name.text() == "partial_cmp")); - gen_partial_ord(adt, trait_ref) + gen_partial_ord(make, adt, trait_ref) } _ => None, } } /// Generate a `Clone` impl based on the fields and members of the target type. -fn gen_clone_impl(adt: &ast::Adt) -> Option { - fn gen_clone_call(target: ast::Expr) -> ast::Expr { - let method = make::name_ref("clone"); - make::expr_method_call(target, method, make::arg_list(None)).into() - } +fn gen_clone_impl(make: &SyntaxFactory, adt: &ast::Adt) -> Option { + let gen_clone_call = |target: ast::Expr| -> ast::Expr { + let method = make.name_ref("clone"); + make.expr_method_call(target, method, make.arg_list([])).into() + }; let expr = match adt { // `Clone` cannot be derived for unions, so no default impl can be provided. ast::Adt::Union(_) => return None, @@ -54,7 +55,7 @@ fn gen_clone_impl(adt: &ast::Adt) -> Option { let mut arms = vec![]; for variant in list.variants() { let name = variant.name()?; - let variant_name = make::ext::path_from_idents(["Self", &format!("{name}")])?; + let variant_name = make.path_from_idents(["Self", &format!("{name}")])?; match variant.field_list() { // => match self { Self::Name { x } => Self::Name { x: x.clone() } } @@ -63,19 +64,20 @@ fn gen_clone_impl(adt: &ast::Adt) -> Option { let mut fields = vec![]; for field in list.fields() { let field_name = field.name()?; - let pat = make::ident_pat(false, false, field_name.clone()); - pats.push(pat.into()); + let pat = make.ident_pat(false, false, field_name.clone()); + pats.push(make.record_pat_field_shorthand(pat.into())); - let path = make::ext::ident_path(&field_name.to_string()); - let method_call = gen_clone_call(make::expr_path(path)); - let name_ref = make::name_ref(&field_name.to_string()); - let field = make::record_expr_field(name_ref, Some(method_call)); + let path = make.ident_path(&field_name.to_string()); + let method_call = gen_clone_call(make.expr_path(path)); + let name_ref = make.name_ref(&field_name.to_string()); + let field = make.record_expr_field(name_ref, Some(method_call)); fields.push(field); } - let pat = make::record_pat(variant_name.clone(), pats.into_iter()); - let fields = make::record_expr_field_list(fields); - let record_expr = make::record_expr(variant_name, fields).into(); - arms.push(make::match_arm(pat.into(), None, record_expr)); + let pat_field_list = make.record_pat_field_list(pats, None); + let pat = make.record_pat_with_fields(variant_name.clone(), pat_field_list); + let fields = make.record_expr_field_list(fields); + let record_expr = make.record_expr(variant_name, fields).into(); + arms.push(make.match_arm(pat.into(), None, record_expr)); } // => match self { Self::Name(arg1) => Self::Name(arg1.clone()) } @@ -84,31 +86,31 @@ fn gen_clone_impl(adt: &ast::Adt) -> Option { let mut fields = vec![]; for (i, _) in list.fields().enumerate() { let field_name = format!("arg{i}"); - let pat = make::ident_pat(false, false, make::name(&field_name)); + let pat = make.ident_pat(false, false, make.name(&field_name)); pats.push(pat.into()); - let f_path = make::expr_path(make::ext::ident_path(&field_name)); + let f_path = make.expr_path(make.ident_path(&field_name)); fields.push(gen_clone_call(f_path)); } - let pat = make::tuple_struct_pat(variant_name.clone(), pats.into_iter()); - let struct_name = make::expr_path(variant_name); + let pat = make.tuple_struct_pat(variant_name.clone(), pats.into_iter()); + let struct_name = make.expr_path(variant_name); let tuple_expr = - make::expr_call(struct_name, make::arg_list(fields)).into(); - arms.push(make::match_arm(pat.into(), None, tuple_expr)); + make.expr_call(struct_name, make.arg_list(fields)).into(); + arms.push(make.match_arm(pat.into(), None, tuple_expr)); } // => match self { Self::Name => Self::Name } None => { - let pattern = make::path_pat(variant_name.clone()); - let variant_expr = make::expr_path(variant_name); - arms.push(make::match_arm(pattern, None, variant_expr)); + let pattern = make.path_pat(variant_name.clone()); + let variant_expr = make.expr_path(variant_name); + arms.push(make.match_arm(pattern, None, variant_expr)); } } } - let match_target = make::expr_path(make::ext::ident_path("self")); - let list = make::match_arm_list(arms).indent(ast::edit::IndentLevel(1)); - make::expr_match(match_target, list).into() + let match_target = make.expr_path(make.ident_path("self")); + let list = make.match_arm_list(arms).indent(ast::edit::IndentLevel(1)); + make.expr_match(match_target, list).into() } ast::Adt::Struct(strukt) => { match strukt.field_list() { @@ -116,43 +118,43 @@ fn gen_clone_impl(adt: &ast::Adt) -> Option { Some(ast::FieldList::RecordFieldList(field_list)) => { let mut fields = vec![]; for field in field_list.fields() { - let base = make::expr_path(make::ext::ident_path("self")); - let target = make::expr_field(base, &field.name()?.to_string()); + let base = make.expr_path(make.ident_path("self")); + let target = make.expr_field(base, &field.name()?.to_string()).into(); let method_call = gen_clone_call(target); - let name_ref = make::name_ref(&field.name()?.to_string()); - let field = make::record_expr_field(name_ref, Some(method_call)); + let name_ref = make.name_ref(&field.name()?.to_string()); + let field = make.record_expr_field(name_ref, Some(method_call)); fields.push(field); } - let struct_name = make::ext::ident_path("Self"); - let fields = make::record_expr_field_list(fields); - make::record_expr(struct_name, fields).into() + let struct_name = make.ident_path("Self"); + let fields = make.record_expr_field_list(fields); + make.record_expr(struct_name, fields).into() } // => Self(self.0.clone(), self.1.clone()) Some(ast::FieldList::TupleFieldList(field_list)) => { let mut fields = vec![]; for (i, _) in field_list.fields().enumerate() { - let f_path = make::expr_path(make::ext::ident_path("self")); - let target = make::expr_field(f_path, &format!("{i}")); + let f_path = make.expr_path(make.ident_path("self")); + let target = make.expr_field(f_path, &format!("{i}")).into(); fields.push(gen_clone_call(target)); } - let struct_name = make::expr_path(make::ext::ident_path("Self")); - make::expr_call(struct_name, make::arg_list(fields)).into() + let struct_name = make.expr_path(make.ident_path("Self")); + make.expr_call(struct_name, make.arg_list(fields)).into() } // => Self { } None => { - let struct_name = make::ext::ident_path("Self"); - let fields = make::record_expr_field_list(None); - make::record_expr(struct_name, fields).into() + let struct_name = make.ident_path("Self"); + let fields = make.record_expr_field_list([]); + make.record_expr(struct_name, fields).into() } } } }; - let body = make::block_expr(None, Some(expr)).indent(ast::edit::IndentLevel(1)); + let body = make.block_expr(None, Some(expr)).indent(ast::edit::IndentLevel(1)); Some(body) } /// Generate a `Debug` impl based on the fields and members of the target type. -fn gen_debug_impl(adt: &ast::Adt) -> Option { +fn gen_debug_impl(make: &SyntaxFactory, adt: &ast::Adt) -> Option { let annotated_name = adt.name()?; match adt { // `Debug` cannot be derived for unions, so no default impl can be provided. @@ -164,156 +166,161 @@ fn gen_debug_impl(adt: &ast::Adt) -> Option { let mut arms = vec![]; for variant in list.variants() { let name = variant.name()?; - let variant_name = make::ext::path_from_idents(["Self", &format!("{name}")])?; - let target = make::expr_path(make::ext::ident_path("f")); + let variant_name = make.path_from_idents(["Self", &format!("{name}")])?; + let target = make.expr_path(make.ident_path("f")); match variant.field_list() { Some(ast::FieldList::RecordFieldList(list)) => { // => f.debug_struct(name) - let target = make::expr_path(make::ext::ident_path("f")); - let method = make::name_ref("debug_struct"); + let target = make.expr_path(make.ident_path("f")); + let method = make.name_ref("debug_struct"); let struct_name = format!("\"{name}\""); - let args = make::arg_list(Some(make::expr_literal(&struct_name).into())); - let mut expr = make::expr_method_call(target, method, args).into(); + let args = make.arg_list([make.expr_literal(&struct_name).into()]); + let mut expr = make.expr_method_call(target, method, args).into(); let mut pats = vec![]; for field in list.fields() { let field_name = field.name()?; // create a field pattern for use in `MyStruct { fields.. }` - let pat = make::ident_pat(false, false, field_name.clone()); - pats.push(pat.into()); + let pat = make.ident_pat(false, false, field_name.clone()); + pats.push(make.record_pat_field_shorthand(pat.into())); // => .field("field_name", field) - let method_name = make::name_ref("field"); - let name = make::expr_literal(&(format!("\"{field_name}\""))).into(); + let method_name = make.name_ref("field"); + let name = make.expr_literal(&(format!("\"{field_name}\""))).into(); let path = &format!("{field_name}"); - let path = make::expr_path(make::ext::ident_path(path)); - let args = make::arg_list(vec![name, path]); - expr = make::expr_method_call(expr, method_name, args).into(); + let path = make.expr_path(make.ident_path(path)); + let args = make.arg_list([name, path]); + expr = make.expr_method_call(expr, method_name, args).into(); } // => .finish() - let method = make::name_ref("finish"); + let method = make.name_ref("finish"); let expr = - make::expr_method_call(expr, method, make::arg_list(None)).into(); + make.expr_method_call(expr, method, make.arg_list([])).into(); // => MyStruct { fields.. } => f.debug_struct("MyStruct")...finish(), - let pat = make::record_pat(variant_name.clone(), pats.into_iter()); - arms.push(make::match_arm(pat.into(), None, expr)); + let pat_field_list = make.record_pat_field_list(pats, None); + let pat = make.record_pat_with_fields(variant_name.clone(), pat_field_list); + arms.push(make.match_arm(pat.into(), None, expr)); } Some(ast::FieldList::TupleFieldList(list)) => { // => f.debug_tuple(name) - let target = make::expr_path(make::ext::ident_path("f")); - let method = make::name_ref("debug_tuple"); + let target = make.expr_path(make.ident_path("f")); + let method = make.name_ref("debug_tuple"); let struct_name = format!("\"{name}\""); - let args = make::arg_list(Some(make::expr_literal(&struct_name).into())); - let mut expr = make::expr_method_call(target, method, args).into(); + let args = make.arg_list([make.expr_literal(&struct_name).into()]); + let mut expr = make.expr_method_call(target, method, args).into(); let mut pats = vec![]; for (i, _) in list.fields().enumerate() { let name = format!("arg{i}"); // create a field pattern for use in `MyStruct(fields..)` - let field_name = make::name(&name); - let pat = make::ident_pat(false, false, field_name.clone()); + let field_name = make.name(&name); + let pat = make.ident_pat(false, false, field_name.clone()); pats.push(pat.into()); // => .field(field) - let method_name = make::name_ref("field"); + let method_name = make.name_ref("field"); let field_path = &name.to_string(); - let field_path = make::expr_path(make::ext::ident_path(field_path)); - let args = make::arg_list(vec![field_path]); - expr = make::expr_method_call(expr, method_name, args).into(); + let field_path = make.expr_path(make.ident_path(field_path)); + let args = make.arg_list([field_path]); + expr = make.expr_method_call(expr, method_name, args).into(); } // => .finish() - let method = make::name_ref("finish"); - let expr = - make::expr_method_call(expr, method, make::arg_list(None)).into(); + let method = make.name_ref("finish"); + let expr= + make.expr_method_call(expr, method, make.arg_list([])).into(); // => MyStruct (fields..) => f.debug_tuple("MyStruct")...finish(), - let pat = make::tuple_struct_pat(variant_name.clone(), pats.into_iter()); - arms.push(make::match_arm(pat.into(), None, expr)); + let pat = make.tuple_struct_pat(variant_name.clone(), pats.into_iter()); + arms.push(make.match_arm(pat.into(), None, expr)); } None => { - let fmt_string = make::expr_literal(&(format!("\"{name}\""))).into(); - let args = make::ext::token_tree_from_node( - make::arg_list([target, fmt_string]).syntax(), + let fmt_string = + make.expr_literal(&(format!("\"{name}\""))).into(); + let args = make.token_tree_from_node( + make.arg_list([target, fmt_string]).syntax(), ); - let macro_name = make::ext::ident_path("write"); - let macro_call = make::expr_macro(macro_name, args); + let macro_name = make.ident_path("write"); + let macro_call = make.expr_macro(macro_name, args); - let variant_name = make::path_pat(variant_name); - arms.push(make::match_arm(variant_name, None, macro_call.into())); + let variant_name = make.path_pat(variant_name); + arms.push(make.match_arm(variant_name, None, macro_call.into())); } } } - let match_target = make::expr_path(make::ext::ident_path("self")); - let list = make::match_arm_list(arms).indent(ast::edit::IndentLevel(1)); - let match_expr = make::expr_match(match_target, list); + let match_target = make.expr_path(make.ident_path("self")); + let list = make.match_arm_list(arms).indent(ast::edit::IndentLevel(1)); + let match_expr = make.expr_match(match_target, list); - let body = make::block_expr(None, Some(match_expr.into())); + let body = make.block_expr(None::, Some(match_expr.into())); let body = body.indent(ast::edit::IndentLevel(1)); Some(body) } ast::Adt::Struct(strukt) => { let name = format!("\"{annotated_name}\""); - let args = make::arg_list(Some(make::expr_literal(&name).into())); - let target = make::expr_path(make::ext::ident_path("f")); + let args = make.arg_list([make.expr_literal(&name).into()]); + let target = make.expr_path(make.ident_path("f")); let expr = match strukt.field_list() { // => f.debug_struct("Name").finish() - None => make::expr_method_call(target, make::name_ref("debug_struct"), args).into(), + None => make.expr_method_call(target, make.name_ref("debug_struct"), args).into(), // => f.debug_struct("Name").field("foo", &self.foo).finish() Some(ast::FieldList::RecordFieldList(field_list)) => { - let method = make::name_ref("debug_struct"); - let mut expr = make::expr_method_call(target, method, args).into(); + let method = make.name_ref("debug_struct"); + let mut expr = make.expr_method_call(target, method, args).into(); for field in field_list.fields() { let name = field.name()?; - let f_name = make::expr_literal(&(format!("\"{name}\""))).into(); - let f_path = make::expr_path(make::ext::ident_path("self")); - let f_path = make::expr_ref(f_path, false); - let f_path = make::expr_field(f_path, &format!("{name}")); - let args = make::arg_list([f_name, f_path]); - expr = make::expr_method_call(expr, make::name_ref("field"), args).into(); + let f_name = + make.expr_literal(&(format!("\"{name}\""))).into(); + let f_path = make.expr_path(make.ident_path("self")); + let f_path = make.expr_field(f_path, &format!("{name}")).into(); + let f_path = make.expr_ref(f_path, false); + let args = make.arg_list([f_name, f_path]); + expr = make.expr_method_call(expr, make.name_ref("field"), args).into(); } expr } - // => f.debug_tuple("Name").field(self.0).finish() + // => f.debug_tuple("Name").field(&self.0).finish() Some(ast::FieldList::TupleFieldList(field_list)) => { - let method = make::name_ref("debug_tuple"); - let mut expr = make::expr_method_call(target, method, args).into(); + let method = make.name_ref("debug_tuple"); + let mut expr = make.expr_method_call(target, method, args).into(); for (i, _) in field_list.fields().enumerate() { - let f_path = make::expr_path(make::ext::ident_path("self")); - let f_path = make::expr_ref(f_path, false); - let f_path = make::expr_field(f_path, &format!("{i}")); - let method = make::name_ref("field"); - expr = make::expr_method_call(expr, method, make::arg_list(Some(f_path))) + let f_path = make.expr_path(make.ident_path("self")); + let f_path = make.expr_field(f_path, &format!("{i}")).into(); + let f_path = make.expr_ref(f_path, false); + let method = make.name_ref("field"); + expr = make + .expr_method_call(expr, method, make.arg_list([f_path])) .into(); } expr } }; - let method = make::name_ref("finish"); - let expr = make::expr_method_call(expr, method, make::arg_list(None)).into(); - let body = make::block_expr(None, Some(expr)).indent(ast::edit::IndentLevel(1)); + let method = make.name_ref("finish"); + let expr = make.expr_method_call(expr, method, make.arg_list([])).into(); + let body = + make.block_expr(None::, Some(expr)).indent(ast::edit::IndentLevel(1)); Some(body) } } } -/// Generate a `Debug` impl based on the fields and members of the target type. -fn gen_default_impl(adt: &ast::Adt) -> Option { - fn gen_default_call() -> Option { - let fn_name = make::ext::path_from_idents(["Default", "default"])?; - Some(make::expr_call(make::expr_path(fn_name), make::arg_list(None)).into()) - } +/// Generate a `Default` impl based on the fields and members of the target type. +fn gen_default_impl(make: &SyntaxFactory, adt: &ast::Adt) -> Option { + let gen_default_call = || -> Option { + let fn_name = make.path_from_idents(["Default", "default"])?; + Some(make.expr_call(make.expr_path(fn_name), make.arg_list([])).into()) + }; match adt { // `Debug` cannot be derived for unions, so no default impl can be provided. ast::Adt::Union(_) => None, @@ -325,42 +332,44 @@ fn gen_default_impl(adt: &ast::Adt) -> Option { let mut fields = vec![]; for field in field_list.fields() { let method_call = gen_default_call()?; - let name_ref = make::name_ref(&field.name()?.to_string()); - let field = make::record_expr_field(name_ref, Some(method_call)); + let name_ref = make.name_ref(&field.name()?.to_string()); + let field = make.record_expr_field(name_ref, Some(method_call)); fields.push(field); } - let struct_name = make::ext::ident_path("Self"); - let fields = make::record_expr_field_list(fields); - make::record_expr(struct_name, fields).into() + let struct_name = make.ident_path("Self"); + let fields = make.record_expr_field_list(fields); + make.record_expr(struct_name, fields).into() } Some(ast::FieldList::TupleFieldList(field_list)) => { - let struct_name = make::expr_path(make::ext::ident_path("Self")); + let struct_name = make.expr_path(make.ident_path("Self")); let fields = field_list .fields() .map(|_| gen_default_call()) .collect::>>()?; - make::expr_call(struct_name, make::arg_list(fields)).into() + make.expr_call(struct_name, make.arg_list(fields)).into() } None => { - let struct_name = make::ext::ident_path("Self"); - let fields = make::record_expr_field_list(None); - make::record_expr(struct_name, fields).into() + let struct_name = make.ident_path("Self"); + let fields = make.record_expr_field_list([]); + make.record_expr(struct_name, fields).into() } }; - let body = make::block_expr(None, Some(expr)).indent(ast::edit::IndentLevel(1)); + let body = make + .block_expr(None::, Some(expr)) + .indent(ast::edit::IndentLevel(1)); Some(body) } } } /// Generate a `Hash` impl based on the fields and members of the target type. -fn gen_hash_impl(adt: &ast::Adt) -> Option { - fn gen_hash_call(target: ast::Expr) -> ast::Stmt { - let method = make::name_ref("hash"); - let arg = make::expr_path(make::ext::ident_path("state")); - let expr = make::expr_method_call(target, method, make::arg_list(Some(arg))).into(); - make::expr_stmt(expr).into() - } +fn gen_hash_impl(make: &SyntaxFactory, adt: &ast::Adt) -> Option { + let gen_hash_call = |target: ast::Expr| -> ast::Stmt { + let method = make.name_ref("hash"); + let arg = make.expr_path(make.ident_path("state")); + let expr = make.expr_method_call(target, method, make.arg_list([arg])).into(); + make.expr_stmt(expr).into() + }; let body = match adt { // `Hash` cannot be derived for unions, so no default impl can be provided. @@ -368,35 +377,36 @@ fn gen_hash_impl(adt: &ast::Adt) -> Option { // => std::mem::discriminant(self).hash(state); ast::Adt::Enum(_) => { - let fn_name = make_discriminant()?; + let fn_name = make_discriminant(make)?; - let arg = make::expr_path(make::ext::ident_path("self")); - let fn_call = make::expr_call(fn_name, make::arg_list(Some(arg))).into(); + let arg = make.expr_path(make.ident_path("self")); + let fn_call: ast::Expr = make.expr_call(fn_name, make.arg_list([arg])).into(); let stmt = gen_hash_call(fn_call); - make::block_expr(Some(stmt), None).indent(ast::edit::IndentLevel(1)) + make.block_expr([stmt], None).indent(ast::edit::IndentLevel(1)) } ast::Adt::Struct(strukt) => match strukt.field_list() { // => self..hash(state); Some(ast::FieldList::RecordFieldList(field_list)) => { let mut stmts = vec![]; for field in field_list.fields() { - let base = make::expr_path(make::ext::ident_path("self")); - let target = make::expr_field(base, &field.name()?.to_string()); + let base = make.expr_path(make.ident_path("self")); + let target = + make.expr_field(base, &field.name()?.to_string()).into(); stmts.push(gen_hash_call(target)); } - make::block_expr(stmts, None).indent(ast::edit::IndentLevel(1)) + make.block_expr(stmts, None).indent(ast::edit::IndentLevel(1)) } // => self..hash(state); Some(ast::FieldList::TupleFieldList(field_list)) => { let mut stmts = vec![]; for (i, _) in field_list.fields().enumerate() { - let base = make::expr_path(make::ext::ident_path("self")); - let target = make::expr_field(base, &format!("{i}")); + let base = make.expr_path(make.ident_path("self")); + let target = make.expr_field(base, &format!("{i}")).into(); stmts.push(gen_hash_call(target)); } - make::block_expr(stmts, None).indent(ast::edit::IndentLevel(1)) + make.block_expr(stmts, None).indent(ast::edit::IndentLevel(1)) } // No fields in the body means there's nothing to hash. @@ -408,32 +418,37 @@ fn gen_hash_impl(adt: &ast::Adt) -> Option { } /// Generate a `PartialEq` impl based on the fields and members of the target type. -fn gen_partial_eq(adt: &ast::Adt, trait_ref: Option>) -> Option { - fn gen_eq_chain(expr: Option, cmp: ast::Expr) -> Option { +fn gen_partial_eq( + make: &SyntaxFactory, + adt: &ast::Adt, + trait_ref: Option>, +) -> Option { + let gen_eq_chain = |expr: Option, cmp: ast::Expr| -> Option { match expr { - Some(expr) => Some(make::expr_bin_op(expr, BinaryOp::LogicOp(LogicOp::And), cmp)), + Some(expr) => Some(make.expr_bin_op(expr, BinaryOp::LogicOp(LogicOp::And), cmp)), None => Some(cmp), } - } + }; - fn gen_record_pat_field(field_name: &str, pat_name: &str) -> ast::RecordPatField { - let pat = make::ext::simple_ident_pat(make::name(pat_name)); - let name_ref = make::name_ref(field_name); - make::record_pat_field(name_ref, pat.into()) - } + let gen_record_pat_field = |field_name: &str, pat_name: &str| -> ast::RecordPatField { + let pat = make.ident_pat(false, false, make.name(pat_name)); + let name_ref = make.name_ref(field_name); + make.record_pat_field(name_ref, pat.into()) + }; - fn gen_record_pat(record_name: ast::Path, fields: Vec) -> ast::RecordPat { - let list = make::record_pat_field_list(fields, None); - make::record_pat_with_fields(record_name, list) - } + let gen_record_pat = + |record_name: ast::Path, fields: Vec| -> ast::RecordPat { + let list = make.record_pat_field_list(fields, None); + make.record_pat_with_fields(record_name, list) + }; - fn gen_variant_path(variant: &ast::Variant) -> Option { - make::ext::path_from_idents(["Self", &variant.name()?.to_string()]) - } + let gen_variant_path = |variant: &ast::Variant| -> Option { + make.path_from_idents(["Self", &variant.name()?.to_string()]) + }; - fn gen_tuple_field(field_name: &str) -> ast::Pat { - ast::Pat::IdentPat(make::ident_pat(false, false, make::name(field_name))) - } + let gen_tuple_field = |field_name: &str| -> ast::Pat { + ast::Pat::IdentPat(make.ident_pat(false, false, make.name(field_name))) + }; // Check that self type and rhs type match. We don't know how to implement the method // automatically otherwise. @@ -451,14 +466,14 @@ fn gen_partial_eq(adt: &ast::Adt, trait_ref: Option>) -> Option { // => std::mem::discriminant(self) == std::mem::discriminant(other) - let lhs_name = make::expr_path(make::ext::ident_path("self")); - let lhs = make::expr_call(make_discriminant()?, make::arg_list(Some(lhs_name.clone()))) - .into(); - let rhs_name = make::expr_path(make::ext::ident_path("other")); - let rhs = make::expr_call(make_discriminant()?, make::arg_list(Some(rhs_name.clone()))) - .into(); + let lhs_name = make.expr_path(make.ident_path("self")); + let lhs = + make.expr_call(make_discriminant(make)?, make.arg_list([lhs_name.clone()])).into(); + let rhs_name = make.expr_path(make.ident_path("other")); + let rhs = + make.expr_call(make_discriminant(make)?, make.arg_list([rhs_name.clone()])).into(); let eq_check = - make::expr_bin_op(lhs, BinaryOp::CmpOp(CmpOp::Eq { negated: false }), rhs); + make.expr_bin_op(lhs, BinaryOp::CmpOp(CmpOp::Eq { negated: false }), rhs); let mut n_cases = 0; let mut arms = vec![]; @@ -480,9 +495,9 @@ fn gen_partial_eq(adt: &ast::Adt, trait_ref: Option>) -> Option>) -> Option>) -> Option>) -> Option continue, @@ -542,57 +557,57 @@ fn gen_partial_eq(adt: &ast::Adt, trait_ref: Option>) -> Option false,` if we've already gone through every case where the variants of self and other match, // and `_ => std::mem::discriminant(self) == std::mem::discriminant(other),` otherwise. if n_cases > 1 { - let lhs = make::wildcard_pat().into(); + let lhs = make.wildcard_pat().into(); let rhs = if arms_len == n_cases { - make::expr_literal("false").into() + make.expr_literal("false").into() } else { eq_check }; - arms.push(make::match_arm(lhs, None, rhs)); + arms.push(make.match_arm(lhs, None, rhs)); } - let match_target = make::expr_tuple([lhs_name, rhs_name]).into(); - let list = make::match_arm_list(arms).indent(ast::edit::IndentLevel(1)); - make::expr_match(match_target, list).into() + let match_target = make.expr_tuple([lhs_name, rhs_name]).into(); + let list = make.match_arm_list(arms).indent(ast::edit::IndentLevel(1)); + make.expr_match(match_target, list).into() } }; - make::block_expr(None, Some(expr)).indent(ast::edit::IndentLevel(1)) + make.block_expr(None::, Some(expr)).indent(ast::edit::IndentLevel(1)) } ast::Adt::Struct(strukt) => match strukt.field_list() { Some(ast::FieldList::RecordFieldList(field_list)) => { let mut expr = None; for field in field_list.fields() { - let lhs = make::expr_path(make::ext::ident_path("self")); - let lhs = make::expr_field(lhs, &field.name()?.to_string()); - let rhs = make::expr_path(make::ext::ident_path("other")); - let rhs = make::expr_field(rhs, &field.name()?.to_string()); + let lhs = make.expr_path(make.ident_path("self")); + let lhs = make.expr_field(lhs, &field.name()?.to_string()).into(); + let rhs = make.expr_path(make.ident_path("other")); + let rhs = make.expr_field(rhs, &field.name()?.to_string()).into(); let cmp = - make::expr_bin_op(lhs, BinaryOp::CmpOp(CmpOp::Eq { negated: false }), rhs); + make.expr_bin_op(lhs, BinaryOp::CmpOp(CmpOp::Eq { negated: false }), rhs); expr = gen_eq_chain(expr, cmp); } - make::block_expr(None, expr).indent(ast::edit::IndentLevel(1)) + make.block_expr(None, expr).indent(ast::edit::IndentLevel(1)) } Some(ast::FieldList::TupleFieldList(field_list)) => { let mut expr = None; for (i, _) in field_list.fields().enumerate() { let idx = format!("{i}"); - let lhs = make::expr_path(make::ext::ident_path("self")); - let lhs = make::expr_field(lhs, &idx); - let rhs = make::expr_path(make::ext::ident_path("other")); - let rhs = make::expr_field(rhs, &idx); + let lhs = make.expr_path(make.ident_path("self")); + let lhs = make.expr_field(lhs, &idx).into(); + let rhs = make.expr_path(make.ident_path("other")); + let rhs = make.expr_field(rhs, &idx).into(); let cmp = - make::expr_bin_op(lhs, BinaryOp::CmpOp(CmpOp::Eq { negated: false }), rhs); + make.expr_bin_op(lhs, BinaryOp::CmpOp(CmpOp::Eq { negated: false }), rhs); expr = gen_eq_chain(expr, cmp); } - make::block_expr(None, expr).indent(ast::edit::IndentLevel(1)) + make.block_expr(None::, expr).indent(ast::edit::IndentLevel(1)) } // No fields in the body means there's nothing to compare. None => { - let expr = make::expr_literal("true").into(); - make::block_expr(None, Some(expr)).indent(ast::edit::IndentLevel(1)) + let expr = make.expr_literal("true").into(); + make.block_expr(None, Some(expr)).indent(ast::edit::IndentLevel(1)) } }, }; @@ -600,29 +615,33 @@ fn gen_partial_eq(adt: &ast::Adt, trait_ref: Option>) -> Option>) -> Option { - fn gen_partial_eq_match(match_target: ast::Expr) -> Option { +fn gen_partial_ord( + make: &SyntaxFactory, + adt: &ast::Adt, + trait_ref: Option>, +) -> Option { + let gen_partial_eq_match = |match_target: ast::Expr| -> Option { let mut arms = vec![]; let variant_name = - make::path_pat(make::ext::path_from_idents(["core", "cmp", "Ordering", "Equal"])?); - let lhs = make::tuple_struct_pat(make::ext::path_from_idents(["Some"])?, [variant_name]); - arms.push(make::match_arm(lhs.into(), None, make::expr_empty_block().into())); + make.path_pat(make.path_from_idents(["core", "cmp", "Ordering", "Equal"])?); + let lhs = make.tuple_struct_pat(make.path_from_idents(["Some"])?, [variant_name]); + arms.push(make.match_arm(lhs.into(), None, make.expr_empty_block().into())); - arms.push(make::match_arm( - make::ident_pat(false, false, make::name("ord")).into(), + arms.push(make.match_arm( + make.ident_pat(false, false, make.name("ord")).into(), None, - make::expr_return(Some(make::expr_path(make::ext::ident_path("ord")))), + make.expr_return(Some(make.expr_path(make.ident_path("ord")))).into(), )); - let list = make::match_arm_list(arms).indent(ast::edit::IndentLevel(1)); - Some(make::expr_stmt(make::expr_match(match_target, list).into()).into()) - } + let list = make.match_arm_list(arms).indent(ast::edit::IndentLevel(1)); + Some(make.expr_stmt(make.expr_match(match_target, list).into()).into()) + }; - fn gen_partial_cmp_call(lhs: ast::Expr, rhs: ast::Expr) -> ast::Expr { - let rhs = make::expr_ref(rhs, false); - let method = make::name_ref("partial_cmp"); - make::expr_method_call(lhs, method, make::arg_list(Some(rhs))).into() - } + let gen_partial_cmp_call = |lhs: ast::Expr, rhs: ast::Expr| -> ast::Expr { + let rhs = make.expr_ref(rhs, false); + let method = make.name_ref("partial_cmp"); + make.expr_method_call(lhs, method, make.arg_list([rhs])).into() + }; // Check that self type and rhs type match. We don't know how to implement the method // automatically otherwise. @@ -643,10 +662,10 @@ fn gen_partial_ord(adt: &ast::Adt, trait_ref: Option>) -> Option { let mut exprs = vec![]; for field in field_list.fields() { - let lhs = make::expr_path(make::ext::ident_path("self")); - let lhs = make::expr_field(lhs, &field.name()?.to_string()); - let rhs = make::expr_path(make::ext::ident_path("other")); - let rhs = make::expr_field(rhs, &field.name()?.to_string()); + let lhs = make.expr_path(make.ident_path("self")); + let lhs = make.expr_field(lhs, &field.name()?.to_string()).into(); + let rhs = make.expr_path(make.ident_path("other")); + let rhs = make.expr_field(rhs, &field.name()?.to_string()).into(); let ord = gen_partial_cmp_call(lhs, rhs); exprs.push(ord); } @@ -656,17 +675,17 @@ fn gen_partial_ord(adt: &ast::Adt, trait_ref: Option>) -> Option>>()?; - make::block_expr(stmts, tail).indent(ast::edit::IndentLevel(1)) + make.block_expr(stmts, tail).indent(ast::edit::IndentLevel(1)) } Some(ast::FieldList::TupleFieldList(field_list)) => { let mut exprs = vec![]; for (i, _) in field_list.fields().enumerate() { let idx = format!("{i}"); - let lhs = make::expr_path(make::ext::ident_path("self")); - let lhs = make::expr_field(lhs, &idx); - let rhs = make::expr_path(make::ext::ident_path("other")); - let rhs = make::expr_field(rhs, &idx); + let lhs = make.expr_path(make.ident_path("self")); + let lhs = make.expr_field(lhs, &idx).into(); + let rhs = make.expr_path(make.ident_path("other")); + let rhs = make.expr_field(rhs, &idx).into(); let ord = gen_partial_cmp_call(lhs, rhs); exprs.push(ord); } @@ -675,13 +694,13 @@ fn gen_partial_ord(adt: &ast::Adt, trait_ref: Option>) -> Option>>()?; - make::block_expr(stmts, tail).indent(ast::edit::IndentLevel(1)) + make.block_expr(stmts, tail).indent(ast::edit::IndentLevel(1)) } // No fields in the body means there's nothing to compare. None => { - let expr = make::expr_literal("true").into(); - make::block_expr(None, Some(expr)).indent(ast::edit::IndentLevel(1)) + let expr = make.expr_literal("true").into(); + make.block_expr(None, Some(expr)).indent(ast::edit::IndentLevel(1)) } }, }; @@ -689,6 +708,6 @@ fn gen_partial_ord(adt: &ast::Adt, trait_ref: Option>) -> Option Option { - Some(make::expr_path(make::ext::path_from_idents(["core", "mem", "discriminant"])?)) +fn make_discriminant(make: &SyntaxFactory) -> Option { + Some(make.expr_path(make.path_from_idents(["core", "mem", "discriminant"])?)) } From ec99ccb2a93c4bdd8f4b5a6a25b8860bcd8992db Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Thu, 26 Mar 2026 18:29:32 +0530 Subject: [PATCH 014/144] add generate_trait_impl_with_item in utils --- .../rust-analyzer/crates/ide-assists/src/utils.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs index 24e458e874fe9..aba562656b78e 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs @@ -758,6 +758,16 @@ pub(crate) fn generate_trait_impl_intransitive_with_item( generate_impl_inner_with_factory(make, false, adt, Some(trait_), false, Some(body)) } +pub(crate) fn generate_trait_impl_with_item( + make: &SyntaxFactory, + is_unsafe: bool, + adt: &ast::Adt, + trait_: ast::Type, + body: ast::AssocItemList, +) -> ast::Impl { + generate_impl_inner_with_factory(make, is_unsafe, adt, Some(trait_), true, Some(body)) +} + fn generate_impl_inner( is_unsafe: bool, adt: &ast::Adt, From 2a894c9edeaa4fd99a58db26e573ed891d05fcc9 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Thu, 26 Mar 2026 18:30:39 +0530 Subject: [PATCH 015/144] make changes to replace_derive_with_manual_impl to update changes to utils --- .../replace_derive_with_manual_impl.rs | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs index f54f7a02d2b37..f281fdf513126 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs @@ -13,7 +13,7 @@ use crate::{ assist_context::{AssistContext, Assists}, utils::{ DefaultMethods, IgnoreAssocItems, add_trait_assoc_items_to_impl_with_factory, - filter_assoc_items, gen_trait_fn_body, generate_trait_impl, + filter_assoc_items, gen_trait_fn_body, generate_trait_impl, generate_trait_impl_with_item, }, }; @@ -127,7 +127,7 @@ fn add_assist( let label = format!("Convert to manual `impl {replace_trait_path} for {annotated_name}`"); acc.add(AssistId::refactor("replace_derive_with_manual_impl"), label, target, |builder| { - let make = SyntaxFactory::without_mappings(); + let make = SyntaxFactory::with_mappings(); let insert_after = Position::after(adt.syntax()); let impl_is_unsafe = trait_.map(|s| s.is_unsafe(ctx.db())).unwrap_or(false); let impl_def = impl_def_from_trait( @@ -141,7 +141,7 @@ fn add_assist( ); let mut editor = builder.make_editor(attr.syntax()); - update_attribute(&mut editor, old_derives, old_tree, old_trait_path, attr); + update_attribute(&make, &mut editor, old_derives, old_tree, old_trait_path, attr); let trait_path = make.ty_path(replace_trait_path.clone()).into(); @@ -177,6 +177,7 @@ fn add_assist( insert_after, vec![make.whitespace("\n\n").into(), impl_def.syntax().clone().into()], ); + editor.add_mappings(make.finish_with_mappings()); builder.add_file_edits(ctx.vfs_file_id(), editor); }) } @@ -207,8 +208,8 @@ fn impl_def_from_trait( return None; } let make = SyntaxFactory::without_mappings(); - let trait_ty = make.ty_path(trait_path.clone()).into(); - let impl_def = generate_trait_impl(&make, impl_is_unsafe, adt, trait_ty); + let trait_ty: ast::Type = make.ty_path(trait_path.clone()).into(); + let impl_def = generate_trait_impl(&make, impl_is_unsafe, adt, trait_ty.clone()); let assoc_items = add_trait_assoc_items_to_impl_with_factory( &make, @@ -223,7 +224,7 @@ fn impl_def_from_trait( assoc_items.split_first().map(|(first, other)| (first.clone_subtree(), other)) { let first_item = if let ast::AssocItem::Fn(ref func) = first - && let Some(body) = gen_trait_fn_body(func, trait_path, adt, None) + && let Some(body) = gen_trait_fn_body(&make, func, trait_path, adt, None) && let Some(func_body) = func.body() { let mut editor = SyntaxEditor::new(first.syntax().clone()); @@ -239,21 +240,17 @@ fn impl_def_from_trait( make.assoc_item_list_empty() }; - let impl_def = impl_def.clone_subtree(); - let mut editor = SyntaxEditor::new(impl_def.syntax().clone()); - editor.replace(impl_def.assoc_item_list()?.syntax(), assoc_item_list.syntax()); - let impl_def = ast::Impl::cast(editor.finish().new_root().clone())?; - Some(impl_def) + Some(generate_trait_impl_with_item(&make, impl_is_unsafe, adt, trait_ty, assoc_item_list)) } fn update_attribute( + make: &SyntaxFactory, editor: &mut SyntaxEditor, old_derives: &[ast::Path], old_tree: &ast::TokenTree, old_trait_path: &ast::Path, attr: &ast::Attr, ) { - let make = SyntaxFactory::without_mappings(); let new_derives = old_derives .iter() .filter(|t| t.to_string() != old_trait_path.to_string()) From d7aada68242f0c223c5b50bb942e7b7a3fcaf1cc Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Thu, 26 Mar 2026 18:31:27 +0530 Subject: [PATCH 016/144] make changes to add_missing_impl_members to update changes to utils --- .../src/handlers/add_missing_impl_members.rs | 24 +++++++----- .../src/utils/gen_trait_fn_body.rs | 37 ++++++++----------- 2 files changed, 29 insertions(+), 32 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs index afdced4215f9f..3689dc24b3601 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs @@ -1,7 +1,7 @@ use hir::HasSource; use syntax::{ Edition, - ast::{self, AstNode, make}, + ast::{self, AstNode, syntax_factory::SyntaxFactory}, syntax_editor::{Position, SyntaxEditor}, }; @@ -9,8 +9,8 @@ use crate::{ AssistId, assist_context::{AssistContext, Assists}, utils::{ - DefaultMethods, IgnoreAssocItems, add_trait_assoc_items_to_impl, filter_assoc_items, - gen_trait_fn_body, + DefaultMethods, IgnoreAssocItems, add_trait_assoc_items_to_impl_with_factory, + filter_assoc_items, gen_trait_fn_body, }, }; @@ -148,7 +148,9 @@ fn add_missing_impl_members_inner( let target = impl_def.syntax().text_range(); acc.add(AssistId::quick_fix(assist_id), label, target, |edit| { - let new_item = add_trait_assoc_items_to_impl( + let make = SyntaxFactory::with_mappings(); + let new_item = add_trait_assoc_items_to_impl_with_factory( + &make, &ctx.sema, ctx.config, &missing_items, @@ -164,6 +166,7 @@ fn add_missing_impl_members_inner( let mut first_new_item = if let DefaultMethods::No = mode && let ast::AssocItem::Fn(func) = &first_new_item && let Some(body) = try_gen_trait_body( + &make, ctx, func, trait_ref, @@ -189,10 +192,10 @@ fn add_missing_impl_members_inner( if let Some(assoc_item_list) = impl_def.assoc_item_list() { assoc_item_list.add_items(&mut editor, new_assoc_items); } else { - let assoc_item_list = make::assoc_item_list(Some(new_assoc_items)).clone_for_update(); + let assoc_item_list = make.assoc_item_list(new_assoc_items); editor.insert_all( Position::after(impl_def.syntax()), - vec![make::tokens::whitespace(" ").into(), assoc_item_list.syntax().clone().into()], + vec![make.whitespace(" ").into(), assoc_item_list.syntax().clone().into()], ); first_new_item = assoc_item_list.assoc_items().next(); } @@ -215,23 +218,24 @@ fn add_missing_impl_members_inner( editor.add_annotation(first_new_item.syntax(), tabstop); }; }; + editor.add_mappings(make.finish_with_mappings()); edit.add_file_edits(ctx.vfs_file_id(), editor); }) } fn try_gen_trait_body( + make: &SyntaxFactory, ctx: &AssistContext<'_>, func: &ast::Fn, trait_ref: hir::TraitRef<'_>, impl_def: &ast::Impl, edition: Edition, ) -> Option { - let trait_path = make::ext::ident_path( - &trait_ref.trait_().name(ctx.db()).display(ctx.db(), edition).to_string(), - ); + let trait_path = + make.ident_path(&trait_ref.trait_().name(ctx.db()).display(ctx.db(), edition).to_string()); let hir_ty = ctx.sema.resolve_type(&impl_def.self_ty()?)?; let adt = hir_ty.as_adt()?.source(ctx.db())?; - gen_trait_fn_body(func, &trait_path, &adt.value, Some(trait_ref)) + gen_trait_fn_body(make, func, &trait_path, &adt.value, Some(trait_ref)) } #[cfg(test)] diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/utils/gen_trait_fn_body.rs b/src/tools/rust-analyzer/crates/ide-assists/src/utils/gen_trait_fn_body.rs index f59e48a04f401..b0d88737fe0f7 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/utils/gen_trait_fn_body.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils/gen_trait_fn_body.rs @@ -1,7 +1,10 @@ //! This module contains functions to generate default trait impl function bodies where possible. use hir::TraitRef; -use syntax::ast::{self, AstNode, BinaryOp, CmpOp, HasName, LogicOp, edit::AstNodeEdit, syntax_factory::SyntaxFactory}; +use syntax::ast::{ + self, AstNode, BinaryOp, CmpOp, HasName, LogicOp, edit::AstNodeEdit, + syntax_factory::SyntaxFactory, +}; /// Generate custom trait bodies without default implementation where possible. /// @@ -94,8 +97,7 @@ fn gen_clone_impl(make: &SyntaxFactory, adt: &ast::Adt) -> Option Option .finish() let method = make.name_ref("finish"); - let expr = - make.expr_method_call(expr, method, make.arg_list([])).into(); + let expr = make.expr_method_call(expr, method, make.arg_list([])).into(); // => MyStruct { fields.. } => f.debug_struct("MyStruct")...finish(), let pat_field_list = make.record_pat_field_list(pats, None); @@ -232,19 +233,16 @@ fn gen_debug_impl(make: &SyntaxFactory, adt: &ast::Adt) -> Option .finish() let method = make.name_ref("finish"); - let expr= - make.expr_method_call(expr, method, make.arg_list([])).into(); + let expr = make.expr_method_call(expr, method, make.arg_list([])).into(); // => MyStruct (fields..) => f.debug_tuple("MyStruct")...finish(), let pat = make.tuple_struct_pat(variant_name.clone(), pats.into_iter()); arms.push(make.match_arm(pat.into(), None, expr)); } None => { - let fmt_string = - make.expr_literal(&(format!("\"{name}\""))).into(); - let args = make.token_tree_from_node( - make.arg_list([target, fmt_string]).syntax(), - ); + let fmt_string = make.expr_literal(&(format!("\"{name}\""))).into(); + let args = + make.token_tree_from_node(make.arg_list([target, fmt_string]).syntax()); let macro_name = make.ident_path("write"); let macro_call = make.expr_macro(macro_name, args); @@ -278,8 +276,7 @@ fn gen_debug_impl(make: &SyntaxFactory, adt: &ast::Adt) -> Option Option Option, Some(expr)) - .indent(ast::edit::IndentLevel(1)); + let body = + make.block_expr(None::, Some(expr)).indent(ast::edit::IndentLevel(1)); Some(body) } } @@ -391,8 +385,7 @@ fn gen_hash_impl(make: &SyntaxFactory, adt: &ast::Adt) -> Option let mut stmts = vec![]; for field in field_list.fields() { let base = make.expr_path(make.ident_path("self")); - let target = - make.expr_field(base, &field.name()?.to_string()).into(); + let target = make.expr_field(base, &field.name()?.to_string()).into(); stmts.push(gen_hash_call(target)); } make.block_expr(stmts, None).indent(ast::edit::IndentLevel(1)) From ce4bc584d9d0860495a5ec5bca0d7aeb1b240d5d Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sat, 28 Mar 2026 13:27:53 +0800 Subject: [PATCH 017/144] Extract trait_name out of analyze_container --- .../crates/ide-assists/src/handlers/extract_function.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs index 549676aa266e1..3ec2ae1dcfbc9 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs @@ -92,12 +92,13 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op let anchor = if self_param.is_some() { Anchor::Method } else { Anchor::Freestanding }; let insert_after = node_to_insert_after(&body, anchor)?; + let trait_name = ast::Trait::cast(insert_after.clone()).and_then(|trait_| trait_.name()); let semantics_scope = ctx.sema.scope(&insert_after)?; let module = semantics_scope.module(); let edition = semantics_scope.krate().edition(ctx.db()); let (container_info, contains_tail_expr) = - body.analyze_container(&ctx.sema, edition, &insert_after)?; + body.analyze_container(&ctx.sema, edition, trait_name)?; let ret_ty = body.return_ty(ctx)?; let control_flow = body.external_control_flow(ctx, &container_info)?; @@ -841,7 +842,7 @@ impl FunctionBody { &self, sema: &Semantics<'db, RootDatabase>, edition: Edition, - insert_after: &SyntaxNode, + trait_name: Option, ) -> Option<(ContainerInfo<'db>, bool)> { let mut ancestors = self.parent()?.ancestors(); let infer_expr_opt = |expr| sema.type_of_expr(&expr?).map(TypeInfo::adjusted); @@ -929,8 +930,7 @@ impl FunctionBody { }; // FIXME: make trait arguments - let trait_name = ast::Trait::cast(insert_after.clone()) - .and_then(|trait_| Some(make::ty_path(make::ext::ident_path(&trait_.name()?.text())))); + let trait_name = trait_name.map(|name| make::ty_path(make::ext::ident_path(&name.text()))); let parent = self.parent()?; let parents = generic_parents(&parent); From 5d9a476cbbed13c9b45a6ed8bff32f45cef152b2 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sat, 28 Mar 2026 13:34:39 +0800 Subject: [PATCH 018/144] Change to lazy for make_this_param --- .../src/handlers/extract_function.rs | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs index 3ec2ae1dcfbc9..fa5bb39c54ba8 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs @@ -1742,23 +1742,25 @@ impl<'db> Function<'db> { module: hir::Module, edition: Edition, ) -> ast::ParamList { - let this_param = self.make_this_param(); + let this_param = self.make_this_param().map(|f| f()); let self_param = self.self_param.clone().filter(|_| this_param.is_none()); let params = self.params.iter().map(|param| param.to_param(ctx, module, edition)); make::param_list(self_param, this_param.into_iter().chain(params)) } - fn make_this_param(&self) -> Option { + fn make_this_param(&self) -> Option ast::Param> { if let Some(name) = self.mods.trait_name.clone() && let Some(self_param) = &self.self_param { - let bounds = make::type_bound_list([make::type_bound(name)]); - let pat = make::path_pat(make::ext::ident_path("this")); - let mut ty = make::impl_trait_type(bounds.unwrap()).into(); - if self_param.amp_token().is_some() { - ty = make::ty_ref(ty, self_param.mut_token().is_some()); - } - Some(make::param(pat, ty)) + Some(|| { + let bounds = make::type_bound_list([make::type_bound(name)]); + let pat = make::path_pat(make::ext::ident_path("this")); + let mut ty = make::impl_trait_type(bounds.unwrap()).into(); + if self_param.amp_token().is_some() { + ty = make::ty_ref(ty, self_param.mut_token().is_some()); + } + make::param(pat, ty) + }) } else { None } From d3868dd81ce9433243607e79f7bf04b7a9fa4ba9 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sat, 28 Mar 2026 16:59:31 +0800 Subject: [PATCH 019/144] feat: add expected name on simple enum variant Example --- ```rust struct Other; struct String; enum Foo { String($0) } ``` **Before this PR** ```text en Foo Foo [] st Other Other [] sp Self Foo [] st String String [] ``` **After this PR** ```text st String String [name] en Foo Foo [] st Other Other [] sp Self Foo [] ``` --- .../ide-completion/src/context/analysis.rs | 8 +++++ .../crates/ide-completion/src/render.rs | 35 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs index bf899539a20ba..a299ad66ceab7 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs @@ -827,6 +827,14 @@ fn expected_type_and_name<'db>( .map(|c| (Some(c.return_type()), None)) .unwrap_or((None, None)) }, + ast::Variant(it) => { + let is_simple_variant = matches!( + it.field_list(), + Some(ast::FieldList::TupleFieldList(list)) + if list.syntax().children_with_tokens().all(|it| it.kind() != T![,]) + ); + (None, it.name().filter(|_| is_simple_variant).map(NameOrNameRef::Name)) + }, ast::Stmt(_) => (None, None), ast::Item(_) => (None, None), _ => { diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render.rs index 765304d8187de..8d79c0fe8a75b 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/render.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/render.rs @@ -3044,6 +3044,41 @@ fn main() { ); } + #[test] + fn enum_variant_name_exact_match_is_high_priority() { + check_relevance( + r#" +struct Other; +struct String; +enum Foo { + String($0) +} + "#, + expect![[r#" + st String String [name] + en Foo Foo [] + st Other Other [] + sp Self Foo [] + "#]], + ); + + check_relevance( + r#" +struct Other; +struct String; +enum Foo { + String(String, $0) +} + "#, + expect![[r#" + en Foo Foo [] + st Other Other [] + sp Self Foo [] + st String String [] + "#]], + ); + } + #[test] fn postfix_inexact_match_is_low_priority() { cov_mark::check!(postfix_inexact_match_is_low_priority); From e38074eeb057f13c697da2be977877313018c345 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sun, 29 Mar 2026 07:05:59 +0800 Subject: [PATCH 020/144] fix: complete envs in nested `env!()` Example --- ```rust fn main() { println!("{}", env!("CA$0")); } ``` **Before this PR** Cannot complete any env **After this PR** ```rust fn main() { println!("{}", env!("CARGO_BIN_NAME")); } ``` --- .../src/completions/env_vars.rs | 50 +++++++++++++++++-- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/env_vars.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/env_vars.rs index 92cbf411c1e33..885d1a30750f8 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/env_vars.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/env_vars.rs @@ -51,11 +51,10 @@ pub(crate) fn complete_cargo_env_vars( original: &ast::String, expanded: &ast::String, ) -> Option<()> { - let is_in_env_expansion = ctx - .sema - .hir_file_for(&expanded.syntax().parent()?) - .macro_file() - .is_some_and(|it| it.is_env_or_option_env(ctx.sema.db)); + let descends = ctx.sema.descend_into_macros_exact_with_file(original.syntax().clone()); + let macro_file = descends.first()?.file_id.macro_file(); + + let is_in_env_expansion = macro_file.is_some_and(|it| it.is_env_or_option_env(ctx.sema.db)); if !is_in_env_expansion { let call = macro_call_for_string_token(expanded)?; let makro = ctx.sema.resolve_macro_call(&call)?; @@ -116,6 +115,47 @@ fn main() { ); } + #[test] + fn complete_in_expanded_env_macro() { + check_edit( + "CARGO_BIN_NAME", + r#" +//- minicore: env +macro_rules! bar { + ($($arg:tt)*) => { $($arg)* } +} + +fn main() { + let foo = bar!(env!("CA$0")); +} + "#, + r#" +macro_rules! bar { + ($($arg:tt)*) => { $($arg)* } +} + +fn main() { + let foo = bar!(env!("CARGO_BIN_NAME")); +} + "#, + ); + + check_edit( + "CARGO_BIN_NAME", + r#" +//- minicore: env, fmt +fn main() { + let foo = format_args!("{}", env!("CA$0")); +} + "#, + r#" +fn main() { + let foo = format_args!("{}", env!("CARGO_BIN_NAME")); +} + "#, + ); + } + #[test] fn doesnt_complete_in_random_strings() { let fixture = r#" From 064cbfd71db23df29ed24e26f9aafe96059fdc18 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sun, 29 Mar 2026 08:16:53 +0800 Subject: [PATCH 021/144] Remove redundant fallback state in include_references --- .../crates/ide-completion/src/completions/postfix.rs | 11 ----------- 1 file changed, 11 deletions(-) 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 5b91e7c456a53..beacc05c3c49c 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 @@ -410,13 +410,10 @@ fn include_references(initial_element: &ast::Expr) -> (ast::Expr, String) { let mut resulting_element = initial_element.clone(); let mut prefix = String::new(); - let mut found_ref_or_deref = false; - while let Some(parent_deref_element) = resulting_element.syntax().parent().and_then(ast::PrefixExpr::cast) && parent_deref_element.op_kind() == Some(ast::UnaryOp::Deref) { - found_ref_or_deref = true; resulting_element = ast::Expr::from(parent_deref_element); prefix.insert(0, '*'); @@ -425,7 +422,6 @@ fn include_references(initial_element: &ast::Expr) -> (ast::Expr, String) { while let Some(parent_ref_element) = resulting_element.syntax().parent().and_then(ast::RefExpr::cast) { - found_ref_or_deref = true; let last_child_or_token = parent_ref_element.syntax().last_child_or_token(); prefix.insert_str( 0, @@ -440,13 +436,6 @@ fn include_references(initial_element: &ast::Expr) -> (ast::Expr, String) { resulting_element = ast::Expr::from(parent_ref_element); } - if !found_ref_or_deref { - // If we do not find any ref/deref expressions, restore - // all the progress of tree climbing - prefix.clear(); - resulting_element = initial_element.clone(); - } - (resulting_element, prefix) } From 9eb2c671df6796db1fca35268fda64a1c6e7ee56 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sun, 29 Mar 2026 08:30:56 +0800 Subject: [PATCH 022/144] fix: postfix completions include nots prefix-expr Example --- Like `is_foo`, `.not`, `.if` ```rust fn main() { let is_foo = true; !is_foo.$0 } ``` **Before this PR** ```rust fn main() { let is_foo = true; !if is_foo { $0 } } ``` **After this PR** ```rust fn main() { let is_foo = true; if !is_foo { $0 } } ``` --- .../ide-completion/src/completions/postfix.rs | 38 ++++++++++++++++--- 1 file changed, 32 insertions(+), 6 deletions(-) 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 beacc05c3c49c..f1ccdd4c731de 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 @@ -402,7 +402,7 @@ fn receiver_accessor(receiver: &ast::Expr) -> ast::Expr { .unwrap_or_else(|| receiver.clone()) } -/// Given an `initial_element`, tries to expand it to include deref(s), and then references. +/// Given an `initial_element`, tries to expand it to include deref(s), not(s), and then references. /// Returns the expanded expressions, and the added prefix as a string /// /// For example, if called with the `42` in `&&mut *42`, would return `(&&mut *42, "&&mut *")`. @@ -410,15 +410,20 @@ fn include_references(initial_element: &ast::Expr) -> (ast::Expr, String) { let mut resulting_element = initial_element.clone(); let mut prefix = String::new(); - while let Some(parent_deref_element) = - resulting_element.syntax().parent().and_then(ast::PrefixExpr::cast) - && parent_deref_element.op_kind() == Some(ast::UnaryOp::Deref) + while let Some(parent) = resulting_element.syntax().parent().and_then(ast::PrefixExpr::cast) + && parent.op_kind() == Some(ast::UnaryOp::Deref) { - resulting_element = ast::Expr::from(parent_deref_element); - + resulting_element = ast::Expr::from(parent); prefix.insert(0, '*'); } + while let Some(parent) = resulting_element.syntax().parent().and_then(ast::PrefixExpr::cast) + && parent.op_kind() == Some(ast::UnaryOp::Not) + { + resulting_element = ast::Expr::from(parent); + prefix.insert(0, '!'); + } + while let Some(parent_ref_element) = resulting_element.syntax().parent().and_then(ast::RefExpr::cast) { @@ -1121,6 +1126,27 @@ fn main() { ) } + #[test] + fn postfix_completion_for_nots() { + check_edit( + "if", + r#" +fn main() { + let is_foo = true; + !is_foo.$0 +} +"#, + r#" +fn main() { + let is_foo = true; + if !is_foo { + $0 +} +} +"#, + ) + } + #[test] fn postfix_completion_for_unsafe() { postfix_completion_for_block("unsafe"); From c2886b23dd8f870beec73077119a29302051ac0a Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Sun, 29 Mar 2026 19:15:17 +0300 Subject: [PATCH 023/144] Allow disabling all fixture support Why? Because sometimes we work on resolving some bug that causes hang/consistent panics/stack overflows/etc., and we put it in a fixture for a test. Then r-a does exactly the same to us, and it's really hard to work this way. --- .../src/completions/ra_fixture.rs | 2 +- .../crates/ide-completion/src/config.rs | 5 +-- .../crates/ide-completion/src/tests.rs | 5 +-- .../crates/ide-db/src/ra_fixture.rs | 19 ++++++++++- .../crates/ide/src/annotations.rs | 12 +++---- .../crates/ide/src/call_hierarchy.rs | 12 ++++--- .../crates/ide/src/goto_declaration.rs | 4 +-- .../crates/ide/src/goto_definition.rs | 10 +++--- .../rust-analyzer/crates/ide/src/hover.rs | 10 +++--- .../crates/ide/src/hover/tests.rs | 4 +-- .../crates/ide/src/inlay_hints.rs | 9 +++--- .../crates/ide/src/inlay_hints/ra_fixture.rs | 2 +- src/tools/rust-analyzer/crates/ide/src/lib.rs | 11 ++++--- .../crates/ide/src/references.rs | 12 +++---- .../crates/ide/src/static_index.rs | 7 ++-- .../crates/ide/src/syntax_highlighting.rs | 4 +-- .../ide/src/syntax_highlighting/html.rs | 4 +-- .../ide/src/syntax_highlighting/inject.rs | 6 ++-- .../ide/src/syntax_highlighting/tests.rs | 4 +-- .../rust-analyzer/src/cli/analysis_stats.rs | 8 ++--- .../crates/rust-analyzer/src/config.rs | 32 ++++++++++++++----- .../rust-analyzer/src/handlers/request.rs | 10 ++++-- .../src/integrated_benchmarks.rs | 10 +++--- .../docs/book/src/configuration_generated.md | 9 ++++++ .../rust-analyzer/editors/code/package.json | 10 ++++++ 25 files changed, 143 insertions(+), 78 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/ra_fixture.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/ra_fixture.rs index b44c90757f687..5a8881edc73e9 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/ra_fixture.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/ra_fixture.rs @@ -22,7 +22,7 @@ pub(crate) fn complete_ra_fixture( &ctx.sema, original.clone(), expanded, - ctx.config.minicore, + &ctx.config.ra_fixture, &mut |_| {}, )?; let (virtual_file_id, virtual_offset) = analysis.map_offset_down(ctx.position.offset)?; diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/config.rs b/src/tools/rust-analyzer/crates/ide-completion/src/config.rs index 5623257a2792a..80c1572972cec 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/config.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/config.rs @@ -6,8 +6,9 @@ use hir::FindPathConfig; use ide_db::{ - MiniCore, SnippetCap, + SnippetCap, imports::{import_assets::ImportPathConfig, insert_use::InsertUseConfig}, + ra_fixture::RaFixtureConfig, }; use crate::{CompletionFieldsToResolve, snippet::Snippet}; @@ -35,7 +36,7 @@ pub struct CompletionConfig<'a> { pub fields_to_resolve: CompletionFieldsToResolve, pub exclude_flyimport: Vec<(String, AutoImportExclusionType)>, pub exclude_traits: &'a [String], - pub minicore: MiniCore<'a>, + pub ra_fixture: RaFixtureConfig<'a>, } #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests.rs index cb1adfcfb65ea..02e299b2a9c19 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests.rs @@ -29,8 +29,9 @@ use expect_test::Expect; use hir::db::HirDatabase; use hir::{PrefixKind, setup_tracing}; use ide_db::{ - FilePosition, MiniCore, RootDatabase, SnippetCap, + FilePosition, RootDatabase, SnippetCap, imports::insert_use::{ImportGranularity, InsertUseConfig}, + ra_fixture::RaFixtureConfig, }; use itertools::Itertools; use stdx::{format_to, trim_indent}; @@ -90,7 +91,7 @@ pub(crate) const TEST_CONFIG: CompletionConfig<'_> = CompletionConfig { exclude_traits: &[], enable_auto_await: true, enable_auto_iter: true, - minicore: MiniCore::default(), + ra_fixture: RaFixtureConfig::default(), }; pub(crate) fn completion_list(#[rust_analyzer::rust_fixture] ra_fixture: &str) -> String { diff --git a/src/tools/rust-analyzer/crates/ide-db/src/ra_fixture.rs b/src/tools/rust-analyzer/crates/ide-db/src/ra_fixture.rs index c9a670b2d1c0f..2f4d319ec8218 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/ra_fixture.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/ra_fixture.rs @@ -52,6 +52,18 @@ impl RootDatabase { } } +#[derive(Debug, Clone, Copy)] +pub struct RaFixtureConfig<'a> { + pub minicore: MiniCore<'a>, + pub disable_ra_fixture: bool, +} + +impl<'a> RaFixtureConfig<'a> { + pub const fn default() -> Self { + Self { minicore: MiniCore::default(), disable_ra_fixture: false } + } +} + pub struct RaFixtureAnalysis { pub db: RootDatabase, tmp_file_ids: Vec<(FileId, usize)>, @@ -69,9 +81,14 @@ impl RaFixtureAnalysis { sema: &Semantics<'_, RootDatabase>, literal: ast::String, expanded: &ast::String, - minicore: MiniCore<'_>, + config: &RaFixtureConfig<'_>, on_cursor: &mut dyn FnMut(TextRange), ) -> Option { + if config.disable_ra_fixture { + return None; + } + let minicore = config.minicore; + if !literal.is_raw() { return None; } diff --git a/src/tools/rust-analyzer/crates/ide/src/annotations.rs b/src/tools/rust-analyzer/crates/ide/src/annotations.rs index 6fb8dedea47cf..107977cb119b6 100644 --- a/src/tools/rust-analyzer/crates/ide/src/annotations.rs +++ b/src/tools/rust-analyzer/crates/ide/src/annotations.rs @@ -1,7 +1,7 @@ use hir::{HasSource, InFile, InRealFile, Semantics}; use ide_db::{ - FileId, FilePosition, FileRange, FxIndexSet, MiniCore, RootDatabase, defs::Definition, - helpers::visit_file_defs, + FileId, FilePosition, FileRange, FxIndexSet, RootDatabase, defs::Definition, + helpers::visit_file_defs, ra_fixture::RaFixtureConfig, }; use itertools::Itertools; use syntax::{AstNode, TextRange, ast::HasName}; @@ -45,7 +45,7 @@ pub struct AnnotationConfig<'a> { pub annotate_enum_variant_references: bool, pub location: AnnotationLocation, pub filter_adjacent_derive_implementations: bool, - pub minicore: MiniCore<'a>, + pub ra_fixture: RaFixtureConfig<'a>, } pub enum AnnotationLocation { @@ -216,7 +216,7 @@ pub(crate) fn resolve_annotation( *data = find_all_refs( &Semantics::new(db), pos, - &FindAllRefsConfig { search_scope: None, minicore: config.minicore }, + &FindAllRefsConfig { search_scope: None, ra_fixture: config.ra_fixture }, ) .map(|result| { result @@ -244,7 +244,7 @@ fn should_skip_runnable(kind: &RunnableKind, binary_target: bool) -> bool { #[cfg(test)] mod tests { use expect_test::{Expect, expect}; - use ide_db::MiniCore; + use ide_db::ra_fixture::RaFixtureConfig; use crate::{Annotation, AnnotationConfig, fixture}; @@ -258,7 +258,7 @@ mod tests { annotate_method_references: true, annotate_enum_variant_references: true, location: AnnotationLocation::AboveName, - minicore: MiniCore::default(), + ra_fixture: RaFixtureConfig::default(), filter_adjacent_derive_implementations: false, }; diff --git a/src/tools/rust-analyzer/crates/ide/src/call_hierarchy.rs b/src/tools/rust-analyzer/crates/ide/src/call_hierarchy.rs index aded911a8db11..402764f11202f 100644 --- a/src/tools/rust-analyzer/crates/ide/src/call_hierarchy.rs +++ b/src/tools/rust-analyzer/crates/ide/src/call_hierarchy.rs @@ -4,9 +4,10 @@ use std::iter; use hir::Semantics; use ide_db::{ - FileRange, FxIndexMap, MiniCore, RootDatabase, + FileRange, FxIndexMap, RootDatabase, defs::{Definition, NameClass, NameRefClass}, helpers::pick_best_token, + ra_fixture::RaFixtureConfig, search::FileReference, }; use syntax::{AstNode, SyntaxKind::IDENT, ast}; @@ -25,7 +26,7 @@ pub struct CallItem { pub struct CallHierarchyConfig<'a> { /// Whether to exclude tests from the call hierarchy pub exclude_tests: bool, - pub minicore: MiniCore<'a>, + pub ra_fixture: RaFixtureConfig<'a>, } pub(crate) fn call_hierarchy( @@ -36,7 +37,7 @@ pub(crate) fn call_hierarchy( goto_definition::goto_definition( db, position, - &GotoDefinitionConfig { minicore: config.minicore }, + &GotoDefinitionConfig { ra_fixture: config.ra_fixture }, ) } @@ -174,7 +175,7 @@ impl CallLocations { #[cfg(test)] mod tests { use expect_test::{Expect, expect}; - use ide_db::{FilePosition, MiniCore}; + use ide_db::{FilePosition, ra_fixture::RaFixtureConfig}; use itertools::Itertools; use crate::fixture; @@ -197,7 +198,8 @@ mod tests { ) } - let config = crate::CallHierarchyConfig { exclude_tests, minicore: MiniCore::default() }; + let config = + crate::CallHierarchyConfig { exclude_tests, ra_fixture: RaFixtureConfig::default() }; let (analysis, pos) = fixture::position(ra_fixture); let mut navs = analysis.call_hierarchy(pos, &config).unwrap().unwrap().info; diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs b/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs index 375ce94bf644f..d2b47a37c7b00 100644 --- a/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs +++ b/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs @@ -79,13 +79,13 @@ pub(crate) fn goto_declaration( #[cfg(test)] mod tests { - use ide_db::{FileRange, MiniCore}; + use ide_db::{FileRange, ra_fixture::RaFixtureConfig}; use itertools::Itertools; use crate::{GotoDefinitionConfig, fixture}; const TEST_CONFIG: GotoDefinitionConfig<'_> = - GotoDefinitionConfig { minicore: MiniCore::default() }; + GotoDefinitionConfig { ra_fixture: RaFixtureConfig::default() }; fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str) { let (analysis, position, expected) = fixture::annotations(ra_fixture); 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 3890bcad7fc61..4cdf0eac7568f 100644 --- a/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs +++ b/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs @@ -9,7 +9,7 @@ use crate::{ use hir::{ AsAssocItem, AssocItem, CallableKind, FileRange, HasCrate, InFile, ModuleDef, Semantics, sym, }; -use ide_db::{MiniCore, ra_fixture::UpmapFromRaFixture}; +use ide_db::ra_fixture::{RaFixtureConfig, UpmapFromRaFixture}; use ide_db::{ RootDatabase, SymbolKind, base_db::{AnchoredPath, SourceDatabase}, @@ -26,7 +26,7 @@ use syntax::{ #[derive(Debug)] pub struct GotoDefinitionConfig<'a> { - pub minicore: MiniCore<'a>, + pub ra_fixture: RaFixtureConfig<'a>, } // Feature: Go to Definition @@ -105,7 +105,7 @@ pub(crate) fn goto_definition( if let Some(token) = ast::String::cast(token.value.clone()) && let Some(original_token) = ast::String::cast(original_token.clone()) && let Some((analysis, fixture_analysis)) = - Analysis::from_ra_fixture(sema, original_token, &token, config.minicore) + Analysis::from_ra_fixture(sema, original_token, &token, &config.ra_fixture) && let Some((virtual_file_id, file_offset)) = fixture_analysis.map_offset_down(offset) { return hir::attach_db_allow_change(&analysis.db, || { @@ -605,11 +605,11 @@ fn expr_to_nav( #[cfg(test)] mod tests { use crate::{GotoDefinitionConfig, fixture}; - use ide_db::{FileRange, MiniCore}; + use ide_db::{FileRange, ra_fixture::RaFixtureConfig}; use itertools::Itertools; const TEST_CONFIG: GotoDefinitionConfig<'_> = - GotoDefinitionConfig { minicore: MiniCore::default() }; + GotoDefinitionConfig { ra_fixture: RaFixtureConfig::default() }; #[track_caller] fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str) { diff --git a/src/tools/rust-analyzer/crates/ide/src/hover.rs b/src/tools/rust-analyzer/crates/ide/src/hover.rs index 958de8930d8a1..df1fcecc991fe 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover.rs @@ -8,11 +8,11 @@ use std::{iter, ops::Not}; use either::Either; use hir::{DisplayTarget, GenericDef, GenericSubstitution, HasCrate, HasSource, Semantics}; use ide_db::{ - FileRange, FxIndexSet, MiniCore, Ranker, RootDatabase, + FileRange, FxIndexSet, Ranker, RootDatabase, defs::{Definition, IdentClass, NameRefClass, OperatorClass}, famous_defs::FamousDefs, helpers::pick_best_token, - ra_fixture::UpmapFromRaFixture, + ra_fixture::{RaFixtureConfig, UpmapFromRaFixture}, }; use itertools::{Itertools, multizip}; use macros::UpmapFromRaFixture; @@ -44,7 +44,7 @@ pub struct HoverConfig<'a> { pub max_enum_variants_count: Option, pub max_subst_ty_len: SubstTyLen, pub show_drop_glue: bool, - pub minicore: MiniCore<'a>, + pub ra_fixture: RaFixtureConfig<'a>, } #[derive(Clone, Debug, PartialEq, Eq)] @@ -221,7 +221,7 @@ fn hover_offset( if let Some(literal) = ast::String::cast(original_token.clone()) && let Some((analysis, fixture_analysis)) = - Analysis::from_ra_fixture(sema, literal.clone(), &literal, config.minicore) + Analysis::from_ra_fixture(sema, literal.clone(), &literal, &config.ra_fixture) { let (virtual_file_id, virtual_offset) = fixture_analysis.map_offset_down(offset)?; return analysis @@ -422,7 +422,7 @@ fn hover_ranged( Either::Left(ast::Expr::Literal(literal)) => { if let Some(literal) = ast::String::cast(literal.token()) && let Some((analysis, fixture_analysis)) = - Analysis::from_ra_fixture(sema, literal.clone(), &literal, config.minicore) + Analysis::from_ra_fixture(sema, literal.clone(), &literal, &config.ra_fixture) { let (virtual_file_id, virtual_range) = fixture_analysis.map_range_down(range)?; return analysis 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 7fbbc576dd36a..7a758cd4c1399 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs @@ -1,5 +1,5 @@ use expect_test::{Expect, expect}; -use ide_db::{FileRange, MiniCore, base_db::SourceDatabase}; +use ide_db::{FileRange, base_db::SourceDatabase, ra_fixture::RaFixtureConfig}; use syntax::TextRange; use crate::{ @@ -25,7 +25,7 @@ const HOVER_BASE_CONFIG: HoverConfig<'_> = HoverConfig { max_enum_variants_count: Some(5), max_subst_ty_len: super::SubstTyLen::Unlimited, show_drop_glue: true, - minicore: MiniCore::default(), + ra_fixture: RaFixtureConfig::default(), }; fn check_hover_no_result(#[rust_analyzer::rust_fixture] ra_fixture: &str) { diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs index a58dc6f030559..83f266043311f 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs @@ -9,7 +9,8 @@ use hir::{ HirDisplay, HirDisplayError, HirWrite, InRealFile, ModuleDef, ModuleDefId, Semantics, sym, }; use ide_db::{ - FileRange, MiniCore, RootDatabase, famous_defs::FamousDefs, text_edit::TextEditBuilder, + FileRange, RootDatabase, famous_defs::FamousDefs, ra_fixture::RaFixtureConfig, + text_edit::TextEditBuilder, }; use ide_db::{FxHashSet, text_edit::TextEdit}; use itertools::Itertools; @@ -328,7 +329,7 @@ pub struct InlayHintsConfig<'a> { pub max_length: Option, pub closing_brace_hints_min_lines: Option, pub fields_to_resolve: InlayFieldsToResolve, - pub minicore: MiniCore<'a>, + pub ra_fixture: RaFixtureConfig<'a>, } impl InlayHintsConfig<'_> { @@ -899,7 +900,7 @@ mod tests { use expect_test::Expect; use hir::ClosureStyle; - use ide_db::MiniCore; + use ide_db::ra_fixture::RaFixtureConfig; use itertools::Itertools; use test_utils::extract_annotations; @@ -942,7 +943,7 @@ mod tests { implicit_drop_hints: false, implied_dyn_trait_hints: false, range_exclusive_hints: false, - minicore: MiniCore::default(), + ra_fixture: RaFixtureConfig::default(), }; pub(super) const TEST_CONFIG: InlayHintsConfig<'_> = InlayHintsConfig { type_hints: true, diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/ra_fixture.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/ra_fixture.rs index bee18416424cf..701c8a8612e93 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/ra_fixture.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/ra_fixture.rs @@ -16,7 +16,7 @@ pub(super) fn hints( let file_id = file_id.file_id(sema.db); let literal = ast::String::cast(literal.token())?; let (analysis, fixture_analysis) = - Analysis::from_ra_fixture(sema, literal.clone(), &literal, config.minicore)?; + Analysis::from_ra_fixture(sema, literal.clone(), &literal, &config.ra_fixture)?; for virtual_file_id in fixture_analysis.files() { acc.extend( analysis diff --git a/src/tools/rust-analyzer/crates/ide/src/lib.rs b/src/tools/rust-analyzer/crates/ide/src/lib.rs index 81a771fec89e1..2f89efd33ff46 100644 --- a/src/tools/rust-analyzer/crates/ide/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide/src/lib.rs @@ -63,6 +63,7 @@ use std::panic::{AssertUnwindSafe, UnwindSafe}; use cfg::CfgOptions; use fetch_crates::CrateInfo; use hir::{ChangeWithProcMacros, EditionedFileId, crate_def_map, sym}; +use ide_db::ra_fixture::RaFixtureAnalysis; use ide_db::{ FxHashMap, FxIndexSet, LineIndexDatabase, base_db::{ @@ -71,7 +72,6 @@ use ide_db::{ }, prime_caches, symbol_index, }; -use ide_db::{MiniCore, ra_fixture::RaFixtureAnalysis}; use macros::UpmapFromRaFixture; use syntax::{AstNode, SourceFile, ast}; use triomphe::Arc; @@ -135,6 +135,7 @@ pub use ide_db::{ label::Label, line_index::{LineCol, LineIndex}, prime_caches::ParallelPrimeCachesProgress, + ra_fixture::RaFixtureConfig, search::{ReferenceCategory, SearchScope}, source_change::{FileSystemEdit, SnippetEdit, SourceChange}, symbol_index::Query, @@ -289,9 +290,9 @@ impl Analysis { sema: &Semantics<'_, RootDatabase>, literal: ast::String, expanded: &ast::String, - minicore: MiniCore<'_>, + config: &RaFixtureConfig<'_>, ) -> Option<(Analysis, RaFixtureAnalysis)> { - Self::from_ra_fixture_with_on_cursor(sema, literal, expanded, minicore, &mut |_| {}) + Self::from_ra_fixture_with_on_cursor(sema, literal, expanded, config, &mut |_| {}) } /// Like [`Analysis::from_ra_fixture()`], but also calls `on_cursor` with the cursor position. @@ -299,11 +300,11 @@ impl Analysis { sema: &Semantics<'_, RootDatabase>, literal: ast::String, expanded: &ast::String, - minicore: MiniCore<'_>, + config: &RaFixtureConfig<'_>, on_cursor: &mut dyn FnMut(TextRange), ) -> Option<(Analysis, RaFixtureAnalysis)> { let analysis = - RaFixtureAnalysis::analyze_ra_fixture(sema, literal, expanded, minicore, on_cursor)?; + RaFixtureAnalysis::analyze_ra_fixture(sema, literal, expanded, config, on_cursor)?; Some((Analysis { db: analysis.db.clone() }, analysis)) } diff --git a/src/tools/rust-analyzer/crates/ide/src/references.rs b/src/tools/rust-analyzer/crates/ide/src/references.rs index 9392651c17943..0288099bbcc2b 100644 --- a/src/tools/rust-analyzer/crates/ide/src/references.rs +++ b/src/tools/rust-analyzer/crates/ide/src/references.rs @@ -19,10 +19,10 @@ use hir::{PathResolution, Semantics}; use ide_db::{ - FileId, MiniCore, RootDatabase, + FileId, RootDatabase, defs::{Definition, NameClass, NameRefClass}, helpers::pick_best_token, - ra_fixture::UpmapFromRaFixture, + ra_fixture::{RaFixtureConfig, UpmapFromRaFixture}, search::{ReferenceCategory, SearchScope, UsageSearchResult}, }; use itertools::Itertools; @@ -90,7 +90,7 @@ pub struct Declaration { #[derive(Debug)] pub struct FindAllRefsConfig<'a> { pub search_scope: Option, - pub minicore: MiniCore<'a>, + pub ra_fixture: RaFixtureConfig<'a>, } /// Find all references to the item at the given position. @@ -179,7 +179,7 @@ pub(crate) fn find_all_refs( if let Some(token) = syntax.token_at_offset(position.offset).left_biased() && let Some(token) = ast::String::cast(token.clone()) && let Some((analysis, fixture_analysis)) = - Analysis::from_ra_fixture(sema, token.clone(), &token, config.minicore) + Analysis::from_ra_fixture(sema, token.clone(), &token, &config.ra_fixture) && let Some((virtual_file_id, file_offset)) = fixture_analysis.map_offset_down(position.offset) { @@ -462,7 +462,7 @@ fn handle_control_flow_keywords( mod tests { use expect_test::{Expect, expect}; use hir::EditionedFileId; - use ide_db::{FileId, MiniCore, RootDatabase}; + use ide_db::{FileId, RootDatabase, ra_fixture::RaFixtureConfig}; use stdx::format_to; use crate::{SearchScope, fixture, references::FindAllRefsConfig}; @@ -1567,7 +1567,7 @@ fn main() { let (analysis, pos) = fixture::position(ra_fixture); let config = FindAllRefsConfig { search_scope: search_scope.map(|it| it(&analysis.db)), - minicore: MiniCore::default(), + ra_fixture: RaFixtureConfig::default(), }; let refs = analysis.find_all_refs(pos, &config).unwrap().unwrap(); diff --git a/src/tools/rust-analyzer/crates/ide/src/static_index.rs b/src/tools/rust-analyzer/crates/ide/src/static_index.rs index aba6b64f977a5..2f63aa0d8cd40 100644 --- a/src/tools/rust-analyzer/crates/ide/src/static_index.rs +++ b/src/tools/rust-analyzer/crates/ide/src/static_index.rs @@ -4,11 +4,12 @@ use arrayvec::ArrayVec; use hir::{Crate, Module, Semantics, db::HirDatabase}; use ide_db::{ - FileId, FileRange, FxHashMap, FxHashSet, MiniCore, RootDatabase, + FileId, FileRange, FxHashMap, FxHashSet, RootDatabase, base_db::{RootQueryDb, SourceDatabase, VfsPath}, defs::{Definition, IdentClass}, documentation::Documentation, famous_defs::FamousDefs, + ra_fixture::RaFixtureConfig, }; use syntax::{AstNode, SyntaxNode, SyntaxToken, TextRange}; @@ -196,7 +197,7 @@ impl StaticIndex<'_> { closing_brace_hints_min_lines: Some(25), fields_to_resolve: InlayFieldsToResolve::empty(), range_exclusive_hints: false, - minicore: MiniCore::default(), + ra_fixture: RaFixtureConfig::default(), }, file_id, None, @@ -225,7 +226,7 @@ impl StaticIndex<'_> { max_enum_variants_count: Some(5), max_subst_ty_len: SubstTyLen::Unlimited, show_drop_glue: true, - minicore: MiniCore::default(), + ra_fixture: RaFixtureConfig::default(), }; let mut result = StaticIndexedFile { file_id, inlay_hints, folds, tokens: vec![] }; diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs index 217b13b4ef95d..9fd3f005ec70a 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs @@ -17,7 +17,7 @@ use either::Either; use hir::{ DefWithBody, EditionedFileId, ExpressionStoreOwner, InFile, InRealFile, MacroKind, Semantics, }; -use ide_db::{FxHashMap, FxHashSet, MiniCore, Ranker, RootDatabase, SymbolKind}; +use ide_db::{FxHashMap, FxHashSet, Ranker, RootDatabase, SymbolKind, ra_fixture::RaFixtureConfig}; use syntax::{ AstNode, AstToken, NodeOrToken, SyntaxKind::*, @@ -65,7 +65,7 @@ pub struct HighlightConfig<'a> { pub macro_bang: bool, /// Whether to highlight unresolved things be their syntax pub syntactic_name_ref_highlighting: bool, - pub minicore: MiniCore<'a>, + pub ra_fixture: RaFixtureConfig<'a>, } // Feature: Semantic Syntax Highlighting diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/html.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/html.rs index 74567e82139fe..423c0c349cd24 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/html.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/html.rs @@ -1,7 +1,7 @@ //! Renders a bit of code as HTML. use hir::Semantics; -use ide_db::MiniCore; +use ide_db::ra_fixture::RaFixtureConfig; use oorandom::Rand32; use stdx::format_to; use syntax::AstNode; @@ -69,7 +69,7 @@ pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: boo inject_doc_comment: true, macro_bang: true, syntactic_name_ref_highlighting: false, - minicore: MiniCore::default(), + ra_fixture: RaFixtureConfig::default(), }, file_id, rainbow, diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/inject.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/inject.rs index 74a8d93dfe82c..6afe5681a9b5c 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/inject.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/inject.rs @@ -27,7 +27,7 @@ pub(super) fn ra_fixture( sema, literal.clone(), expanded, - config.minicore, + &config.ra_fixture, &mut |range| { hl.add(HlRange { range, @@ -56,7 +56,7 @@ pub(super) fn ra_fixture( macro_bang: config.macro_bang, // What if there is a fixture inside a fixture? It's fixtures all the way down. // (In fact, we have a fixture inside a fixture in our test suite!) - minicore: config.minicore, + ra_fixture: config.ra_fixture, }, tmp_file_id, ) @@ -186,7 +186,7 @@ pub(super) fn doc_comment( specialize_operator: config.operator, inject_doc_comment: config.inject_doc_comment, macro_bang: config.macro_bang, - minicore: config.minicore, + ra_fixture: config.ra_fixture, }, tmp_file_id, None, diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs index aecd1d3fdb562..e8d185b7b6369 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs @@ -1,7 +1,7 @@ use std::time::Instant; use expect_test::{ExpectFile, expect_file}; -use ide_db::{MiniCore, SymbolKind}; +use ide_db::{SymbolKind, ra_fixture::RaFixtureConfig}; use span::Edition; use test_utils::{AssertLinear, bench, bench_fixture, skip_slow_tests}; @@ -17,7 +17,7 @@ const HL_CONFIG: HighlightConfig<'_> = HighlightConfig { inject_doc_comment: true, macro_bang: true, syntactic_name_ref_highlighting: false, - minicore: MiniCore::default(), + ra_fixture: RaFixtureConfig::default(), }; #[test] 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 74828cba02eda..19d5e8fdced11 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 @@ -23,10 +23,10 @@ use hir_def::{ use hir_ty::InferenceResult; use ide::{ Analysis, AnalysisHost, AnnotationConfig, DiagnosticsConfig, Edition, InlayFieldsToResolve, - InlayHintsConfig, LineCol, RootDatabase, + InlayHintsConfig, LineCol, RaFixtureConfig, RootDatabase, }; use ide_db::{ - EditionedFileId, LineIndexDatabase, MiniCore, SnippetCap, + EditionedFileId, LineIndexDatabase, SnippetCap, base_db::{SourceDatabase, salsa::Database}, }; use itertools::Itertools; @@ -1397,7 +1397,7 @@ impl flags::AnalysisStats { closing_brace_hints_min_lines: Some(20), fields_to_resolve: InlayFieldsToResolve::empty(), range_exclusive_hints: true, - minicore: MiniCore::default(), + ra_fixture: RaFixtureConfig::default(), }, analysis.editioned_file_id_to_vfs(file_id), None, @@ -1416,7 +1416,7 @@ impl flags::AnalysisStats { annotate_enum_variant_references: false, location: ide::AnnotationLocation::AboveName, filter_adjacent_derive_implementations: false, - minicore: MiniCore::default(), + ra_fixture: RaFixtureConfig::default(), }; for &file_id in file_ids { let msg = format!("annotations: {}", vfs.file_path(file_id.file_id(db))); diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs index 2ccd85f0e34ec..eb390f024fdf6 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs @@ -12,7 +12,8 @@ use ide::{ CompletionFieldsToResolve, DiagnosticsConfig, GenericParameterHints, GotoDefinitionConfig, GotoImplementationConfig, HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayFieldsToResolve, InlayHintsConfig, JoinLinesConfig, MemoryLayoutHoverConfig, - MemoryLayoutHoverRenderKind, RenameConfig, Snippet, SnippetScope, SourceRootId, + MemoryLayoutHoverRenderKind, RaFixtureConfig, RenameConfig, Snippet, SnippetScope, + SourceRootId, }; use ide_db::{ MiniCore, SnippetCap, @@ -727,6 +728,11 @@ config_data! { /// the `Problems Panel`. diagnostics_warningsAsInfo: Vec = vec![], + /// Disable support for `#[rust_analyzer::rust_fixture]` snippets. + /// + /// If you are not working on rust-analyzer itself, you should ignore this config. + disableFixtureSupport: bool = false, + /// Enforce the import granularity setting for all files. If set to false rust-analyzer will /// try to keep import styles consistent per file. imports_granularity_enforce: bool = false, @@ -1504,6 +1510,8 @@ pub struct LensConfig { // annotations pub location: AnnotationLocation, pub filter_adjacent_derive_implementations: bool, + + disable_ra_fixture: bool, } #[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -1559,7 +1567,7 @@ impl LensConfig { annotate_method_references: self.method_refs, annotate_enum_variant_references: self.enum_variant_refs, location: self.location.into(), - minicore, + ra_fixture: RaFixtureConfig { minicore, disable_ra_fixture: self.disable_ra_fixture }, filter_adjacent_derive_implementations: self.filter_adjacent_derive_implementations, } } @@ -1816,8 +1824,15 @@ impl Config { } } + pub fn ra_fixture<'a>(&self, minicore: MiniCore<'a>) -> RaFixtureConfig<'a> { + RaFixtureConfig { minicore, disable_ra_fixture: *self.disableFixtureSupport(None) } + } + pub fn call_hierarchy<'a>(&self, minicore: MiniCore<'a>) -> CallHierarchyConfig<'a> { - CallHierarchyConfig { exclude_tests: self.references_excludeTests().to_owned(), minicore } + CallHierarchyConfig { + exclude_tests: self.references_excludeTests().to_owned(), + ra_fixture: self.ra_fixture(minicore), + } } pub fn completion<'a>( @@ -1878,7 +1893,7 @@ impl Config { }) .collect(), exclude_traits: self.completion_excludeTraits(source_root), - minicore, + ra_fixture: self.ra_fixture(minicore), } } @@ -1987,12 +2002,12 @@ impl Config { None => ide::SubstTyLen::Unlimited, }, show_drop_glue: *self.hover_dropGlue_enable(), - minicore, + ra_fixture: self.ra_fixture(minicore), } } pub fn goto_definition<'a>(&self, minicore: MiniCore<'a>) -> GotoDefinitionConfig<'a> { - GotoDefinitionConfig { minicore } + GotoDefinitionConfig { ra_fixture: self.ra_fixture(minicore) } } pub fn inlay_hints<'a>(&self, minicore: MiniCore<'a>) -> InlayHintsConfig<'a> { @@ -2082,7 +2097,7 @@ impl Config { implicit_drop_hints: self.inlayHints_implicitDrops_enable().to_owned(), implied_dyn_trait_hints: self.inlayHints_impliedDynTraitHints_enable().to_owned(), range_exclusive_hints: self.inlayHints_rangeExclusiveHints_enable().to_owned(), - minicore, + ra_fixture: self.ra_fixture(minicore), } } @@ -2135,7 +2150,7 @@ impl Config { .to_owned(), inject_doc_comment: self.semanticHighlighting_doc_comment_inject_enable().to_owned(), syntactic_name_ref_highlighting: false, - minicore, + ra_fixture: self.ra_fixture(minicore), } } @@ -2621,6 +2636,7 @@ impl Config { location: *self.lens_location(), filter_adjacent_derive_implementations: *self .gotoImplementations_filterAdjacentDerives(), + disable_ra_fixture: *self.disableFixtureSupport(None), } } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs index ad07da77597de..64b4e39449ccc 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs @@ -1395,7 +1395,10 @@ pub(crate) fn handle_references( let Some(refs) = snap.analysis.find_all_refs( position, - &FindAllRefsConfig { search_scope: None, minicore: snap.minicore() }, + &FindAllRefsConfig { + search_scope: None, + ra_fixture: snap.config.ra_fixture(snap.minicore()), + }, )? else { return Ok(None); @@ -2202,7 +2205,10 @@ fn show_ref_command_link( .analysis .find_all_refs( *position, - &FindAllRefsConfig { search_scope: None, minicore: snap.minicore() }, + &FindAllRefsConfig { + search_scope: None, + ra_fixture: snap.config.ra_fixture(snap.minicore()), + }, ) .unwrap_or(None) { diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs index 6a74b8a54deb6..af449c473a85e 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/integrated_benchmarks.rs @@ -13,10 +13,10 @@ use hir::ChangeWithProcMacros; use ide::{ AnalysisHost, CallableSnippets, CompletionConfig, CompletionFieldsToResolve, DiagnosticsConfig, - FilePosition, TextSize, + FilePosition, RaFixtureConfig, TextSize, }; use ide_db::{ - MiniCore, SnippetCap, + SnippetCap, imports::insert_use::{ImportGranularity, InsertUseConfig}, }; use project_model::CargoConfig; @@ -190,7 +190,7 @@ fn integrated_completion_benchmark() { exclude_traits: &[], enable_auto_await: true, enable_auto_iter: true, - minicore: MiniCore::default(), + ra_fixture: RaFixtureConfig::default(), }; let position = FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() }; @@ -245,7 +245,7 @@ fn integrated_completion_benchmark() { exclude_traits: &[], enable_auto_await: true, enable_auto_iter: true, - minicore: MiniCore::default(), + ra_fixture: RaFixtureConfig::default(), }; let position = FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() }; @@ -298,7 +298,7 @@ fn integrated_completion_benchmark() { exclude_traits: &[], enable_auto_await: true, enable_auto_iter: true, - minicore: MiniCore::default(), + ra_fixture: RaFixtureConfig::default(), }; let position = FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() }; diff --git a/src/tools/rust-analyzer/docs/book/src/configuration_generated.md b/src/tools/rust-analyzer/docs/book/src/configuration_generated.md index 35fba5accdbbe..31ef4fddead81 100644 --- a/src/tools/rust-analyzer/docs/book/src/configuration_generated.md +++ b/src/tools/rust-analyzer/docs/book/src/configuration_generated.md @@ -618,6 +618,15 @@ The warnings will be indicated by a blue squiggly underline in code and a blue i the `Problems Panel`. +## rust-analyzer.disableFixtureSupport {#disableFixtureSupport} + +Default: `false` + +Disable support for `#[rust_analyzer::rust_fixture]` snippets. + +If you are not working on rust-analyzer itself, you should ignore this config. + + ## rust-analyzer.document.symbol.search.excludeLocals {#document.symbol.search.excludeLocals} Default: `true` diff --git a/src/tools/rust-analyzer/editors/code/package.json b/src/tools/rust-analyzer/editors/code/package.json index 1dd513c9de404..a1a414b4f454a 100644 --- a/src/tools/rust-analyzer/editors/code/package.json +++ b/src/tools/rust-analyzer/editors/code/package.json @@ -1591,6 +1591,16 @@ } } }, + { + "title": "rust-analyzer", + "properties": { + "rust-analyzer.disableFixtureSupport": { + "markdownDescription": "Disable support for `#[rust_analyzer::rust_fixture]` snippets.\n\nIf you are not working on rust-analyzer itself, you should ignore this config.", + "default": false, + "type": "boolean" + } + } + }, { "title": "Document", "properties": { From 12439cb7451c296368d5d44901621b5462906568 Mon Sep 17 00:00:00 2001 From: The rustc-josh-sync Cronjob Bot Date: Mon, 30 Mar 2026 05:04:33 +0000 Subject: [PATCH 024/144] Prepare for merging from rust-lang/rust This updates the rust-version file to 80ad55752e5ae6c2d1bc143b819eb8d1c00167d1. --- src/tools/rust-analyzer/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/rust-version b/src/tools/rust-analyzer/rust-version index 68f38716dbb0b..a89983e1de3de 100644 --- a/src/tools/rust-analyzer/rust-version +++ b/src/tools/rust-analyzer/rust-version @@ -1 +1 @@ -1174f784096deb8e4ba93f7e4b5ccb7bb4ba2c55 +80ad55752e5ae6c2d1bc143b819eb8d1c00167d1 From e82a38040ebd2c653cc23131db8306492a012ca2 Mon Sep 17 00:00:00 2001 From: Weixie Cui Date: Thu, 26 Mar 2026 10:01:16 +0800 Subject: [PATCH 025/144] fix: Correct missing-args messages for sched_getaffinity and getenv shims These branches reused the libc::write error string when arguments were absent. Assisted by an AI coding tool (see CONTRIBUTING.md). Signed-off-by: Weixie Cui --- src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim.rs index a0dd3b5846f48..3924cb264297c 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/shim.rs @@ -521,7 +521,7 @@ impl<'db> Evaluator<'db> { "sched_getaffinity" => { let [_pid, _set_size, set] = args else { return Err(MirEvalError::InternalError( - "libc::write args are not provided".into(), + "sched_getaffinity args are not provided".into(), )); }; let set = Address::from_bytes(set.get(self)?)?; @@ -533,9 +533,7 @@ impl<'db> Evaluator<'db> { } "getenv" => { let [name] = args else { - return Err(MirEvalError::InternalError( - "libc::write args are not provided".into(), - )); + return Err(MirEvalError::InternalError("getenv args are not provided".into())); }; let mut name_buf = vec![]; let name = { From d29df23ea5bdbad59b771db86e623f1af138566c Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Mon, 30 Mar 2026 12:35:11 +0800 Subject: [PATCH 026/144] feat: offer with else-branch on tail-expr For assist 'convert_to_guarded_return' Example --- ```rust fn main() -> i32 { if$0 true { foo(); } else { bar() } } ``` **Before this PR** Assist not applicable **After this PR** ```rust fn main() -> i32 { if false { return bar(); } foo(); } ``` --- .../src/handlers/convert_to_guarded_return.rs | 251 ++++++++++++++---- .../crates/ide-assists/src/tests.rs | 2 + 2 files changed, 201 insertions(+), 52 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs index db45916792206..f9fa6cc66cceb 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs @@ -1,17 +1,19 @@ use std::iter::once; use either::Either; -use hir::{Semantics, TypeInfo}; +use hir::Semantics; use ide_db::{RootDatabase, ty_filter::TryEnum}; use syntax::{ AstNode, - SyntaxKind::{CLOSURE_EXPR, FN, FOR_EXPR, LOOP_EXPR, WHILE_EXPR, WHITESPACE}, + SyntaxKind::WHITESPACE, SyntaxNode, T, ast::{ self, edit::{AstNodeEdit, IndentLevel}, syntax_factory::SyntaxFactory, }, + match_ast, + syntax_editor::SyntaxEditor, }; use crate::{ @@ -71,9 +73,7 @@ fn if_expr_to_guarded_return( ) -> Option<()> { let make = SyntaxFactory::without_mappings(); let else_block = match if_expr.else_branch() { - Some(ast::ElseBranch::Block(block_expr)) if is_never_block(&ctx.sema, &block_expr) => { - Some(block_expr) - } + Some(ast::ElseBranch::Block(block_expr)) => Some(block_expr), Some(_) => return None, _ => None, }; @@ -96,25 +96,20 @@ fn if_expr_to_guarded_return( let parent_block = if_expr.syntax().parent()?.ancestors().find_map(ast::BlockExpr::cast)?; - if parent_block.tail_expr() != Some(if_expr.clone().into()) - && !(else_block.is_some() && ast::ExprStmt::can_cast(if_expr.syntax().parent()?.kind())) - { - return None; - } - // check for early return and continue if is_early_block(&then_block) || is_never_block(&ctx.sema, &then_branch) { return None; } let parent_container = parent_block.syntax().parent()?; + let else_block = ElseBlock::new(&ctx.sema, else_block, &parent_container)?; - let early_expression = else_block - .or_else(|| { - early_expression(parent_container, &ctx.sema, &make) - .map(ast::make::tail_only_block_expr) - })? - .reset_indent(); + if parent_block.tail_expr() != Some(if_expr.clone().into()) + && !(else_block.is_never_block + && ast::ExprStmt::can_cast(if_expr.syntax().parent()?.kind())) + { + return None; + } then_block.syntax().first_child_or_token().map(|t| t.kind() == T!['{'])?; @@ -137,6 +132,7 @@ fn if_expr_to_guarded_return( |edit| { let make = SyntaxFactory::without_mappings(); let if_indent_level = IndentLevel::from_node(if_expr.syntax()); + let early_expression = else_block.make_early_block(&ctx.sema, &make); let replacement = let_chains.into_iter().map(|expr| { if let ast::Expr::LetExpr(let_expr) = &expr && let (Some(pat), Some(expr)) = (let_expr.pat(), let_expr.expr()) @@ -204,14 +200,9 @@ fn let_stmt_to_guarded_return( let happy_pattern = try_enum.happy_pattern(pat); let target = let_stmt.syntax().text_range(); - let make = SyntaxFactory::without_mappings(); - let early_expression: ast::Expr = { - let parent_block = - let_stmt.syntax().parent()?.ancestors().find_map(ast::BlockExpr::cast)?; - let parent_container = parent_block.syntax().parent()?; - - early_expression(parent_container, &ctx.sema, &make)? - }; + let parent_block = let_stmt.syntax().parent()?.ancestors().find_map(ast::BlockExpr::cast)?; + let parent_container = parent_block.syntax().parent()?; + let else_block = ElseBlock::new(&ctx.sema, None, &parent_container)?; acc.add( AssistId::refactor_rewrite("convert_to_guarded_return"), @@ -226,7 +217,7 @@ fn let_stmt_to_guarded_return( happy_pattern, let_stmt.ty(), expr.reset_indent(), - ast::make::tail_only_block_expr(early_expression), + else_block.make_early_block(&ctx.sema, &make), ); let let_else_stmt = let_else_stmt.indent(let_indent_level); let_else_stmt.syntax().clone() @@ -239,33 +230,119 @@ fn let_stmt_to_guarded_return( ) } -fn early_expression( - parent_container: SyntaxNode, - sema: &Semantics<'_, RootDatabase>, - make: &SyntaxFactory, -) -> Option { - let return_none_expr = || { - let none_expr = make.expr_path(make.ident_path("None")); - make.expr_return(Some(none_expr)) - }; - if let Some(fn_) = ast::Fn::cast(parent_container.clone()) - && let Some(fn_def) = sema.to_def(&fn_) - && let Some(TryEnum::Option) = TryEnum::from_ty(sema, &fn_def.ret_type(sema.db)) - { - return Some(return_none_expr().into()); +struct ElseBlock<'db> { + exist_else_block: Option, + is_never_block: bool, + kind: EarlyKind<'db>, +} + +impl<'db> ElseBlock<'db> { + fn new( + sema: &Semantics<'db, RootDatabase>, + exist_else_block: Option, + parent_container: &SyntaxNode, + ) -> Option { + let is_never_block = exist_else_block.as_ref().is_some_and(|it| is_never_block(sema, it)); + let kind = EarlyKind::from_node(parent_container, sema)?; + + Some(Self { exist_else_block, is_never_block, kind }) } - if let Some(body) = ast::ClosureExpr::cast(parent_container.clone()).and_then(|it| it.body()) - && let Some(ret_ty) = sema.type_of_expr(&body).map(TypeInfo::original) - && let Some(TryEnum::Option) = TryEnum::from_ty(sema, &ret_ty) - { - return Some(return_none_expr().into()); + + fn make_early_block( + self, + sema: &Semantics<'_, RootDatabase>, + make: &SyntaxFactory, + ) -> ast::BlockExpr { + let Some(block_expr) = self.exist_else_block else { + return make.tail_only_block_expr(self.kind.make_early_expr(sema, make, None)); + }; + + if self.is_never_block { + return block_expr.reset_indent(); + } + + let block_expr = block_expr.reset_indent().clone_subtree(); + let last_stmt = block_expr.statements().last().map(|it| it.syntax().clone()); + let tail_expr = block_expr.tail_expr().map(|it| it.syntax().clone()); + let Some(last_element) = tail_expr.clone().or(last_stmt.clone()) else { + return make.tail_only_block_expr(self.kind.make_early_expr(sema, make, None)); + }; + let whitespace = last_element.prev_sibling_or_token().filter(|it| it.kind() == WHITESPACE); + + let make = SyntaxFactory::without_mappings(); + let mut edit = SyntaxEditor::new(block_expr.syntax().clone()); + + if let Some(tail_expr) = block_expr.tail_expr() + && !self.kind.is_unit() + { + let early_expr = self.kind.make_early_expr(sema, &make, Some(tail_expr.clone())); + edit.replace(tail_expr.syntax(), early_expr.syntax()); + } else { + let last_stmt = match block_expr.tail_expr() { + Some(expr) => make.expr_stmt(expr).syntax().clone(), + None => last_element.clone_for_update(), + }; + let whitespace = + make.whitespace(&whitespace.map_or(String::new(), |it| it.to_string())); + let early_expr = self.kind.make_early_expr(sema, &make, None).syntax().clone().into(); + edit.replace_with_many( + last_element, + vec![last_stmt.into(), whitespace.into(), early_expr], + ); + } + + ast::BlockExpr::cast(edit.finish().new_root().clone()).unwrap() } +} - Some(match parent_container.kind() { - WHILE_EXPR | LOOP_EXPR | FOR_EXPR => make.expr_continue(None).into(), - FN | CLOSURE_EXPR => make.expr_return(None).into(), - _ => return None, - }) +enum EarlyKind<'db> { + Continue, + Return(hir::Type<'db>), +} + +impl<'db> EarlyKind<'db> { + fn from_node( + parent_container: &SyntaxNode, + sema: &Semantics<'db, RootDatabase>, + ) -> Option { + match_ast! { + match parent_container { + ast::Fn(it) => Some(Self::Return(sema.to_def(&it)?.ret_type(sema.db))), + ast::ClosureExpr(it) => Some(Self::Return(sema.type_of_expr(&it.body()?)?.original)), + ast::WhileExpr(_) => Some(Self::Continue), + ast::LoopExpr(_) => Some(Self::Continue), + ast::ForExpr(_) => Some(Self::Continue), + _ => None + } + } + } + + fn make_early_expr( + &self, + sema: &Semantics<'_, RootDatabase>, + make: &SyntaxFactory, + ret: Option, + ) -> ast::Expr { + match self { + EarlyKind::Continue => make.expr_continue(None).into(), + EarlyKind::Return(ty) => { + let expr = match TryEnum::from_ty(sema, ty) { + Some(TryEnum::Option) => { + ret.or_else(|| Some(make.expr_path(make.ident_path("None")))) + } + _ => ret, + }; + make.expr_return(expr).into() + } + } + } + + fn is_unit(&self) -> bool { + match self { + EarlyKind::Continue => true, + EarlyKind::Return(ty) => ty.is_unit(), + } + } } fn flat_let_chain(mut expr: ast::Expr, make: &SyntaxFactory) -> Vec { @@ -464,6 +541,74 @@ fn main() { ); } + #[test] + fn convert_if_let_has_else_block() { + check_assist( + convert_to_guarded_return, + r#" +fn main() -> i32 { + if$0 true { + foo(); + } else { + bar() + } +} +"#, + r#" +fn main() -> i32 { + if false { + return bar(); + } + foo(); +} +"#, + ); + + check_assist( + convert_to_guarded_return, + r#" +fn main() { + if$0 true { + foo(); + } else { + bar() + } +} +"#, + r#" +fn main() { + if false { + bar(); + return + } + foo(); +} +"#, + ); + + check_assist( + convert_to_guarded_return, + r#" +fn main() { + if$0 true { + foo(); + } else { + bar(); + } +} +"#, + r#" +fn main() { + if false { + bar(); + return + } + foo(); +} +"#, + ); + } + #[test] fn convert_if_let_has_never_type_else_block() { check_assist( @@ -512,7 +657,7 @@ fn main() { } #[test] - fn convert_if_let_has_else_block_in_statement() { + fn convert_if_let_has_never_type_else_block_in_statement() { check_assist( convert_to_guarded_return, r#" @@ -1186,16 +1331,18 @@ fn main() { } #[test] - fn ignore_else_branch() { + fn ignore_else_branch_has_non_never_types_in_statement() { check_assist_not_applicable( convert_to_guarded_return, r#" fn main() { + some_statements(); if$0 true { foo(); } else { bar() } + some_statements(); } "#, ); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs b/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs index a52bd74d146a4..1c90c95fe155f 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs @@ -479,6 +479,7 @@ pub fn test_some_range(a: int) -> bool { expect![[r#" Extract into... Replace if let with match + Convert to guarded return "#]] .assert_eq(&expected); } @@ -511,6 +512,7 @@ pub fn test_some_range(a: int) -> bool { expect![[r#" Extract into... Replace if let with match + Convert to guarded return "#]] .assert_eq(&expected); } From e45ac4b1b757213b6175bce955f6594b4c603bae Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 30 Mar 2026 10:59:06 +0530 Subject: [PATCH 027/144] accept make::ext:path_from_indents for Syntaxfactory constructor and add mapping for token tree from node constructor --- .../crates/syntax/src/ast/syntax_factory/constructors.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs index 14bd66d79d771..5db31c3bf5351 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs @@ -2053,13 +2053,7 @@ impl SyntaxFactory { &self, parts: impl IntoIterator, ) -> Option { - let mut iter = parts.into_iter(); - let base = self.ident_path(iter.next()?); - let path = iter.fold(base, |base, s| { - let segment = self.ident_path(s); - self.path_concat(base, segment) - }); - Some(path) + make::ext::path_from_idents(parts).map(|path| path.clone_for_update()) } pub fn token_tree_from_node(&self, node: &SyntaxNode) -> ast::TokenTree { From 5e21b1715ea28209c6a2acf4661758d68440c303 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 30 Mar 2026 15:17:00 +0530 Subject: [PATCH 028/144] fix doc link --- src/tools/rust-analyzer/crates/hir-def/src/expr_store.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store.rs index ca523622ec844..62a17168b18eb 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store.rs @@ -520,7 +520,7 @@ impl ExpressionStore { self.const_expr_origins().iter().map(|&(id, _)| id) } - /// Like [`Self::signature_const_expr_roots`], but also returns the origin + /// Like [`Self::expr_roots`], but also returns the origin /// of each expression. pub fn expr_roots_with_origins(&self) -> impl Iterator { self.const_expr_origins().iter().map(|&(id, origin)| (id, origin)) From f8c9427819c4e69d981ff8a504c4e1c17fabce14 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Tue, 31 Mar 2026 00:54:19 +0300 Subject: [PATCH 029/144] Fix a cycle in bounds lowering Those will never cease to surprise me. Basically, an associated type bound can be either `Trait` or `Self::Assoc: Trait`. The former is included in `explicit_implied_predicates_of()` and therefore in elaboration, but the later is not. We included both, so fix that. This does not fix the fundamental issue that cycles in elaboration can cause hangs/stack overflows, just this incorrect case. rustc deals with cycles by detecting them ahead of time (before any elaboration) and aborting with an error, I'm not sure yet how to handle them for r-a. Also refactor the code a bit (the hundredth time) in an attempt to make it clearer, and return iterators instead of slices from the functions to be more flexible. --- .../crates/hir-ty/src/builtin_derive.rs | 15 ++- .../crates/hir-ty/src/display.rs | 4 +- .../crates/hir-ty/src/dyn_compatibility.rs | 10 +- .../crates/hir-ty/src/infer/expr.rs | 2 +- .../crates/hir-ty/src/infer/path.rs | 2 +- .../rust-analyzer/crates/hir-ty/src/lower.rs | 103 +++++++++--------- .../crates/hir-ty/src/method_resolution.rs | 2 +- .../hir-ty/src/method_resolution/confirm.rs | 4 +- .../hir-ty/src/method_resolution/probe.rs | 2 +- .../crates/hir-ty/src/next_solver/interner.rs | 92 +++++++--------- .../crates/hir-ty/src/next_solver/ty.rs | 2 +- .../crates/hir-ty/src/specialization.rs | 2 +- .../crates/hir-ty/src/tests/regression.rs | 15 +++ .../rust-analyzer/crates/hir/src/display.rs | 4 +- src/tools/rust-analyzer/crates/hir/src/lib.rs | 2 +- 15 files changed, 134 insertions(+), 127 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/builtin_derive.rs b/src/tools/rust-analyzer/crates/hir-ty/src/builtin_derive.rs index 92629b7a05324..eb3922f4b6233 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/builtin_derive.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/builtin_derive.rs @@ -174,8 +174,11 @@ pub fn predicates<'db>(db: &'db dyn HirDatabase, impl_: BuiltinDeriveImplId) -> if matches!(loc.adt, AdtId::EnumId(_)) { // Enums don't have extra bounds. GenericPredicates::from_explicit_own_predicates(StoredEarlyBinder::bind( - Clauses::new_from_slice(adt_predicates.explicit_predicates().skip_binder()) - .store(), + Clauses::new_from_iter( + interner, + adt_predicates.own_explicit_predicates().skip_binder(), + ) + .store(), )) } else { simple_trait_predicates(interner, loc, generic_params, adt_predicates, trait_id) @@ -191,7 +194,7 @@ pub fn predicates<'db>(db: &'db dyn HirDatabase, impl_: BuiltinDeriveImplId) -> )); }; let duplicated_bounds = - adt_predicates.explicit_predicates().iter_identity_copied().filter_map(|pred| { + adt_predicates.explicit_predicates().iter_identity().filter_map(|pred| { let mentions_pointee = pred.visit_with(&mut MentionsPointee { pointee_param_idx }).is_break(); if !mentions_pointee { @@ -212,7 +215,7 @@ pub fn predicates<'db>(db: &'db dyn HirDatabase, impl_: BuiltinDeriveImplId) -> interner, adt_predicates .explicit_predicates() - .iter_identity_copied() + .iter_identity() .chain(duplicated_bounds) .chain(unsize_bound), ) @@ -313,7 +316,7 @@ fn simple_trait_predicates<'db>( interner, adt_predicates .explicit_predicates() - .iter_identity_copied() + .iter_identity() .chain(extra_predicates) .chain(assoc_type_bounds), ) @@ -440,7 +443,7 @@ mod tests { format_to!( predicates, "{}\n\n", - preds.iter().format_with("\n", |pred, formatter| formatter(&format_args!( + preds.format_with("\n", |pred, formatter| formatter(&format_args!( "{pred:?}" ))), ); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/display.rs b/src/tools/rust-analyzer/crates/hir-ty/src/display.rs index d680588645644..0c4e34db7db0e 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/display.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/display.rs @@ -640,7 +640,7 @@ fn write_projection<'db>( // FIXME: We shouldn't use `param.id`, it should be removed. We should know the // `GenericDefId` from the formatted type (store it inside the `HirFormatter`). let bounds = GenericPredicates::query_all(f.db, param.id.parent()) - .iter_identity_copied() + .iter_identity() .filter(|wc| { let ty = match wc.kind().skip_binder() { ClauseKind::Trait(tr) => tr.self_ty(), @@ -1466,7 +1466,7 @@ impl<'db> HirDisplay<'db> for Ty<'db> { } TypeParamProvenance::ArgumentImplTrait => { let bounds = GenericPredicates::query_all(f.db, param.id.parent()) - .iter_identity_copied() + .iter_identity() .filter(|wc| match wc.kind().skip_binder() { ClauseKind::Trait(tr) => tr.self_ty() == *self, ClauseKind::Projection(proj) => proj.self_ty() == *self, diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/dyn_compatibility.rs b/src/tools/rust-analyzer/crates/hir-ty/src/dyn_compatibility.rs index 4c300affd8a2d..e70918f8e1125 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/dyn_compatibility.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/dyn_compatibility.rs @@ -141,7 +141,7 @@ pub fn generics_require_sized_self(db: &dyn HirDatabase, def: GenericDefId) -> b // FIXME: We should use `explicit_predicates_of` here, which hasn't been implemented to // rust-analyzer yet // https://github.com/rust-lang/rust/blob/ddaf12390d3ffb7d5ba74491a48f3cd528e5d777/compiler/rustc_hir_analysis/src/collect/predicates_of.rs#L490 - elaborate::elaborate(interner, predicates.iter_identity_copied()).any(|pred| { + elaborate::elaborate(interner, predicates.iter_identity()).any(|pred| { match pred.kind().skip_binder() { ClauseKind::Trait(trait_pred) => { if sized == trait_pred.def_id().0 @@ -164,7 +164,7 @@ pub fn generics_require_sized_self(db: &dyn HirDatabase, def: GenericDefId) -> b // So, just return single boolean value for existence of such `Self` reference fn predicates_reference_self(db: &dyn HirDatabase, trait_: TraitId) -> bool { GenericPredicates::query_explicit(db, trait_.into()) - .iter_identity_copied() + .iter_identity() .any(|pred| predicate_references_self(db, trait_, pred, AllowSelfProjection::No)) } @@ -360,8 +360,8 @@ where cb(MethodViolationCode::UndispatchableReceiver)?; } - let predicates = GenericPredicates::query_own(db, func.into()); - for pred in predicates.iter_identity_copied() { + let predicates = GenericPredicates::query_own_explicit(db, func.into()); + for pred in predicates.iter_identity() { let pred = pred.kind().skip_binder(); if matches!(pred, ClauseKind::TypeOutlives(_)) { @@ -459,7 +459,7 @@ fn receiver_is_dispatchable<'db>( clauses: Clauses::new_from_iter( interner, generic_predicates - .iter_identity_copied() + .iter_identity() .chain([unsize_predicate.upcast(interner), trait_predicate.upcast(interner)]) .chain(meta_sized_predicate), ), diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs index dc57b1d1c215b..ee34a30ebaaf0 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs @@ -2158,7 +2158,7 @@ impl<'db> InferenceContext<'_, 'db> { ); let param_env = self.table.param_env; self.table.register_predicates(clauses_as_obligations( - generic_predicates.iter_instantiated_copied(self.interner(), parameters.as_slice()), + generic_predicates.iter_instantiated(self.interner(), parameters.as_slice()), ObligationCause::new(), param_env, )); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/path.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/path.rs index 71d68ccd47a6a..3cadc8e93359e 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/path.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/path.rs @@ -228,7 +228,7 @@ impl<'db> InferenceContext<'_, 'db> { let predicates = GenericPredicates::query_all(self.db, def); let param_env = self.table.param_env; self.table.register_predicates(clauses_as_obligations( - predicates.iter_instantiated_copied(interner, subst.as_slice()), + predicates.iter_instantiated(interner, subst.as_slice()), ObligationCause::new(), param_env, )); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs index 7259099107cad..71a7db6559a86 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs @@ -2016,17 +2016,21 @@ fn type_alias_bounds_with_diagnostics<'db>( #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct GenericPredicates { - // The order is the following: first, if `parent_is_trait == true`, comes the implicit trait - // predicate for the parent. Then come the bounds of the associated types of the parents, - // then the explicit, self-only predicates for the parent, then the explicit, self-only trait - // predicate for the child, then the bounds of the associated types of the child, - // then the implicit trait predicate for the child, if `is_trait` is `true`. + // The order is the following: + // + // 1. If `has_trait_implied_predicate == true`, the implicit trait predicate. + // 2. The bounds of the associated types of the parents, coming from `Trait`. + // Note: associated type bounds from `Self::Assoc: Trait` on traits *won't* be included + // here, they are in 3. + // 3. The explicit, self-only predicates for the parent. + // 4. The explicit, self-only trait predicate for the child, + // 5. The bounds of the associated types of the child. predicates: StoredEarlyBinder, + // Keep this ordered according to the above. + has_trait_implied_predicate: bool, parent_explicit_self_predicates_start: u32, own_predicates_start: u32, own_assoc_ty_bounds_start: u32, - is_trait: bool, - parent_is_trait: bool, } #[salsa::tracked] @@ -2065,11 +2069,10 @@ impl GenericPredicates { let len = predicates.get().skip_binder().len() as u32; Self { predicates, + has_trait_implied_predicate: false, parent_explicit_self_predicates_start: 0, own_predicates_start: 0, own_assoc_ty_bounds_start: len, - is_trait: false, - parent_is_trait: false, } } @@ -2082,58 +2085,68 @@ impl GenericPredicates { pub fn query_all<'db>( db: &'db dyn HirDatabase, def: GenericDefId, - ) -> EarlyBinder<'db, &'db [Clause<'db>]> { + ) -> EarlyBinder<'db, impl Iterator>> { Self::query(db, def).all_predicates() } #[inline] - pub fn query_own<'db>( + pub fn query_own_explicit<'db>( db: &'db dyn HirDatabase, def: GenericDefId, - ) -> EarlyBinder<'db, &'db [Clause<'db>]> { - Self::query(db, def).own_predicates() + ) -> EarlyBinder<'db, impl Iterator>> { + Self::query(db, def).own_explicit_predicates() } #[inline] pub fn query_explicit<'db>( db: &'db dyn HirDatabase, def: GenericDefId, - ) -> EarlyBinder<'db, &'db [Clause<'db>]> { + ) -> EarlyBinder<'db, impl Iterator>> { Self::query(db, def).explicit_predicates() } #[inline] - pub fn query_explicit_implied<'db>( - db: &'db dyn HirDatabase, - def: GenericDefId, - ) -> EarlyBinder<'db, &'db [Clause<'db>]> { - Self::query(db, def).explicit_implied_predicates() + pub fn all_predicates(&self) -> EarlyBinder<'_, impl Iterator>> { + self.predicates.get().map_bound(|it| it.as_slice().iter().copied()) } #[inline] - pub fn all_predicates(&self) -> EarlyBinder<'_, &[Clause<'_>]> { - self.predicates.get().map_bound(|it| it.as_slice()) + pub fn own_explicit_predicates(&self) -> EarlyBinder<'_, impl Iterator>> { + self.predicates + .get() + .map_bound(|it| it.as_slice()[self.own_predicates_start as usize..].iter().copied()) } #[inline] - pub fn own_predicates(&self) -> EarlyBinder<'_, &[Clause<'_>]> { - self.predicates.get().map_bound(|it| &it.as_slice()[self.own_predicates_start as usize..]) + pub fn explicit_predicates(&self) -> EarlyBinder<'_, impl Iterator>> { + self.predicates.get().map_bound(|it| { + it.as_slice()[usize::from(self.has_trait_implied_predicate)..].iter().copied() + }) } - /// Returns the predicates, minus the implicit `Self: Trait` predicate and bounds of the - /// associated types for a trait. #[inline] - pub fn explicit_predicates(&self) -> EarlyBinder<'_, &[Clause<'_>]> { + pub fn explicit_non_assoc_types_predicates( + &self, + ) -> EarlyBinder<'_, impl Iterator>> { self.predicates.get().map_bound(|it| { - &it.as_slice()[self.parent_explicit_self_predicates_start as usize + it.as_slice()[self.parent_explicit_self_predicates_start as usize ..self.own_assoc_ty_bounds_start as usize] + .iter() + .copied() }) } #[inline] - pub fn explicit_implied_predicates(&self) -> EarlyBinder<'_, &[Clause<'_>]> { - self.predicates.get().map_bound(|it| { - &it.as_slice()[usize::from(self.parent_is_trait)..it.len() - usize::from(self.is_trait)] + pub fn explicit_assoc_types_predicates( + &self, + ) -> EarlyBinder<'_, impl Iterator>> { + self.predicates.get().map_bound(|predicates| { + let predicates = predicates.as_slice(); + predicates[usize::from(self.has_trait_implied_predicate) + ..self.parent_explicit_self_predicates_start as usize] + .iter() + .copied() + .chain(predicates[self.own_assoc_ty_bounds_start as usize..].iter().copied()) }) } } @@ -2142,10 +2155,8 @@ pub(crate) fn param_env_from_predicates<'db>( interner: DbInterner<'db>, predicates: &'db GenericPredicates, ) -> ParamEnv<'db> { - let clauses = rustc_type_ir::elaborate::elaborate( - interner, - predicates.all_predicates().iter_identity_copied(), - ); + let clauses = + rustc_type_ir::elaborate::elaborate(interner, predicates.all_predicates().iter_identity()); let clauses = Clauses::new_from_iter(interner, clauses); // FIXME: We should normalize projections here, like rustc does. @@ -2290,42 +2301,28 @@ fn generic_predicates(db: &dyn HirDatabase, def: GenericDefId) -> (GenericPredic let diagnostics = create_diagnostics(ctx.diagnostics); - // The order is: - // - // 1. parent implicit trait pred - // 2. parent assoc bounds - // 3. parent self only preds - // 4. own self only preds - // 5. own assoc ty bounds - // 6. own implicit trait pred - // - // The purpose of this is to index the slice of the followings, without making extra `Vec`s or - // iterators: - // - explicit self only predicates, of own or own + self - // - explicit predicates, of own or own + self let predicates = parent_implicit_trait_predicate .iter() + .chain(own_implicit_trait_predicate.iter()) .chain(parent_assoc_ty_bounds.iter()) .chain(parent_predicates.iter()) .chain(own_predicates.iter()) .chain(own_assoc_ty_bounds.iter()) - .chain(own_implicit_trait_predicate.iter()) .copied() .collect::>(); - let parent_is_trait = parent_implicit_trait_predicate.is_some(); - let is_trait = own_implicit_trait_predicate.is_some(); + let has_trait_implied_predicate = + parent_implicit_trait_predicate.is_some() || own_implicit_trait_predicate.is_some(); let parent_explicit_self_predicates_start = - parent_is_trait as u32 + parent_assoc_ty_bounds.len() as u32; + has_trait_implied_predicate as u32 + parent_assoc_ty_bounds.len() as u32; let own_predicates_start = parent_explicit_self_predicates_start + parent_predicates.len() as u32; let own_assoc_ty_bounds_start = own_predicates_start + own_predicates.len() as u32; let predicates = GenericPredicates { + has_trait_implied_predicate, parent_explicit_self_predicates_start, own_predicates_start, own_assoc_ty_bounds_start, - is_trait, - parent_is_trait, predicates: StoredEarlyBinder::bind(Clauses::new_from_slice(&predicates).store()), }; return (predicates, diagnostics); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs b/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs index 05b9ea5d748fe..b18e48c1fed36 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs @@ -324,7 +324,7 @@ impl<'db> InferenceTable<'db> { // any late-bound regions appearing in its bounds. let bounds = GenericPredicates::query_all(self.db, method_item.into()); let bounds = clauses_as_obligations( - bounds.iter_instantiated_copied(interner, args.as_slice()), + bounds.iter_instantiated(interner, args.as_slice()), ObligationCause::new(), self.param_env, ); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution/confirm.rs b/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution/confirm.rs index ec589085a88de..94c70c29f74bc 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution/confirm.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution/confirm.rs @@ -136,7 +136,7 @@ impl<'a, 'b, 'db> ConfirmContext<'a, 'b, 'db> { ); let illegal_sized_bound = self.predicates_require_illegal_sized_bound( GenericPredicates::query_all(self.db(), self.candidate.into()) - .iter_instantiated_copied(self.interner(), filler_args.as_slice()), + .iter_instantiated(self.interner(), filler_args.as_slice()), ); // Unify the (adjusted) self type with what the method expects. @@ -509,7 +509,7 @@ impl<'a, 'b, 'db> ConfirmContext<'a, 'b, 'db> { let def_id = self.candidate; let method_predicates = clauses_as_obligations( GenericPredicates::query_all(self.db(), def_id.into()) - .iter_instantiated_copied(self.interner(), all_args), + .iter_instantiated(self.interner(), all_args), ObligationCause::new(), self.ctx.table.param_env, ); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution/probe.rs b/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution/probe.rs index 8c76bfbc076b2..3604076ccdc77 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution/probe.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution/probe.rs @@ -1595,7 +1595,7 @@ impl<'a, 'db, Choice: ProbeChoice<'db>> ProbeContext<'a, 'db, Choice> { // Check whether the impl imposes obligations we have to worry about. let impl_bounds = GenericPredicates::query_all(self.db(), impl_def_id.into()); let impl_bounds = clauses_as_obligations( - impl_bounds.iter_instantiated_copied(self.interner(), impl_args.as_slice()), + impl_bounds.iter_instantiated(self.interner(), impl_args.as_slice()), ObligationCause::new(), self.param_env(), ); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/interner.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/interner.rs index 5b81c7675dbbf..622648bc8d52b 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/interner.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/interner.rs @@ -1439,81 +1439,55 @@ impl<'db> Interner for DbInterner<'db> { } } - #[tracing::instrument(level = "debug", skip(self), ret)] fn predicates_of( self, def_id: Self::DefId, ) -> EarlyBinder> { - predicates_of(self.db, def_id).all_predicates().map_bound(|it| it.iter().copied()) + predicates_of(self.db, def_id).all_predicates() } - #[tracing::instrument(level = "debug", skip(self), ret)] fn own_predicates_of( self, def_id: Self::DefId, ) -> EarlyBinder> { - predicates_of(self.db, def_id).own_predicates().map_bound(|it| it.iter().copied()) + predicates_of(self.db, def_id).own_explicit_predicates() } - #[tracing::instrument(skip(self), ret)] fn explicit_super_predicates_of( self, def_id: Self::TraitId, ) -> EarlyBinder> { - let is_self = |ty: Ty<'db>| match ty.kind() { - rustc_type_ir::TyKind::Param(param) => param.index == 0, - _ => false, - }; - - GenericPredicates::query_explicit(self.db, def_id.0.into()).map_bound(move |predicates| { - predicates - .iter() - .copied() - .filter(move |p| match p.kind().skip_binder() { - // rustc has the following assertion: - // https://github.com/rust-lang/rust/blob/52618eb338609df44978b0ca4451ab7941fd1c7a/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs#L525-L608 - ClauseKind::Trait(it) => is_self(it.self_ty()), - ClauseKind::TypeOutlives(it) => is_self(it.0), - ClauseKind::Projection(it) => is_self(it.self_ty()), - ClauseKind::HostEffect(it) => is_self(it.self_ty()), - _ => false, - }) - .map(|p| (p, Span::dummy())) - }) + GenericPredicates::query(self.db, def_id.0.into()) + .explicit_non_assoc_types_predicates() + .map_bound(move |predicates| { + predicates.filter(|p| is_clause_at_ty(p, is_ty_self)).map(|p| (p, Span::dummy())) + }) } - #[tracing::instrument(skip(self), ret)] fn explicit_implied_predicates_of( self, def_id: Self::DefId, ) -> EarlyBinder> { - fn is_self_or_assoc(ty: Ty<'_>) -> bool { - match ty.kind() { - rustc_type_ir::TyKind::Param(param) => param.index == 0, - rustc_type_ir::TyKind::Alias(rustc_type_ir::AliasTyKind::Projection, alias) => { - is_self_or_assoc(alias.self_ty()) - } - _ => false, + fn is_ty_assoc_of_self(ty: Ty<'_>) -> bool { + // FIXME: Is this correct wrt. combined kind of assoc type bounds, i.e. `where Self::Assoc: Trait` + // wrt. `Assoc2`, which we should exclude? + if let TyKind::Alias(AliasTyKind::Projection, alias) = ty.kind() { + is_ty_assoc_of_self(alias.self_ty()) + } else { + is_ty_self(ty) } } - predicates_of(self.db, def_id).explicit_implied_predicates().map_bound(|predicates| { - predicates - .iter() - .copied() - .filter(|p| match p.kind().skip_binder() { - ClauseKind::Trait(it) => is_self_or_assoc(it.self_ty()), - ClauseKind::TypeOutlives(it) => is_self_or_assoc(it.0), - ClauseKind::Projection(it) => is_self_or_assoc(it.self_ty()), - ClauseKind::HostEffect(it) => is_self_or_assoc(it.self_ty()), - // FIXME: Not sure is this correct to allow other clauses but we might replace - // `generic_predicates_ns` query here with something closer to rustc's - // `implied_bounds_with_filter`, which is more granular lowering than this - // "lower at once and then filter" implementation. - _ => true, - }) - .map(|p| (p, Span::dummy())) - }) + let predicates = predicates_of(self.db, def_id); + let non_assoc_types = predicates + .explicit_non_assoc_types_predicates() + .skip_binder() + .filter(|p| is_clause_at_ty(p, is_ty_self)); + let assoc_types = predicates + .explicit_assoc_types_predicates() + .skip_binder() + .filter(|p| is_clause_at_ty(p, is_ty_assoc_of_self)); + EarlyBinder::bind(non_assoc_types.chain(assoc_types).map(|it| (it, Span::dummy()))) } fn impl_super_outlives( @@ -2294,6 +2268,24 @@ impl<'db> Interner for DbInterner<'db> { } } +fn is_ty_self(ty: Ty<'_>) -> bool { + match ty.kind() { + TyKind::Param(param) => param.index == 0, + _ => false, + } +} +fn is_clause_at_ty(p: &Clause<'_>, filter: impl FnOnce(Ty<'_>) -> bool) -> bool { + match p.kind().skip_binder() { + // rustc has the following assertion: + // https://github.com/rust-lang/rust/blob/52618eb338609df44978b0ca4451ab7941fd1c7a/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs#L525-L608 + ClauseKind::Trait(it) => filter(it.self_ty()), + ClauseKind::TypeOutlives(it) => filter(it.0), + ClauseKind::Projection(it) => filter(it.self_ty()), + ClauseKind::HostEffect(it) => filter(it.self_ty()), + _ => false, + } +} + impl<'db> DbInterner<'db> { pub fn shift_bound_var_indices(self, bound_vars: usize, value: T) -> T where diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/ty.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/ty.rs index 192cdb70aee5e..8e892b65ea383 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/ty.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/ty.rs @@ -696,7 +696,7 @@ impl<'db> Ty<'db> { TypeOrConstParamData::TypeParamData(p) => match p.provenance { TypeParamProvenance::ArgumentImplTrait => { let predicates = GenericPredicates::query_all(db, param.id.parent()) - .iter_identity_copied() + .iter_identity() .filter(|wc| match wc.kind().skip_binder() { ClauseKind::Trait(tr) => tr.self_ty() == self, ClauseKind::Projection(pred) => pred.self_ty() == self, diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/specialization.rs b/src/tools/rust-analyzer/crates/hir-ty/src/specialization.rs index 90cbcfea6abef..8bc6c51fae6f0 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/specialization.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/specialization.rs @@ -109,7 +109,7 @@ fn specializes_query( // only be referenced via projection predicates. ocx.register_obligations(clauses_as_obligations( GenericPredicates::query_all(db, parent_impl_def_id.into()) - .iter_instantiated_copied(interner, parent_args.as_slice()), + .iter_instantiated(interner, parent_args.as_slice()), cause.clone(), param_env, )); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs index e4fc7e56c6ae7..d3dfc44c227f9 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs @@ -2841,3 +2841,18 @@ fn wrapped_abs>(v: T) -> T { "#, ); } + +#[test] +fn regression_21899() { + check_no_mismatches( + r#" +trait B where + Self::T: B, +{ + type T; +} + +fn foo(v: T::T) {} + "#, + ); +} diff --git a/src/tools/rust-analyzer/crates/hir/src/display.rs b/src/tools/rust-analyzer/crates/hir/src/display.rs index 4bfdd239f9374..53f24713cdccc 100644 --- a/src/tools/rust-analyzer/crates/hir/src/display.rs +++ b/src/tools/rust-analyzer/crates/hir/src/display.rs @@ -76,7 +76,7 @@ fn write_builtin_derive_impl_method<'db>( let predicates = hir_ty::builtin_derive::predicates(db, impl_).explicit_predicates().skip_binder(); - write_params_bounds(f, predicates)?; + write_params_bounds(f, &Vec::from_iter(predicates))?; } Ok(()) @@ -578,7 +578,7 @@ impl<'db> HirDisplay<'db> for TypeParam { let ty = self.ty(f.db).ty; let predicates = GenericPredicates::query_all(f.db, self.id.parent()); let predicates = predicates - .iter_identity_copied() + .iter_identity() .filter(|wc| match wc.kind().skip_binder() { ClauseKind::Trait(tr) => tr.self_ty() == ty, ClauseKind::Projection(proj) => proj.self_ty() == ty, diff --git a/src/tools/rust-analyzer/crates/hir/src/lib.rs b/src/tools/rust-analyzer/crates/hir/src/lib.rs index bc5e164830546..eb5b3b37a66db 100644 --- a/src/tools/rust-analyzer/crates/hir/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir/src/lib.rs @@ -4680,7 +4680,7 @@ impl TypeParam { pub fn trait_bounds(self, db: &dyn HirDatabase) -> Vec { let self_ty = self.ty(db).ty; GenericPredicates::query_explicit(db, self.id.parent()) - .iter_identity_copied() + .iter_identity() .filter_map(|pred| match &pred.kind().skip_binder() { ClauseKind::Trait(trait_ref) if trait_ref.self_ty() == self_ty => { Some(Trait::from(trait_ref.def_id().0)) From d08892e642290597b948c5d7720350492d7d602c Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Tue, 31 Mar 2026 04:30:34 +0300 Subject: [PATCH 030/144] implement `feature(more_qualified_paths)` Specifically, this allows the following patterns and expressions which were not allowed before: ```rust let ::Assoc { a } = ::Assoc { a: 0 }; let (::Assoc::ES { a } | ::Assoc::ET(a)) = ::Assoc::ES { a: 0 }; let (::ES { a } | ::ET(a)) = ::ES { a: 0 }; ``` Co-authored-by: Waffle Lapkin Co-authored-by: Chayim Refael Friedman --- .../rust-analyzer/crates/hir-ty/src/infer.rs | 100 +++++++++++++++- .../crates/hir-ty/src/tests/traits.rs | 111 ++++++++++++++++-- 2 files changed, 199 insertions(+), 12 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs index d14e9d6526540..bd897113bf0e5 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs @@ -1706,6 +1706,61 @@ impl<'body, 'db> InferenceContext<'body, 'db> { self.generic_def, LifetimeElisionKind::Infer, ); + + if let Some(type_anchor) = path.type_anchor() { + let mut segments = path.segments(); + if segments.is_empty() { + return (self.err_ty(), None); + } + let (mut ty, type_ns) = ctx.lower_ty_ext(type_anchor); + ty = self.table.process_user_written_ty(ty); + + if let Some(TypeNs::SelfType(impl_)) = type_ns + && let Some(trait_ref) = self.db.impl_trait(impl_) + && let trait_ref = trait_ref.instantiate_identity() + && let Some(assoc_type) = trait_ref + .def_id + .0 + .trait_items(self.db) + .associated_type_by_name(segments.first().unwrap().name) + { + // `::AssocType` + let args = self.infcx().fill_rest_fresh_args(assoc_type.into(), trait_ref.args); + let alias = Ty::new_alias( + self.interner(), + AliasTyKind::Projection, + AliasTy::new_from_args(self.interner(), assoc_type.into(), args), + ); + ty = self.table.try_structurally_resolve_type(alias); + segments = segments.skip(1); + } + + let variant = match ty.as_adt() { + Some((AdtId::StructId(id), _)) => id.into(), + Some((AdtId::UnionId(id), _)) => id.into(), + Some((AdtId::EnumId(id), _)) => { + if let Some(segment) = segments.first() + && let enum_data = id.enum_variants(self.db) + && let Some(variant) = enum_data.variant(segment.name) + { + // FIXME: Report error if there are generics on the variant. + segments = segments.skip(1); + variant.into() + } else { + return (self.err_ty(), None); + } + } + None => return (self.err_ty(), None), + }; + + if !segments.is_empty() { + // FIXME: Report an error. + return (self.err_ty(), None); + } else { + return (ty, Some(variant)); + } + } + let mut path_ctx = ctx.at_path(path, node); let interner = DbInterner::conjure(); let (resolution, unresolved) = if value_ns { @@ -1838,6 +1893,46 @@ impl<'body, 'db> InferenceContext<'body, 'db> { }); (ty, variant) } + TypeNs::TraitId(_) => { + let Some(remaining_idx) = unresolved else { + return (self.err_ty(), None); + }; + + let remaining_segments = path.segments().skip(remaining_idx); + + if remaining_segments.len() >= 2 { + path_ctx.ignore_last_segment(); + } + + let (mut ty, _) = path_ctx.lower_partly_resolved_path(resolution, true); + ty = self.table.process_user_written_ty(ty); + + if let Some(segment) = remaining_segments.get(1) + && let Some((AdtId::EnumId(id), _)) = ty.as_adt() + { + let enum_data = id.enum_variants(self.db); + if let Some(variant) = enum_data.variant(segment.name) { + return if remaining_segments.len() == 2 { + (ty, Some(variant.into())) + } else { + // We still have unresolved paths, but enum variants never have + // associated types! + // FIXME: Report an error. + (self.err_ty(), None) + }; + } + } + + let variant = ty.as_adt().and_then(|(id, _)| match id { + AdtId::StructId(s) => Some(VariantId::StructId(s)), + AdtId::UnionId(u) => Some(VariantId::UnionId(u)), + AdtId::EnumId(_) => { + // FIXME Error E0071, expected struct, variant or union type, found enum `Foo` + None + } + }); + (ty, variant) + } TypeNs::TypeAliasId(it) => { let Some(mod_path) = path.mod_path() else { never!("resolver should always resolve lang item paths"); @@ -1859,10 +1954,7 @@ impl<'body, 'db> InferenceContext<'body, 'db> { // FIXME potentially resolve assoc type (self.err_ty(), None) } - TypeNs::AdtId(AdtId::EnumId(_)) - | TypeNs::BuiltinType(_) - | TypeNs::TraitId(_) - | TypeNs::ModuleId(_) => { + TypeNs::AdtId(AdtId::EnumId(_)) | TypeNs::BuiltinType(_) | TypeNs::ModuleId(_) => { // FIXME diagnostic (self.err_ty(), None) } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs index 22359d8f1f1d4..1d27d52a36604 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs @@ -4449,14 +4449,14 @@ impl Trait for () { let a = Self::Assoc { x }; // ^ S let a = ::Assoc { x }; // unstable - // ^ {unknown} + // ^ S // should be `Copy` but we don't track ownership anyway. let value = S { x }; if let Self::Assoc { x } = value {} // ^ u32 if let ::Assoc { x } = value {} // unstable - // ^ {unknown} + // ^ u32 } } "#, @@ -4508,22 +4508,22 @@ impl Trait for () { let a = Self::Assoc::Struct { x }; // ^ E let a = ::Assoc::Struct { x }; // unstable - // ^ {unknown} + // ^ E let a = ::Struct { x }; // unstable - // ^ {unknown} + // ^ E let a = <::Assoc>::Struct { x }; // unstable - // ^ {unknown} + // ^ E // should be `Copy` but we don't track ownership anyway. let value = E::Struct { x: 42 }; if let Self::Assoc::Struct { x } = value {} // ^ u32 if let ::Assoc::Struct { x } = value {} // unstable - // ^ {unknown} + // ^ u32 if let ::Struct { x } = value {} // unstable - // ^ {unknown} + // ^ u32 if let <::Assoc>::Struct { x } = value {} // unstable - // ^ {unknown} + // ^ u32 } } "#, @@ -5148,3 +5148,98 @@ fn foo(v: Struct) { "#, ); } + +#[test] +fn more_qualified_paths() { + check_infer( + r#" +struct T; +struct S { + a: u32, +} + +trait Trait { + type Assoc; + + fn foo(); +} + +impl Trait for T { + type Assoc = S; + + fn foo() { + let ::Assoc { a } = ::Assoc { a: 0 }; + } +} + +enum E { + ES { a: u32 }, + ET(u32), +} + +impl Trait for E { + type Assoc = Self; + + fn foo() { + let ::Assoc::ES { a } = ::Assoc::ES { a: 0 }; + } +} + +fn foo() { + let ::Assoc { a } = ::Assoc { a: 0 }; + + let ::ES { a } = (::ES { a: 0 }) else { loop {} }; + let ::ET(a) = ::ET(0) else { loop {} }; + let ::Assoc::ES { a } = (::Assoc::ES { a: 0 }) else { loop {} }; + let ::Assoc::ET(a) = ::Assoc::ET(0) else { loop {} }; +} + "#, + expect![[r#" + 137..202 '{ ... }': () + 151..170 '... { a }': S + 167..168 'a': u32 + 173..195 '...a: 0 }': S + 192..193 '0': u32 + 306..379 '{ ... }': () + 320..343 '... { a }': E + 340..341 'a': u32 + 346..372 '...a: 0 }': E + 369..370 '0': u32 + 392..748 '{ ...} }; }': () + 402..427 '::ES { a }': E + 479..480 'a': u32 + 486..502 '::E...a: 0 }': E + 499..500 '0': u32 + 509..520 '{ loop {} }': ! + 511..518 'loop {}': ! + 516..518 '{}': () + 530..540 '::ET(a)': E + 538..539 'a': u32 + 543..550 '::ET': fn ET(u32) -> E + 543..553 '::ET(0)': E + 551..552 '0': u32 + 559..570 '{ loop {} }': ! + 561..568 'loop {}': ! + 566..568 '{}': () + 580..609 ' E + 702..728 ' Date: Tue, 31 Mar 2026 12:13:54 +0800 Subject: [PATCH 031/144] Add a test for convert tail-expr to continue --- .../src/handlers/convert_to_guarded_return.rs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs index f9fa6cc66cceb..e59527b0e0951 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs @@ -1067,6 +1067,37 @@ fn main() { ); } + #[test] + fn convert_let_inside_for_with_else() { + check_assist( + convert_to_guarded_return, + r#" +fn main() { + for n in ns { + if$0 let Some(n) = n { + foo(n); + bar(); + } else { + baz() + } + } +} +"#, + r#" +fn main() { + for n in ns { + let Some(n) = n else { + baz(); + continue + }; + foo(n); + bar(); + } +} +"#, + ); + } + #[test] fn convert_let_stmt_inside_fn() { check_assist( From 5e1b388bf8d1c22ca4a03144596c44f19a01e80c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lauren=C8=9Biu=20Nicola?= Date: Tue, 31 Mar 2026 11:51:22 +0300 Subject: [PATCH 032/144] Bump @vscode/vsce and ovsx --- .../editors/code/package-lock.json | 486 ++++++++++++++++-- .../rust-analyzer/editors/code/package.json | 4 +- 2 files changed, 458 insertions(+), 32 deletions(-) diff --git a/src/tools/rust-analyzer/editors/code/package-lock.json b/src/tools/rust-analyzer/editors/code/package-lock.json index b51dc4d1320df..1c626e392c910 100644 --- a/src/tools/rust-analyzer/editors/code/package-lock.json +++ b/src/tools/rust-analyzer/editors/code/package-lock.json @@ -27,12 +27,12 @@ "@typescript-eslint/eslint-plugin": "^8.25.0", "@typescript-eslint/parser": "^8.25.0", "@vscode/test-electron": "^2.4.1", - "@vscode/vsce": "^3.6.0", + "@vscode/vsce": "^3.7.1", "esbuild": "^0.25.0", "eslint": "^9.21.0", "eslint-config-prettier": "^10.0.2", "eslint-define-config": "^2.1.0", - "ovsx": "0.10.1", + "ovsx": "0.10.10", "prettier": "^3.5.2", "tslib": "^2.8.1", "typescript": "^5.7.3", @@ -255,6 +255,40 @@ "node": ">=6.9.0" } }, + "node_modules/@emnapi/core": { + "version": "1.9.1", + "resolved": "https://registry.npmmirror.com/@emnapi/core/-/core-1.9.1.tgz", + "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.1", + "resolved": "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.9.1.tgz", + "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", + "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.0", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", @@ -952,6 +986,287 @@ "node": ">=12" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmmirror.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, + "node_modules/@node-rs/crc32": { + "version": "1.10.6", + "resolved": "https://registry.npmmirror.com/@node-rs/crc32/-/crc32-1.10.6.tgz", + "integrity": "sha512-+llXfqt+UzgoDzT9of5vPQPGqTAVCohU74I9zIBkNo5TH6s2P31DFJOGsJQKN207f0GHnYv5pV3wh3BCY/un/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@node-rs/crc32-android-arm-eabi": "1.10.6", + "@node-rs/crc32-android-arm64": "1.10.6", + "@node-rs/crc32-darwin-arm64": "1.10.6", + "@node-rs/crc32-darwin-x64": "1.10.6", + "@node-rs/crc32-freebsd-x64": "1.10.6", + "@node-rs/crc32-linux-arm-gnueabihf": "1.10.6", + "@node-rs/crc32-linux-arm64-gnu": "1.10.6", + "@node-rs/crc32-linux-arm64-musl": "1.10.6", + "@node-rs/crc32-linux-x64-gnu": "1.10.6", + "@node-rs/crc32-linux-x64-musl": "1.10.6", + "@node-rs/crc32-wasm32-wasi": "1.10.6", + "@node-rs/crc32-win32-arm64-msvc": "1.10.6", + "@node-rs/crc32-win32-ia32-msvc": "1.10.6", + "@node-rs/crc32-win32-x64-msvc": "1.10.6" + } + }, + "node_modules/@node-rs/crc32-android-arm-eabi": { + "version": "1.10.6", + "resolved": "https://registry.npmmirror.com/@node-rs/crc32-android-arm-eabi/-/crc32-android-arm-eabi-1.10.6.tgz", + "integrity": "sha512-vZAMuJXm3TpWPOkkhxdrofWDv+Q+I2oO7ucLRbXyAPmXFNDhHtBxbO1rk9Qzz+M3eep8ieS4/+jCL1Q0zacNMQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-android-arm64": { + "version": "1.10.6", + "resolved": "https://registry.npmmirror.com/@node-rs/crc32-android-arm64/-/crc32-android-arm64-1.10.6.tgz", + "integrity": "sha512-Vl/JbjCinCw/H9gEpZveWCMjxjcEChDcDBM8S4hKay5yyoRCUHJPuKr4sjVDBeOm+1nwU3oOm6Ca8dyblwp4/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-darwin-arm64": { + "version": "1.10.6", + "resolved": "https://registry.npmmirror.com/@node-rs/crc32-darwin-arm64/-/crc32-darwin-arm64-1.10.6.tgz", + "integrity": "sha512-kARYANp5GnmsQiViA5Qu74weYQ3phOHSYQf0G+U5wB3NB5JmBHnZcOc46Ig21tTypWtdv7u63TaltJQE41noyg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-darwin-x64": { + "version": "1.10.6", + "resolved": "https://registry.npmmirror.com/@node-rs/crc32-darwin-x64/-/crc32-darwin-x64-1.10.6.tgz", + "integrity": "sha512-Q99bevJVMfLTISpkpKBlXgtPUItrvTWKFyiqoKH5IvscZmLV++NH4V13Pa17GTBmv9n18OwzgQY4/SRq6PQNVA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-freebsd-x64": { + "version": "1.10.6", + "resolved": "https://registry.npmmirror.com/@node-rs/crc32-freebsd-x64/-/crc32-freebsd-x64-1.10.6.tgz", + "integrity": "sha512-66hpawbNjrgnS9EDMErta/lpaqOMrL6a6ee+nlI2viduVOmRZWm9Rg9XdGTK/+c4bQLdtC6jOd+Kp4EyGRYkAg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-linux-arm-gnueabihf": { + "version": "1.10.6", + "resolved": "https://registry.npmmirror.com/@node-rs/crc32-linux-arm-gnueabihf/-/crc32-linux-arm-gnueabihf-1.10.6.tgz", + "integrity": "sha512-E8Z0WChH7X6ankbVm8J/Yym19Cq3otx6l4NFPS6JW/cWdjv7iw+Sps2huSug+TBprjbcEA+s4TvEwfDI1KScjg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-linux-arm64-gnu": { + "version": "1.10.6", + "resolved": "https://registry.npmmirror.com/@node-rs/crc32-linux-arm64-gnu/-/crc32-linux-arm64-gnu-1.10.6.tgz", + "integrity": "sha512-LmWcfDbqAvypX0bQjQVPmQGazh4dLiVklkgHxpV4P0TcQ1DT86H/SWpMBMs/ncF8DGuCQ05cNyMv1iddUDugoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-linux-arm64-musl": { + "version": "1.10.6", + "resolved": "https://registry.npmmirror.com/@node-rs/crc32-linux-arm64-musl/-/crc32-linux-arm64-musl-1.10.6.tgz", + "integrity": "sha512-k8ra/bmg0hwRrIEE8JL1p32WfaN9gDlUUpQRWsbxd1WhjqvXea7kKO6K4DwVxyxlPhBS9Gkb5Urq7Y4mXANzaw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-linux-x64-gnu": { + "version": "1.10.6", + "resolved": "https://registry.npmmirror.com/@node-rs/crc32-linux-x64-gnu/-/crc32-linux-x64-gnu-1.10.6.tgz", + "integrity": "sha512-IfjtqcuFK7JrSZ9mlAFhb83xgium30PguvRjIMI45C3FJwu18bnLk1oR619IYb/zetQT82MObgmqfKOtgemEKw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-linux-x64-musl": { + "version": "1.10.6", + "resolved": "https://registry.npmmirror.com/@node-rs/crc32-linux-x64-musl/-/crc32-linux-x64-musl-1.10.6.tgz", + "integrity": "sha512-LbFYsA5M9pNunOweSt6uhxenYQF94v3bHDAQRPTQ3rnjn+mK6IC7YTAYoBjvoJP8lVzcvk9hRj8wp4Jyh6Y80g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-wasm32-wasi": { + "version": "1.10.6", + "resolved": "https://registry.npmmirror.com/@node-rs/crc32-wasm32-wasi/-/crc32-wasm32-wasi-1.10.6.tgz", + "integrity": "sha512-KaejdLgHMPsRaxnM+OG9L9XdWL2TabNx80HLdsCOoX9BVhEkfh39OeahBo8lBmidylKbLGMQoGfIKDjq0YMStw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.5" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@node-rs/crc32-win32-arm64-msvc": { + "version": "1.10.6", + "resolved": "https://registry.npmmirror.com/@node-rs/crc32-win32-arm64-msvc/-/crc32-win32-arm64-msvc-1.10.6.tgz", + "integrity": "sha512-x50AXiSxn5Ccn+dCjLf1T7ZpdBiV1Sp5aC+H2ijhJO4alwznvXgWbopPRVhbp2nj0i+Gb6kkDUEyU+508KAdGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-win32-ia32-msvc": { + "version": "1.10.6", + "resolved": "https://registry.npmmirror.com/@node-rs/crc32-win32-ia32-msvc/-/crc32-win32-ia32-msvc-1.10.6.tgz", + "integrity": "sha512-DpDxQLaErJF9l36aghe1Mx+cOnYLKYo6qVPqPL9ukJ5rAGLtCdU0C+Zoi3gs9ySm8zmbFgazq/LvmsZYU42aBw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/crc32-win32-x64-msvc": { + "version": "1.10.6", + "resolved": "https://registry.npmmirror.com/@node-rs/crc32-win32-x64-msvc/-/crc32-win32-x64-msvc-1.10.6.tgz", + "integrity": "sha512-5B1vXosIIBw1m2Rcnw62IIfH7W9s9f7H7Ma0rRuhT8HR4Xh8QCgw6NJSI2S2MCngsGktYnAhyUvs81b7efTyQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1375,6 +1690,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmmirror.com/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -1625,17 +1951,17 @@ } }, "node_modules/@vscode/vsce": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.6.0.tgz", - "integrity": "sha512-u2ZoMfymRNJb14aHNawnXJtXHLXDVKc1oKZaH4VELKT/9iWKRVgtQOdwxCgtwSxJoqYvuK4hGlBWQJ05wxADhg==", + "version": "3.7.1", + "resolved": "https://registry.npmmirror.com/@vscode/vsce/-/vsce-3.7.1.tgz", + "integrity": "sha512-OTm2XdMt2YkpSn2Nx7z2EJtSuhRHsTPYsSK59hr3v8jRArK+2UEoju4Jumn1CmpgoBLGI6ReHLJ/czYltNUW3g==", "dev": true, "license": "MIT", "dependencies": { "@azure/identity": "^4.1.0", - "@secretlint/node": "^10.1.1", - "@secretlint/secretlint-formatter-sarif": "^10.1.1", - "@secretlint/secretlint-rule-no-dotenv": "^10.1.1", - "@secretlint/secretlint-rule-preset-recommend": "^10.1.1", + "@secretlint/node": "^10.1.2", + "@secretlint/secretlint-formatter-sarif": "^10.1.2", + "@secretlint/secretlint-rule-no-dotenv": "^10.1.2", + "@secretlint/secretlint-rule-preset-recommend": "^10.1.2", "@vscode/vsce-sign": "^2.0.0", "azure-devops-node-api": "^12.5.0", "chalk": "^4.1.2", @@ -1652,7 +1978,7 @@ "minimatch": "^3.0.3", "parse-semver": "^1.1.1", "read": "^1.0.7", - "secretlint": "^10.1.1", + "secretlint": "^10.1.2", "semver": "^7.5.2", "tmp": "^0.2.3", "typed-rest-client": "^1.8.4", @@ -2982,6 +3308,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/define-lazy-prop": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", @@ -2995,6 +3339,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/delaunator": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", @@ -3936,6 +4298,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/globby": { "version": "14.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", @@ -4004,6 +4383,19 @@ "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -4314,6 +4706,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-it-type": { + "version": "5.1.3", + "resolved": "https://registry.npmmirror.com/is-it-type/-/is-it-type-5.1.3.tgz", + "integrity": "sha512-AX2uU0HW+TxagTgQXOJY7+2fbFHemC7YFBwN1XqD8qQMKdtfbOC8OC3fUb4s5NU59a3662Dzwto8tWDdZYRXxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "globalthis": "^1.0.2" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -5065,6 +5470,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -5192,23 +5607,23 @@ } }, "node_modules/ovsx": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/ovsx/-/ovsx-0.10.1.tgz", - "integrity": "sha512-8i7+MJMMeq73m1zPEIClSFe17SNuuzU5br7G77ZIfOC24elB4pGQs0N1qRd+gnnbyhL5Qu96G21nFOVOBa2OBg==", + "version": "0.10.10", + "resolved": "https://registry.npmmirror.com/ovsx/-/ovsx-0.10.10.tgz", + "integrity": "sha512-/X5J4VLKPUGGaMynW9hgvsGg9jmwsK/3RhODeA2yzdeDbb8PUSNcg5GQ9aPDJW/znlqNvAwQcXAyE+Cq0RRvAQ==", "dev": true, "license": "EPL-2.0", "dependencies": { - "@vscode/vsce": "^3.2.1", + "@vscode/vsce": "^3.7.1", "commander": "^6.2.1", "follow-redirects": "^1.14.6", "is-ci": "^2.0.0", "leven": "^3.1.0", "semver": "^7.6.0", "tmp": "^0.2.3", - "yauzl": "^3.1.3" + "yauzl-promise": "^4.0.0" }, "bin": { - "ovsx": "lib/ovsx" + "ovsx": "bin/ovsx" }, "engines": { "node": ">= 20" @@ -5224,20 +5639,6 @@ "node": ">= 6" } }, - "node_modules/ovsx/node_modules/yauzl": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.2.0.tgz", - "integrity": "sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-crc32": "~0.2.3", - "pend": "~1.2.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -6074,6 +6475,16 @@ "simple-concat": "^1.0.0" } }, + "node_modules/simple-invariant": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/simple-invariant/-/simple-invariant-2.0.1.tgz", + "integrity": "sha512-1sbhsxqI+I2tqlmjbz99GXNmZtr6tKIyEgGGnJw/MKGblalqk/XoOYYFJlBzTKZCxx8kLaD3FD5s9BEEjx5Pyg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/slash": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", @@ -7138,6 +7549,21 @@ "fd-slicer": "~1.1.0" } }, + "node_modules/yauzl-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/yauzl-promise/-/yauzl-promise-4.0.0.tgz", + "integrity": "sha512-/HCXpyHXJQQHvFq9noqrjfa/WpQC2XYs3vI7tBiAi4QiIU1knvYhZGaO1QPjwIVMdqflxbmwgMXtYeaRiAE0CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@node-rs/crc32": "^1.7.0", + "is-it-type": "^5.1.2", + "simple-invariant": "^2.0.1" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/yazl": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", diff --git a/src/tools/rust-analyzer/editors/code/package.json b/src/tools/rust-analyzer/editors/code/package.json index 484c2700aeb73..ec66d29626ddf 100644 --- a/src/tools/rust-analyzer/editors/code/package.json +++ b/src/tools/rust-analyzer/editors/code/package.json @@ -64,12 +64,12 @@ "@typescript-eslint/eslint-plugin": "^8.25.0", "@typescript-eslint/parser": "^8.25.0", "@vscode/test-electron": "^2.4.1", - "@vscode/vsce": "^3.6.0", + "@vscode/vsce": "^3.7.1", "esbuild": "^0.25.0", "eslint": "^9.21.0", "eslint-config-prettier": "^10.0.2", "eslint-define-config": "^2.1.0", - "ovsx": "0.10.1", + "ovsx": "0.10.10", "prettier": "^3.5.2", "tslib": "^2.8.1", "typescript": "^5.7.3", From 57ef70fb9a36dc03ef47d550c431d9661f2b8daf Mon Sep 17 00:00:00 2001 From: Wilfred Hughes Date: Thu, 12 Mar 2026 19:31:50 +0000 Subject: [PATCH 033/144] internal: Document when crate cycles can occur This is legal when there are dev-dependencies, and rust-analyzer itself even does this. This causes spurious warnings in several cases, such as generating SCIP for rust-analyzer: ``` $ cargo run --bin rust-analyzer --release -- scip . 2026-03-12T18:40:33.824092Z WARN cyclic deps: cfg(Idx::(21)) -> cfg(Idx::(21)), alternative path: cfg(Idx::(21)) ``` In this case, the `cfg` crate enables its `tt` feature by depending on itself in dev-dependencies. --- .../rust-analyzer/crates/base-db/src/input.rs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/tools/rust-analyzer/crates/base-db/src/input.rs b/src/tools/rust-analyzer/crates/base-db/src/input.rs index 246c57edc2df3..4f32abafd77d1 100644 --- a/src/tools/rust-analyzer/crates/base-db/src/input.rs +++ b/src/tools/rust-analyzer/crates/base-db/src/input.rs @@ -929,6 +929,27 @@ impl<'a> IntoIterator for &'a Env { } } +/// The crate graph had a cycle. This is typically a bug, and +/// rust-analyzer logs a warning when it encounters a cycle. Generally +/// rust-analyzer will continue working OK in the presence of cycle, +/// but it's better to have an accurate crate graph. +/// +/// ## dev-dependencies +/// +/// Note that it's actually legal for a cargo package (i.e. a thing +/// with a Cargo.toml) to depend on itself in dev-dependencies. This +/// can enable additional features, and is typically used when a +/// project wants features to be enabled in tests. Dev-dependencies +/// are not propagated, so they aren't visible to package that depend +/// on this one. +/// +/// +/// +/// However, rust-analyzer constructs its crate graph from Cargo +/// metadata, so it can end up producing a cyclic crate graph from a +/// well-formed package graph. +/// +/// #[derive(Debug)] pub struct CyclicDependenciesError { path: Vec<(CrateBuilderId, Option)>, From 3afed9e0087f3f392a211a9768e2332752942b07 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Tue, 31 Mar 2026 21:49:38 +0800 Subject: [PATCH 034/144] feat: support labeled block for convert_to_guarded_return Example --- ```rust fn main() { 'l: { if$0 let Some(n) = n { foo(n); bar(); } } } ``` **Before this PR** Assist not applicable **After this PR** ```rust fn main() { 'l: { let Some(n) = n else { break 'l }; foo(n); bar(); } } ``` --- .../src/handlers/convert_to_guarded_return.rs | 45 +++++++++++++++++-- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs index e59527b0e0951..f8c4fcc5feaac 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs @@ -101,8 +101,8 @@ fn if_expr_to_guarded_return( return None; } - let parent_container = parent_block.syntax().parent()?; - let else_block = ElseBlock::new(&ctx.sema, else_block, &parent_container)?; + let container = container_of(&parent_block)?; + let else_block = ElseBlock::new(&ctx.sema, else_block, &container)?; if parent_block.tail_expr() != Some(if_expr.clone().into()) && !(else_block.is_never_block @@ -201,8 +201,8 @@ fn let_stmt_to_guarded_return( let target = let_stmt.syntax().text_range(); let parent_block = let_stmt.syntax().parent()?.ancestors().find_map(ast::BlockExpr::cast)?; - let parent_container = parent_block.syntax().parent()?; - let else_block = ElseBlock::new(&ctx.sema, None, &parent_container)?; + let container = container_of(&parent_block)?; + let else_block = ElseBlock::new(&ctx.sema, None, &container)?; acc.add( AssistId::refactor_rewrite("convert_to_guarded_return"), @@ -230,6 +230,13 @@ fn let_stmt_to_guarded_return( ) } +fn container_of(block: &ast::BlockExpr) -> Option { + if block.label().is_some() { + return Some(block.syntax().clone()); + } + block.syntax().parent() +} + struct ElseBlock<'db> { exist_else_block: Option, is_never_block: bool, @@ -297,6 +304,7 @@ impl<'db> ElseBlock<'db> { enum EarlyKind<'db> { Continue, + Break(ast::Lifetime, hir::Type<'db>), Return(hir::Type<'db>), } @@ -309,6 +317,7 @@ impl<'db> EarlyKind<'db> { match parent_container { ast::Fn(it) => Some(Self::Return(sema.to_def(&it)?.ret_type(sema.db))), ast::ClosureExpr(it) => Some(Self::Return(sema.type_of_expr(&it.body()?)?.original)), + ast::BlockExpr(it) => Some(Self::Break(it.label()?.lifetime()?, sema.type_of_expr(&it.into())?.original)), ast::WhileExpr(_) => Some(Self::Continue), ast::LoopExpr(_) => Some(Self::Continue), ast::ForExpr(_) => Some(Self::Continue), @@ -325,6 +334,7 @@ impl<'db> EarlyKind<'db> { ) -> ast::Expr { match self { EarlyKind::Continue => make.expr_continue(None).into(), + EarlyKind::Break(label, _) => make.expr_break(Some(label.clone()), ret).into(), EarlyKind::Return(ty) => { let expr = match TryEnum::from_ty(sema, ty) { Some(TryEnum::Option) => { @@ -340,6 +350,7 @@ impl<'db> EarlyKind<'db> { fn is_unit(&self) -> bool { match self { EarlyKind::Continue => true, + EarlyKind::Break(_, ty) => ty.is_unit(), EarlyKind::Return(ty) => ty.is_unit(), } } @@ -1067,6 +1078,32 @@ fn main() { ); } + #[test] + fn convert_let_inside_labeled_block() { + check_assist( + convert_to_guarded_return, + r#" +fn main() { + 'l: { + if$0 let Some(n) = n { + foo(n); + bar(); + } + } +} +"#, + r#" +fn main() { + 'l: { + let Some(n) = n else { break 'l }; + foo(n); + bar(); + } +} +"#, + ); + } + #[test] fn convert_let_inside_for_with_else() { check_assist( From 2dea90f91ee265ed0d923cdb834fa9abb09bc3e4 Mon Sep 17 00:00:00 2001 From: Wilfred Hughes Date: Tue, 31 Mar 2026 18:19:26 +0100 Subject: [PATCH 035/144] fix: Set VS Code extension kind explicitly The VS Code extension needs to be a `workspace` extension, because it relies on access to the workspace. The rust-analyzer binary needs to run on the same machine as the checkout of the code it's working on. https://code.visualstudio.com/api/advanced-topics/remote-extensions#architecture-and-extension-kinds https://code.visualstudio.com/api/advanced-topics/extension-host#preferred-extension-location If an extension doesn't set extensionKind, VS Code will try to deduce the kind based on the presence of various fields in the package.json, such as a `main`. https://github.com/microsoft/vscode/blob/fc23f2d26631c6a2c4bf9f69506ea74c90a32804/src/vs/workbench/services/extensions/common/extensionManifestPropertiesService.ts#L222 Instead, mark the extension kind as explicitly `workspace`. This is more explicit and prevents future changes to package.json accidentally making it run in the wrong environment. It's also helpful when debugging startup bugs. --- src/tools/rust-analyzer/editors/code/package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tools/rust-analyzer/editors/code/package.json b/src/tools/rust-analyzer/editors/code/package.json index ec66d29626ddf..a117033f8025d 100644 --- a/src/tools/rust-analyzer/editors/code/package.json +++ b/src/tools/rust-analyzer/editors/code/package.json @@ -31,6 +31,9 @@ "vscode": "^1.93.0" }, "enabledApiProposals": [], + "extensionKind": [ + "workspace" + ], "scripts": { "vscode:prepublish": "npm run build-base -- --minify", "package": "vsce package -o rust-analyzer.vsix", From f28056fd009f3566af8d71aeb5973e5c22c129da Mon Sep 17 00:00:00 2001 From: Wilfred Hughes Date: Thu, 19 Feb 2026 14:17:31 +0000 Subject: [PATCH 036/144] fix: Use the correct project root when there are multiple workspaces Previously, Config::root_path() would always return the LSP rootUri of the first workspace folder. This can cause issues when the user has multiple workspaces open in their editor, especially if the first one in the list isn't a Rust project. This was noted as an issue in rust-lang/rust-analyzer#21483, and added comments suggesting that we should deprecate root_path(). This change splits root_path() into a `workspace_root_for()` function that handles the multiple workspace case correctly, and a `default_root_path()` fallback. This is particularly useful when the user has configured project-relative paths to e.g. their discover command or rustfmt, but it's the correct behaviour in general. AI disclosure: First draft was written with Claude Opus. --- .../crates/rust-analyzer/src/config.rs | 30 +++++++++++++++---- .../rust-analyzer/src/handlers/request.rs | 9 +++++- .../crates/rust-analyzer/src/main_loop.rs | 11 +++++-- .../crates/rust-analyzer/src/reload.rs | 4 +-- 4 files changed, 44 insertions(+), 10 deletions(-) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs index 1bc164b157a7c..90857a3073109 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs @@ -1070,6 +1070,7 @@ struct ClientInfo { version: Option, } +/// The configuration of this rust-analyzer instance. #[derive(Clone)] pub struct Config { /// Projects that have a Cargo.toml or a rust-project.json in a @@ -1079,11 +1080,16 @@ pub struct Config { /// Projects whose configuration was generated by a command /// configured in discoverConfig. discovered_projects_from_command: Vec, - /// The workspace roots as registered by the LSP client + /// The workspace roots as registered by the LSP client. workspace_roots: Vec, caps: ClientCapabilities, - /// The LSP root path, deprecated in favor of `workspace_roots` + + /// The root of the first project encountered. This is deprecated + /// because rust-analyzer might be handling multiple projects. + /// + /// Prefer `workspace_roots` and `workspace_root_for()`. root_path: AbsPathBuf, + snippets: Vec, client_info: Option, @@ -1787,9 +1793,23 @@ impl Config { s } - pub fn root_path(&self) -> &AbsPathBuf { - // We should probably use `workspace_roots` here if set - &self.root_path + /// Find the workspace root that contains the given path, using the + /// longest prefix match. + pub fn workspace_root_for(&self, path: &AbsPath) -> &AbsPathBuf { + self.workspace_roots + .iter() + .filter(|root| path.starts_with(root.as_path())) + .max_by_key(|root| root.as_str().len()) + .unwrap_or(self.default_root_path()) + } + + /// Best-effort root path for the current project. + /// + /// Use `workspace_root_for` where possible, because + /// `default_root_path` may return the wrong path when a user has + /// multiple workspaces. + pub fn default_root_path(&self) -> &AbsPathBuf { + self.workspace_roots.first().unwrap_or(&self.root_path) } pub fn caps(&self) -> &ClientCapabilities { diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs index c24591b7ab753..9c2e0a5f321b4 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs @@ -2453,7 +2453,14 @@ fn run_rustfmt( let cmd_path = if command.contains(std::path::MAIN_SEPARATOR) || (cfg!(windows) && command.contains('/')) { - snap.config.root_path().join(cmd).into() + let project_root = Utf8PathBuf::from_path_buf(current_dir.clone()) + .ok() + .and_then(|p| AbsPathBuf::try_from(p).ok()); + let project_root = project_root + .as_ref() + .map(|dir| snap.config.workspace_root_for(dir)) + .unwrap_or(snap.config.default_root_path()); + project_root.join(cmd).into() } else { cmd }; diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs index 7c494de6f73d0..a8c3d062d041c 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs @@ -830,12 +830,19 @@ impl GlobalState { let command = cfg.command.clone(); let discover = DiscoverCommand::new(self.discover_sender.clone(), command); + let discover_path = match &arg { + DiscoverProjectParam::Buildfile(it) => it, + DiscoverProjectParam::Path(it) => it, + }; + let current_dir = + self.config.workspace_root_for(discover_path.as_path()).clone(); + let arg = match arg { DiscoverProjectParam::Buildfile(it) => DiscoverArgument::Buildfile(it), DiscoverProjectParam::Path(it) => DiscoverArgument::Path(it), }; - match discover.spawn(arg, self.config.root_path().as_ref()) { + match discover.spawn(arg, current_dir.as_ref()) { Ok(handle) => { if self.discover_jobs_active == 0 { let title = &cfg.progress_label.clone(); @@ -953,7 +960,7 @@ impl GlobalState { if let Some(dir) = dir { message += &format!( ": {}", - match dir.strip_prefix(self.config.root_path()) { + match dir.strip_prefix(self.config.workspace_root_for(&dir)) { Some(relative_path) => relative_path.as_utf8_path(), None => dir.as_ref(), } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs index 83f4a19b39fad..71accbed4ef16 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs @@ -390,7 +390,7 @@ impl GlobalState { info!(%cause, "will fetch build data"); let workspaces = Arc::clone(&self.workspaces); let config = self.config.cargo(None); - let root_path = self.config.root_path().clone(); + let root_path = self.config.default_root_path().clone(); self.task_pool.handle.spawn_with_sender(ThreadIntent::Worker, move |sender| { sender.send(Task::FetchBuildData(BuildDataProgress::Begin)).unwrap(); @@ -883,7 +883,7 @@ impl GlobalState { config, crate::flycheck::FlycheckConfigJson::default(), None, - self.config.root_path().clone(), + self.config.default_root_path().clone(), None, None, )] From c9878352c8b6b8bd284d798efbfc07ab0b12fcf9 Mon Sep 17 00:00:00 2001 From: Wilfred Hughes Date: Thu, 19 Mar 2026 13:24:56 +0000 Subject: [PATCH 037/144] internal: Split absolute path collection from reading files This is not a logical change, and just makes the next commit simpler. It also shouldn't impact performance, because the vast majority of events have a single path. --- src/tools/rust-analyzer/crates/vfs-notify/src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/vfs-notify/src/lib.rs b/src/tools/rust-analyzer/crates/vfs-notify/src/lib.rs index 428b19c50b9d6..f91d830ca0dbf 100644 --- a/src/tools/rust-analyzer/crates/vfs-notify/src/lib.rs +++ b/src/tools/rust-analyzer/crates/vfs-notify/src/lib.rs @@ -198,7 +198,7 @@ impl NotifyActor { && let EventKind::Create(_) | EventKind::Modify(_) | EventKind::Remove(_) = event.kind { - let files = event + let abs_paths: Vec = event .paths .into_iter() .filter_map(|path| { @@ -207,6 +207,10 @@ impl NotifyActor { .expect("path is absolute"), ) }) + .collect(); + + let files = abs_paths + .into_iter() .filter_map(|path| -> Option<(AbsPathBuf, Option>)> { // Ignore events for files/directories that we're not watching. if !(self.watched_file_entries.contains(&path) From 76e7fb9dd51392c9edfa6fc9e5dc76a2d85cf488 Mon Sep 17 00:00:00 2001 From: Wilfred Hughes Date: Thu, 19 Mar 2026 13:27:25 +0000 Subject: [PATCH 038/144] fix: Support filesystems that don't send Create events On some filesystems, particularly FUSE on Linux, we don't get Create(...) events. We do get Access(Open(Any)) events, so handle those consistently with create/modify/remove events. This fixes missed file notifications when using Sapling SCM with EdenFS, although I believe the problem can occur on other FUSE environments. Reproduction: Commit a change with Sapling that adds a new file foo.rs and references it with `mod foo;` in lib.rs. Configure rust-analyzer as follows: ``` { "rust-analyzer.files.watcher": "server", "rust-analyzer.server.extraEnv": { "RA_LOG": "vfs_notify=debug" }, } ``` Go to the previous commit, restart rust-analyzer, then go to the next commit. The logs only show: ``` 2026-03-18T07:16:54.211788903-07:00 DEBUG vfs-notify event event=NotifyEvent(Ok(Event { kind: Access(Open(Any)), paths: ["/data/users/wilfred/scratch/src/foo.rs"], attr:tracker: None, attr:flag: None, attr:info: None, attr:source: None })) 2026-03-18T07:16:54.211906733-07:00 DEBUG vfs-notify event event=NotifyEvent(Ok(Event { kind: Access(Open(Any)), paths: ["/data/users/wilfred/scratch/src/foo.rs"], attr:tracker: None, attr:flag: None, attr:info: None, attr:source: None })) 2026-03-18T07:16:54.216467168-07:00 DEBUG vfs-notify event event=NotifyEvent(Ok(Event { kind: Access(Open(Any)), paths: ["/data/users/wilfred/scratch/src/lib.rs"], attr:tracker: None, attr:flag: None, attr:info: None, attr:source: None })) 2026-03-18T07:16:54.216811304-07:00 DEBUG vfs-notify event event=NotifyEvent(Ok(Event { kind: Access(Open(Any)), paths: ["/data/users/wilfred/scratch/src/lib.rs"], attr:tracker: None, attr:flag: None, attr:info: None, attr:source: None })) ``` Observe that `mod foo;` has a red squiggle and shows "unresolved module, can't find module file: foo.rs, or foo/mod.rs". This commit fixes that. --- .../crates/vfs-notify/src/lib.rs | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/tools/rust-analyzer/crates/vfs-notify/src/lib.rs b/src/tools/rust-analyzer/crates/vfs-notify/src/lib.rs index f91d830ca0dbf..6465a85d2d29b 100644 --- a/src/tools/rust-analyzer/crates/vfs-notify/src/lib.rs +++ b/src/tools/rust-analyzer/crates/vfs-notify/src/lib.rs @@ -14,7 +14,7 @@ use std::{ }; use crossbeam_channel::{Receiver, Sender, select, unbounded}; -use notify::{Config, EventKind, RecommendedWatcher, RecursiveMode, Watcher}; +use notify::{Config, EventKind, RecommendedWatcher, RecursiveMode, Watcher, event::AccessKind}; use paths::{AbsPath, AbsPathBuf, Utf8PathBuf}; use rayon::iter::{IndexedParallelIterator as _, IntoParallelIterator as _, ParallelIterator}; use rustc_hash::FxHashSet; @@ -63,6 +63,7 @@ struct NotifyActor { sender: loader::Sender, watched_file_entries: FxHashSet, watched_dir_entries: Vec, + seen_paths: FxHashSet, // Drop order is significant. watcher: Option<(RecommendedWatcher, Receiver)>, } @@ -79,6 +80,7 @@ impl NotifyActor { sender, watched_dir_entries: Vec::new(), watched_file_entries: FxHashSet::default(), + seen_paths: FxHashSet::default(), watcher: None, } } @@ -120,6 +122,7 @@ impl NotifyActor { let n_total = config.load.len(); self.watched_dir_entries.clear(); self.watched_file_entries.clear(); + self.seen_paths.clear(); self.send(loader::Message::Progress { n_total, @@ -195,8 +198,10 @@ impl NotifyActor { }, Event::NotifyEvent(event) => { if let Some(event) = log_notify_error(event) - && let EventKind::Create(_) | EventKind::Modify(_) | EventKind::Remove(_) = - event.kind + && let EventKind::Create(_) + | EventKind::Modify(_) + | EventKind::Remove(_) + | EventKind::Access(AccessKind::Open(_)) = event.kind { let abs_paths: Vec = event .paths @@ -209,6 +214,24 @@ impl NotifyActor { }) .collect(); + let mut saw_new_file = false; + for abs_path in &abs_paths { + if self.seen_paths.insert(abs_path.clone()) { + saw_new_file = true; + } + } + + // Only consider access events for files that we haven't seen + // before. + // + // This is important on FUSE filesystems, where we may not get a + // Create event. In other cases we're about to access the file, so + // we don't want an infinite loop where processing an Access event + // creates another Access event. + if matches!(event.kind, EventKind::Access(_)) && !saw_new_file { + continue; + } + let files = abs_paths .into_iter() .filter_map(|path| -> Option<(AbsPathBuf, Option>)> { From f3dcff63f48f59e79577916d3223a1238cc60468 Mon Sep 17 00:00:00 2001 From: erfanio Date: Tue, 31 Mar 2026 22:28:07 +1100 Subject: [PATCH 039/144] Update neovim LSP instructions for neovim 0.11+ --- .../docs/book/src/other_editors.md | 69 ++++++++++--------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/src/tools/rust-analyzer/docs/book/src/other_editors.md b/src/tools/rust-analyzer/docs/book/src/other_editors.md index f7116fc19a6c2..1cb2a44063b25 100644 --- a/src/tools/rust-analyzer/docs/book/src/other_editors.md +++ b/src/tools/rust-analyzer/docs/book/src/other_editors.md @@ -137,24 +137,22 @@ To use the LSP server in [ale](https://github.com/dense-analysis/ale): ### nvim-lsp -Neovim 0.5 has built-in language server support. For a quick start -configuration of rust-analyzer, use -[neovim/nvim-lspconfig](https://github.com/neovim/nvim-lspconfig#rust_analyzer). -Once `neovim/nvim-lspconfig` is installed, use -`lua require'lspconfig'.rust_analyzer.setup({})` in your `init.vim`. - -You can also pass LSP settings to the server: +Neovim 0.5+ added build-in support for language server with most of the heavy +lifting happening in "framework" plugins such as +[neovim/nvim-lspconfig](https://github.com/neovim/nvim-lspconfig). +Since v0.11+ Neovim has full featured LSP support. nvim-lspconfig is +still recommended to get the +[rust-analyzer config](https://github.com/neovim/nvim-lspconfig/blob/master/lsp/rust_analyzer.lua) +for free. + +1. Install [neovim/nvim-lspconfig](https://github.com/neovim/nvim-lspconfig) +2. Add `lua vim.lsp.enable('rust-analyzer')` to your `init.vim` +3. Customize your setup. ```lua lua << EOF -local lspconfig = require'lspconfig' - -local on_attach = function(client) - require'completion'.on_attach(client) -end - -lspconfig.rust_analyzer.setup({ - on_attach = on_attach, +-- You can pass LSP settings to the server: +vim.lsp.config("rust_analyzer", { settings = { ["rust-analyzer"] = { imports = { @@ -171,30 +169,35 @@ lspconfig.rust_analyzer.setup({ procMacro = { enable = true }, - } - } + }, + }, }) -EOF -``` - -If you're running Neovim 0.10 or later, you can enable inlay hints via `on_attach`: -```lua -lspconfig.rust_analyzer.setup({ - on_attach = function(client, bufnr) - vim.lsp.inlay_hint.enable(true, { bufnr = bufnr }) - end +-- You can enable different LSP features +vim.api.nvim_create_autocmd("LspAttach", { + callback = function(ev) + local client = assert(vim.lsp.get_client_by_id(ev.data.client_id)) + -- Inlay hints display inferred types, etc. + if client:supports_method("inlayHint/resolve") then + vim.lsp.inlay_hint.enable(true, { bufnr = ev.buf }) + end + -- Completion can be invoked via ctrl+x ctrl+o. It displays a list of + -- names inferred from the context (e.g. method names, variables, etc.) + if client:supports_method("textDocument/completion") then + vim.lsp.completion.enable(true, client.id, ev.buf, {}) + end + end, }) +EOF ``` -Note that the hints are only visible after `rust-analyzer` has finished loading **and** you have to -edit the file to trigger a re-render. - -See for more tips on -getting started. +Note that the hints are only visible after `rust-analyzer` has finished loading +**and** you have to edit the file to trigger a re-render. -Check out for a batteries -included rust-analyzer setup for Neovim. +The instructions here use the 0.11+ API, if you're running an older version, you +can follow this guide or check +out for a batteries included +rust-analyzer setup for Neovim. ### vim-lsp From 76d4fb28aef6d0a2bd09087ae380bbfd8a1d44f5 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Wed, 1 Apr 2026 14:36:34 +0800 Subject: [PATCH 040/144] fix: Not suggest name in nested type in variant MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Example --- ```rust struct Other; struct Vec(T); enum Foo { Vec(Vec<$0>) } ``` **Before this PR** ```text st Vec<…> Vec<{unknown}> [name] en Foo Foo [] st Other Other [] sp Self Foo [] ``` **After this PR** ```text en Foo Foo [] st Other Other [] sp Self Foo [] st Vec<…> Vec<{unknown}> [] ``` --- .../ide-completion/src/context/analysis.rs | 5 +++++ .../crates/ide-completion/src/render.rs | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs index 762e60d676e68..294e70dd56a29 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs @@ -828,10 +828,15 @@ fn expected_type_and_name<'db>( .unwrap_or((None, None)) }, ast::Variant(it) => { + let is_simple_field = |field: ast::TupleField| { + let Some(ty) = field.ty() else { return true }; + matches!(ty, ast::Type::PathType(_)) && ty.generic_arg_list().is_none() + }; let is_simple_variant = matches!( it.field_list(), Some(ast::FieldList::TupleFieldList(list)) if list.syntax().children_with_tokens().all(|it| it.kind() != T![,]) + && list.fields().next().is_none_or(is_simple_field) ); (None, it.name().filter(|_| is_simple_variant).map(NameOrNameRef::Name)) }, diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render.rs index 89e15a0cd1b5a..4751ee36eceb0 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/render.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/render.rs @@ -3077,6 +3077,22 @@ enum Foo { st String String [] "#]], ); + + check_relevance( + r#" +struct Other; +struct Vec(T); +enum Foo { + Vec(Vec<$0>) +} + "#, + expect![[r#" + en Foo Foo [] + st Other Other [] + sp Self Foo [] + st Vec<…> Vec<{unknown}> [] + "#]], + ); } #[test] From a73252f086e39e2e51adc6b25ab711b1be0d9fd9 Mon Sep 17 00:00:00 2001 From: so1ve Date: Wed, 1 Apr 2026 17:02:28 +0800 Subject: [PATCH 041/144] feat: support macro expansion in `#[doc = ...]` attributes --- .../rust-analyzer/crates/hir-def/src/attrs.rs | 379 +++++++++++++++--- .../crates/ide/src/hover/tests.rs | 172 ++++++++ 2 files changed, 493 insertions(+), 58 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs b/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs index e3e1aac7090ae..f91c82d729f84 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs @@ -29,8 +29,10 @@ use base_db::Crate; use cfg::{CfgExpr, CfgOptions}; use either::Either; use hir_expand::{ - HirFileId, InFile, Lookup, + AstId, ExpandTo, HirFileId, InFile, Lookup, attrs::{Meta, expand_cfg_attr, expand_cfg_attr_with_doc_comments}, + mod_path::ModPath, + span_map::SpanMap, }; use intern::Symbol; use itertools::Itertools; @@ -38,6 +40,7 @@ use la_arena::ArenaMap; use rustc_abi::ReprOptions; use rustc_hash::FxHashSet; use smallvec::SmallVec; +use span::AstIdMap; use syntax::{ AstNode, AstToken, NodeOrToken, SmolStr, SourceFile, SyntaxNode, SyntaxToken, T, ast::{self, AttrDocCommentIter, HasAttrs, IsString, TokenTreeChildren}, @@ -49,7 +52,9 @@ use crate::{ LocalFieldId, MacroId, ModuleId, TypeOrConstParamId, VariantId, db::DefDatabase, hir::generics::{GenericParams, LocalLifetimeParamId, LocalTypeOrConstParamId}, - nameres::ModuleOrigin, + macro_call_as_call_id, + nameres::{MacroSubNs, ModuleOrigin, crate_def_map}, + resolver::{HasResolver, Resolver}, src::{HasChildSource, HasSource}, }; @@ -398,6 +403,28 @@ fn attrs_source( (owner, None, None, krate) } +fn resolver_for_attr_def_id(db: &dyn DefDatabase, owner: AttrDefId) -> Resolver<'_> { + match owner { + AttrDefId::ModuleId(id) => id.resolver(db), + AttrDefId::AdtId(AdtId::StructId(id)) => id.resolver(db), + AttrDefId::AdtId(AdtId::UnionId(id)) => id.resolver(db), + AttrDefId::AdtId(AdtId::EnumId(id)) => id.resolver(db), + AttrDefId::FunctionId(id) => id.resolver(db), + AttrDefId::EnumVariantId(id) => id.resolver(db), + AttrDefId::StaticId(id) => id.resolver(db), + AttrDefId::ConstId(id) => id.resolver(db), + AttrDefId::TraitId(id) => id.resolver(db), + AttrDefId::TypeAliasId(id) => id.resolver(db), + AttrDefId::MacroId(MacroId::Macro2Id(id)) => id.resolver(db), + AttrDefId::MacroId(MacroId::MacroRulesId(id)) => id.resolver(db), + AttrDefId::MacroId(MacroId::ProcMacroId(id)) => id.resolver(db), + AttrDefId::ImplId(id) => id.resolver(db), + AttrDefId::ExternBlockId(id) => id.resolver(db), + AttrDefId::ExternCrateId(id) => id.resolver(db), + AttrDefId::UseId(id) => id.resolver(db), + } +} + fn collect_attrs( db: &dyn DefDatabase, owner: AttrDefId, @@ -479,8 +506,9 @@ pub struct RustcLayoutScalarValidRange { struct DocsSourceMapLine { /// The offset in [`Docs::docs`]. string_offset: TextSize, - /// The offset in the AST of the text. - ast_offset: TextSize, + /// The offset in the AST of the text. `None` for macro-expanded doc strings + /// where we cannot provide a faithful source mapping. + ast_offset: Option, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -569,12 +597,14 @@ impl Docs { source_map.partition_point(|line| line.string_offset <= string_range.start()) - 1; let after_range = &source_map[after_range..]; let line = after_range.first()?; + // Unmapped lines (from macro-expanded docs) cannot be mapped back to AST. + let ast_offset = line.ast_offset?; if after_range.get(1).is_some_and(|next_line| next_line.string_offset < string_range.end()) { // The range is combined from two lines - cannot map it back. return None; } - let ast_range = string_range - line.string_offset + line.ast_offset; + let ast_range = string_range - line.string_offset + ast_offset; let is_inner = if inner_docs_start .is_some_and(|inner_docs_start| string_range.start() >= inner_docs_start) { @@ -638,7 +668,7 @@ impl Docs { for line in doc.split('\n') { self.docs_source_map.push(DocsSourceMapLine { string_offset: TextSize::of(&self.docs), - ast_offset: offset_in_ast, + ast_offset: Some(offset_in_ast), }); offset_in_ast += TextSize::of(line) + TextSize::of("\n"); @@ -652,6 +682,21 @@ impl Docs { } } + fn extend_with_unmapped_doc_str(&mut self, doc: &str, indent: &mut usize) { + for line in doc.split('\n') { + self.docs_source_map.push(DocsSourceMapLine { + string_offset: TextSize::of(&self.docs), + ast_offset: None, + }); + let line = line.trim_end(); + if let Some(line_indent) = line.chars().position(|ch| !ch.is_whitespace()) { + *indent = std::cmp::min(*indent, line_indent); + } + self.docs.push_str(line); + self.docs.push('\n'); + } + } + fn remove_indent(&mut self, indent: usize, start_source_map_index: usize) { /// In case of panics, we want to avoid corrupted UTF-8 in `self.docs`, so we clear it. struct Guard<'a>(&'a mut Docs); @@ -721,7 +766,9 @@ impl Docs { // line should not get shifted (in general, the shift for the string offset is by the // number of lines until the current one, excluding the current one). line_source.string_offset -= accumulated_offset; - line_source.ast_offset += indent_size; + if let Some(ref mut ast_offset) = line_source.ast_offset { + *ast_offset += indent_size; + } accumulated_offset += indent_size; } @@ -757,6 +804,20 @@ pub struct DeriveInfo { pub helpers: Box<[Symbol]>, } +struct DocMacroExpander<'db> { + db: &'db dyn DefDatabase, + krate: Crate, + recursion_depth: usize, + recursion_limit: usize, +} + +struct DocExprSourceCtx<'db> { + resolver: Resolver<'db>, + file_id: HirFileId, + ast_id_map: &'db AstIdMap, + span_map: SpanMap, +} + fn extract_doc_aliases(result: &mut Vec, attr: Meta) -> ControlFlow { if let Meta::TokenTree { path, tt } = attr && path.is1("doc") @@ -785,7 +846,125 @@ fn extract_cfgs(result: &mut Vec, attr: Meta) -> ControlFlow( +fn expand_doc_expr_via_macro_pipeline<'db>( + expander: &mut DocMacroExpander<'db>, + source_ctx: &DocExprSourceCtx<'db>, + expr: ast::Expr, +) -> Option { + match expr { + ast::Expr::Literal(literal) => match literal.kind() { + ast::LiteralKind::String(string) => string.value().ok().map(Into::into), + _ => None, + }, + ast::Expr::MacroExpr(macro_expr) => { + let macro_call = macro_expr.macro_call()?; + let (expr, new_source_ctx) = expand_doc_macro_call(expander, source_ctx, macro_call)?; + // After expansion, the expr lives in the expansion file; use its source context. + expand_doc_expr_via_macro_pipeline(expander, &new_source_ctx, expr) + } + _ => None, + } +} + +fn expand_doc_macro_call<'db>( + expander: &mut DocMacroExpander<'db>, + source_ctx: &DocExprSourceCtx<'db>, + macro_call: ast::MacroCall, +) -> Option<(ast::Expr, DocExprSourceCtx<'db>)> { + if expander.recursion_depth >= expander.recursion_limit { + return None; + } + + let path = macro_call.path()?; + let mod_path = ModPath::from_src(expander.db, path, &mut |range| { + source_ctx.span_map.span_for_range(range).ctx + })?; + let call_site = source_ctx.span_map.span_for_range(macro_call.syntax().text_range()); + let ast_id = AstId::new(source_ctx.file_id, source_ctx.ast_id_map.ast_id(¯o_call)); + let call_id = macro_call_as_call_id( + expander.db, + ast_id, + &mod_path, + call_site.ctx, + ExpandTo::Expr, + expander.krate, + |path| { + source_ctx.resolver.resolve_path_as_macro_def(expander.db, path, Some(MacroSubNs::Bang)) + }, + &mut |_, _| (), + ) + .ok()? + .value?; + + expander.recursion_depth += 1; + let parse = expander.db.parse_macro_expansion(call_id).value.0; + let expr = parse.cast::().map(|parse| parse.tree())?; + expander.recursion_depth -= 1; + + // Build a new source context for the expansion file so that any further + // recursive expansion (e.g. a user macro expanding to `concat!(...)`) + // correctly resolves AstIds and spans in the expansion. + let expansion_file_id: HirFileId = call_id.into(); + let new_source_ctx = DocExprSourceCtx { + resolver: source_ctx.resolver.clone(), + file_id: expansion_file_id, + ast_id_map: expander.db.ast_id_map(expansion_file_id), + span_map: expander.db.span_map(expansion_file_id), + }; + Some((expr, new_source_ctx)) +} + +fn extend_with_attrs<'a, 'db>( + result: &mut Docs, + node: &SyntaxNode, + expect_inner_attrs: bool, + indent: &mut usize, + get_cfg_options: &dyn Fn() -> &'a CfgOptions, + cfg_options: &mut Option<&'a CfgOptions>, + mut expander: Option<&mut DocMacroExpander<'db>>, + source_ctx: Option<&DocExprSourceCtx<'db>>, +) { + expand_cfg_attr_with_doc_comments::<_, Infallible>( + AttrDocCommentIter::from_syntax_node(node).filter(|attr| match attr { + Either::Left(attr) => attr.kind().is_inner() == expect_inner_attrs, + Either::Right(comment) => comment + .kind() + .doc + .is_some_and(|kind| (kind == ast::CommentPlacement::Inner) == expect_inner_attrs), + }), + || *cfg_options.get_or_insert_with(get_cfg_options), + |attr| { + match attr { + Either::Right(doc_comment) => result.extend_with_doc_comment(doc_comment, indent), + Either::Left((attr, _, _, top_attr)) => match attr { + Meta::NamedKeyValue { name: Some(name), value: Some(value), .. } + if name.text() == "doc" => + { + result.extend_with_doc_attr(value, indent); + } + Meta::NamedKeyValue { name: Some(name), value: None, .. } + if name.text() == "doc" => + { + if let (Some(expander), Some(source_ctx)) = + (expander.as_deref_mut(), source_ctx) + && let Some(expr) = top_attr.expr() + && let Some(expanded) = + expand_doc_expr_via_macro_pipeline(expander, source_ctx, expr) + { + result.extend_with_unmapped_doc_str(&expanded, indent); + } + } + _ => {} + }, + } + ControlFlow::Continue(()) + }, + ); +} + +fn extract_docs<'a, 'db>( + mut expander: Option<&mut DocMacroExpander<'db>>, + resolver: Option<&Resolver<'db>>, get_cfg_options: &dyn Fn() -> &'a CfgOptions, source: InFile, outer_mod_decl: Option>, @@ -802,49 +981,69 @@ fn extract_docs<'a>( }; let mut cfg_options = None; - let mut extend_with_attrs = - |result: &mut Docs, node: &SyntaxNode, expect_inner_attrs, indent: &mut usize| { - expand_cfg_attr_with_doc_comments::<_, Infallible>( - AttrDocCommentIter::from_syntax_node(node).filter(|attr| match attr { - Either::Left(attr) => attr.kind().is_inner() == expect_inner_attrs, - Either::Right(comment) => comment.kind().doc.is_some_and(|kind| { - (kind == ast::CommentPlacement::Inner) == expect_inner_attrs - }), - }), - || cfg_options.get_or_insert_with(get_cfg_options), - |attr| { - match attr { - Either::Right(doc_comment) => { - result.extend_with_doc_comment(doc_comment, indent) - } - Either::Left((attr, _, _, _)) => match attr { - // FIXME: Handle macros: `#[doc = concat!("foo", "bar")]`. - Meta::NamedKeyValue { - name: Some(name), value: Some(value), .. - } if name.text() == "doc" => { - result.extend_with_doc_attr(value, indent); - } - _ => {} - }, - } - ControlFlow::Continue(()) - }, - ); - }; if let Some(outer_mod_decl) = outer_mod_decl { let mut indent = usize::MAX; - extend_with_attrs(&mut result, outer_mod_decl.value.syntax(), false, &mut indent); + let outer_source_ctx = + if let (Some(expander), Some(resolver)) = (expander.as_deref(), resolver) { + Some(DocExprSourceCtx { + resolver: resolver.clone(), + file_id: outer_mod_decl.file_id, + ast_id_map: expander.db.ast_id_map(outer_mod_decl.file_id), + span_map: expander.db.span_map(outer_mod_decl.file_id), + }) + } else { + None + }; + extend_with_attrs( + &mut result, + outer_mod_decl.value.syntax(), + false, + &mut indent, + get_cfg_options, + &mut cfg_options, + expander.as_deref_mut(), + outer_source_ctx.as_ref(), + ); result.remove_indent(indent, 0); result.outline_mod = Some((outer_mod_decl.file_id, result.docs_source_map.len())); } let inline_source_map_start = result.docs_source_map.len(); let mut indent = usize::MAX; - extend_with_attrs(&mut result, source.value.syntax(), false, &mut indent); + let inline_source_ctx = + if let (Some(expander), Some(resolver)) = (expander.as_deref(), resolver) { + Some(DocExprSourceCtx { + resolver: resolver.clone(), + file_id: source.file_id, + ast_id_map: expander.db.ast_id_map(source.file_id), + span_map: expander.db.span_map(source.file_id), + }) + } else { + None + }; + extend_with_attrs( + &mut result, + source.value.syntax(), + false, + &mut indent, + get_cfg_options, + &mut cfg_options, + expander.as_deref_mut(), + inline_source_ctx.as_ref(), + ); if let Some(inner_attrs_node) = &inner_attrs_node { result.inline_inner_docs_start = Some(TextSize::of(&result.docs)); - extend_with_attrs(&mut result, inner_attrs_node, true, &mut indent); + extend_with_attrs( + &mut result, + inner_attrs_node, + true, + &mut indent, + get_cfg_options, + &mut cfg_options, + expander.as_deref_mut(), + inline_source_ctx.as_ref(), + ); } result.remove_indent(indent, inline_source_map_start); @@ -1292,10 +1491,25 @@ impl AttrFlags { pub fn docs(db: &dyn DefDatabase, owner: AttrDefId) -> Option> { let (source, outer_mod_decl, _extra_crate_attrs, krate) = attrs_source(db, owner); let inner_attrs_node = source.value.inner_attributes_node(); + let resolver = resolver_for_attr_def_id(db, owner); + let def_map = crate_def_map(db, krate); + let recursion_limit = if cfg!(test) { + std::cmp::min(32, def_map.recursion_limit() as usize) + } else { + def_map.recursion_limit() as usize + }; + let mut expander = DocMacroExpander { db, krate, recursion_depth: 0, recursion_limit }; // Note: we don't have to pass down `_extra_crate_attrs` here, since `extract_docs` // does not handle crate-level attributes related to docs. // See: https://doc.rust-lang.org/rustdoc/write-documentation/the-doc-attribute.html#at-the-crate-level - extract_docs(&|| krate.cfg_options(db), source, outer_mod_decl, inner_attrs_node) + extract_docs( + Some(&mut expander), + Some(&resolver), + &|| krate.cfg_options(db), + source, + outer_mod_decl, + inner_attrs_node, + ) } #[inline] @@ -1308,8 +1522,25 @@ impl AttrFlags { db: &dyn DefDatabase, variant: VariantId, ) -> ArenaMap>> { + let krate = variant.module(db).krate(db); + let resolver = variant.resolver(db); + let def_map = crate_def_map(db, krate); + let recursion_limit = if cfg!(test) { + std::cmp::min(32, def_map.recursion_limit() as usize) + } else { + def_map.recursion_limit() as usize + }; collect_field_attrs(db, variant, |cfg_options, field| { - extract_docs(&|| cfg_options, field, None, None) + let mut expander = + DocMacroExpander { db, krate, recursion_depth: 0, recursion_limit }; + extract_docs( + Some(&mut expander), + Some(&resolver), + &|| cfg_options, + field, + None, + None, + ) }) } } @@ -1580,19 +1811,27 @@ mod tests { [ DocsSourceMapLine { string_offset: 0, - ast_offset: 123, + ast_offset: Some( + 123, + ), }, DocsSourceMapLine { string_offset: 5, - ast_offset: 128, + ast_offset: Some( + 128, + ), }, DocsSourceMapLine { string_offset: 15, - ast_offset: 261, + ast_offset: Some( + 261, + ), }, DocsSourceMapLine { string_offset: 20, - ast_offset: 267, + ast_offset: Some( + 267, + ), }, ] "#]] @@ -1607,19 +1846,27 @@ mod tests { [ DocsSourceMapLine { string_offset: 0, - ast_offset: 124, + ast_offset: Some( + 124, + ), }, DocsSourceMapLine { string_offset: 4, - ast_offset: 129, + ast_offset: Some( + 129, + ), }, DocsSourceMapLine { string_offset: 13, - ast_offset: 262, + ast_offset: Some( + 262, + ), }, DocsSourceMapLine { string_offset: 17, - ast_offset: 268, + ast_offset: Some( + 268, + ), }, ] "#]] @@ -1632,35 +1879,51 @@ mod tests { [ DocsSourceMapLine { string_offset: 0, - ast_offset: 124, + ast_offset: Some( + 124, + ), }, DocsSourceMapLine { string_offset: 4, - ast_offset: 129, + ast_offset: Some( + 129, + ), }, DocsSourceMapLine { string_offset: 13, - ast_offset: 262, + ast_offset: Some( + 262, + ), }, DocsSourceMapLine { string_offset: 17, - ast_offset: 268, + ast_offset: Some( + 268, + ), }, DocsSourceMapLine { string_offset: 21, - ast_offset: 124, + ast_offset: Some( + 124, + ), }, DocsSourceMapLine { string_offset: 25, - ast_offset: 129, + ast_offset: Some( + 129, + ), }, DocsSourceMapLine { string_offset: 34, - ast_offset: 262, + ast_offset: Some( + 262, + ), }, DocsSourceMapLine { string_offset: 38, - ast_offset: 268, + ast_offset: Some( + 268, + ), }, ] "#]] 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 7a758cd4c1399..e7a8b140f883b 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs @@ -11404,3 +11404,175 @@ pub trait MyTrait { "#]], ); } + +#[test] +fn test_hover_doc_attr_macro_generated_method() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! concat {} + +macro_rules! bar { + () => { + struct Bar; + impl Bar { + #[doc = concat!("Do", " the foo")] + fn foo(&self) {} + } + } +} + +bar!(); + +fn foo() { let bar = Bar; bar.fo$0o(); } +"#, + expect![[r#" + *foo* + + ```rust + ra_test_fixture::Bar + ``` + + ```rust + fn foo(&self) + ``` + + --- + + Do the foo + "#]], + ); +} + +#[test] +fn test_hover_doc_attr_concat_macro() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! concat {} + +#[doc = concat!("Hello", " ", "World")] +struct Ba$0r; +"#, + expect![[r#" + *Bar* + + ```rust + ra_test_fixture + ``` + + ```rust + struct Bar + ``` + + --- + + size = 0, align = 1, no Drop + + --- + + Hello World + "#]], + ); +} + +#[test] +fn test_hover_doc_attr_user_macro_returning_string() { + check( + r#" +macro_rules! doc_str { + () => { "Documentation from macro" }; +} + +#[doc = doc_str!()] +struct Ba$0r; +"#, + expect![[r#" + *Bar* + + ```rust + ra_test_fixture + ``` + + ```rust + struct Bar + ``` + + --- + + size = 0, align = 1, no Drop + + --- + + Documentation from macro + "#]], + ); +} + +#[test] +fn test_hover_doc_attr_mixed_literal_and_macro() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! concat {} + +/// First line +#[doc = concat!("Second", " line")] +struct Ba$0r; +"#, + expect![[r#" + *Bar* + + ```rust + ra_test_fixture + ``` + + ```rust + struct Bar + ``` + + --- + + size = 0, align = 1, no Drop + + --- + + First line + Second line + "#]], + ); +} + +#[test] +fn test_hover_doc_attr_field_with_macro() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! concat {} + +struct Bar { + #[doc = concat!("field", " docs")] + ba$0z: i32, +} +"#, + expect![[r#" + *baz* + + ```rust + ra_test_fixture::Bar + ``` + + ```rust + baz: i32 + ``` + + --- + + size = 4, align = 4, offset = 0, no Drop + + --- + + field docs + "#]], + ); +} From 4c22d0d87b2cb6cd44a55d594a0c2bfbf8ff4f00 Mon Sep 17 00:00:00 2001 From: so1ve Date: Wed, 1 Apr 2026 17:08:35 +0800 Subject: [PATCH 042/144] update tests --- .../rust-analyzer/crates/ide/src/hover/tests.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) 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 e7a8b140f883b..776b161d691d7 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs @@ -11406,23 +11406,26 @@ pub trait MyTrait { } #[test] -fn test_hover_doc_attr_macro_generated_method() { +fn test_hover_doc_attr_macro_generated_method_stringify_self_ty() { check( r#" #[rustc_builtin_macro] macro_rules! concat {} +#[rustc_builtin_macro] +macro_rules! stringify {} + macro_rules! bar { - () => { - struct Bar; - impl Bar { - #[doc = concat!("Do", " the foo")] + ($SelfT:ident) => { + struct $SelfT; + impl $SelfT { + #[doc = concat!("Do the foo for ", stringify!($SelfT))] fn foo(&self) {} } } } -bar!(); +bar!(Bar); fn foo() { let bar = Bar; bar.fo$0o(); } "#, @@ -11439,7 +11442,7 @@ fn foo() { let bar = Bar; bar.fo$0o(); } --- - Do the foo + Do the foo for Bar "#]], ); } From 2ca6c7d0b9fa8eae3750477ff6f229ce02814b30 Mon Sep 17 00:00:00 2001 From: so1ve Date: Wed, 1 Apr 2026 17:13:04 +0800 Subject: [PATCH 043/144] chore: fix clippy --- src/tools/rust-analyzer/crates/hir-def/src/attrs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs b/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs index f91c82d729f84..3bf709043679c 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs @@ -1041,7 +1041,7 @@ fn extract_docs<'a, 'db>( &mut indent, get_cfg_options, &mut cfg_options, - expander.as_deref_mut(), + expander, inline_source_ctx.as_ref(), ); } From 74fa516573f711e80266598c13f9eed9f0faf9f0 Mon Sep 17 00:00:00 2001 From: so1ve Date: Wed, 1 Apr 2026 17:34:43 +0800 Subject: [PATCH 044/144] tests: add `include_str` test --- .../crates/ide/src/hover/tests.rs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) 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 776b161d691d7..63af9a31fb159 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs @@ -11479,6 +11479,42 @@ struct Ba$0r; ); } +#[test] +fn test_hover_doc_attr_include_str_macro() { + check( + r#" +//- /main.rs +#[rustc_builtin_macro] +macro_rules! include_str {} + +#[doc = include_str!("docs.md")] +struct Ba$0r; + +//- /docs.md +Included docs from file. +"#, + expect![[r#" + *Bar* + + ```rust + ra_test_fixture + ``` + + ```rust + struct Bar + ``` + + --- + + size = 0, align = 1, no Drop + + --- + + Included docs from file. + "#]], + ); +} + #[test] fn test_hover_doc_attr_user_macro_returning_string() { check( From 0f9a616ce608dd1c3115a6a2f80140337f855012 Mon Sep 17 00:00:00 2001 From: so1ve Date: Wed, 1 Apr 2026 18:19:44 +0800 Subject: [PATCH 045/144] fix: handle `ParenExpr` correctly --- .../rust-analyzer/crates/hir-def/src/attrs.rs | 3 ++ .../crates/ide/src/hover/tests.rs | 41 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs b/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs index 3bf709043679c..a5bc283330526 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs @@ -852,6 +852,9 @@ fn expand_doc_expr_via_macro_pipeline<'db>( expr: ast::Expr, ) -> Option { match expr { + ast::Expr::ParenExpr(paren_expr) => { + expand_doc_expr_via_macro_pipeline(expander, source_ctx, paren_expr.expr()?) + } ast::Expr::Literal(literal) => match literal.kind() { ast::LiteralKind::String(string) => string.value().ok().map(Into::into), _ => None, 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 63af9a31fb159..a57db032db2c9 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs @@ -11447,6 +11447,47 @@ fn foo() { let bar = Bar; bar.fo$0o(); } ); } +#[test] +fn test_hover_doc_attr_macro_argument_expr_issue_7688() { + check( + r#" +#[rustc_builtin_macro] +macro_rules! concat {} + +macro_rules! doc_comment { + ($x:expr, $($tt:tt)*) => { + #[doc = $x] + $($tt)* + }; +} + +doc_comment! { + concat!("Hello", " world"), + struct Ba$0r; +} +"#, + expect![[r#" + *Bar* + + ```rust + ra_test_fixture + ``` + + ```rust + struct Bar + ``` + + --- + + size = 0, align = 1, no Drop + + --- + + Hello world + "#]], + ); +} + #[test] fn test_hover_doc_attr_concat_macro() { check( From 99d0c359275b180801ecb15eaebc5c333efa96d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Wed, 1 Apr 2026 13:56:21 +0200 Subject: [PATCH 046/144] Fix rustc-pull CI workflow --- src/tools/rust-analyzer/.github/workflows/rustc-pull.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tools/rust-analyzer/.github/workflows/rustc-pull.yml b/src/tools/rust-analyzer/.github/workflows/rustc-pull.yml index 37cf5f3726b25..be3362b79bbfe 100644 --- a/src/tools/rust-analyzer/.github/workflows/rustc-pull.yml +++ b/src/tools/rust-analyzer/.github/workflows/rustc-pull.yml @@ -12,6 +12,7 @@ jobs: uses: rust-lang/josh-sync/.github/workflows/rustc-pull.yml@main with: github-app-id: ${{ vars.APP_CLIENT_ID }} + pr-author: "workflows-rust-analyzer[bot]" zulip-stream-id: 185405 zulip-bot-email: "rust-analyzer-ci-bot@rust-lang.zulipchat.com" pr-base-branch: master From 6fc3880b0402cd31dd02a165c767958e540d88f2 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Wed, 1 Apr 2026 08:09:16 +0000 Subject: [PATCH 047/144] hwaddress: automatically add -Ctarget-feature=tagged-globals --- compiler/rustc_codegen_llvm/src/llvm_util.rs | 1 + compiler/rustc_codegen_ssa/src/target_features.rs | 11 ++++++++++- src/doc/unstable-book/src/compiler-flags/sanitizer.md | 9 ++------- tests/codegen-llvm/sanitizer/hwasan-vs-khwasan.rs | 2 ++ tests/ui/sanitizer/hwaddress.rs | 4 +--- tests/ui/sanitizer/hwaddress.stderr | 7 ------- 6 files changed, 16 insertions(+), 18 deletions(-) delete mode 100644 tests/ui/sanitizer/hwaddress.stderr diff --git a/compiler/rustc_codegen_llvm/src/llvm_util.rs b/compiler/rustc_codegen_llvm/src/llvm_util.rs index 3e0a6efde0252..180559d28d848 100644 --- a/compiler/rustc_codegen_llvm/src/llvm_util.rs +++ b/compiler/rustc_codegen_llvm/src/llvm_util.rs @@ -630,6 +630,7 @@ fn llvm_features_by_flags(sess: &Session, features: &mut Vec) { } target_features::retpoline_features_by_flags(sess, features); + target_features::sanitizer_features_by_flags(sess, features); // -Zfixed-x18 if sess.opts.unstable_opts.fixed_x18 { diff --git a/compiler/rustc_codegen_ssa/src/target_features.rs b/compiler/rustc_codegen_ssa/src/target_features.rs index 8ac3f0555db27..24f731c01996d 100644 --- a/compiler/rustc_codegen_ssa/src/target_features.rs +++ b/compiler/rustc_codegen_ssa/src/target_features.rs @@ -10,7 +10,7 @@ use rustc_session::Session; use rustc_session::lint::builtin::AARCH64_SOFTFLOAT_NEON; use rustc_session::parse::feature_err; use rustc_span::{Span, Symbol, edit_distance, sym}; -use rustc_target::spec::Arch; +use rustc_target::spec::{Arch, SanitizerSet}; use rustc_target::target_features::{RUSTC_SPECIFIC_FEATURES, Stability}; use smallvec::SmallVec; @@ -460,6 +460,15 @@ pub fn retpoline_features_by_flags(sess: &Session, features: &mut Vec) { } } +/// Computes the backend target features to be added to account for sanitizer flags. +pub fn sanitizer_features_by_flags(sess: &Session, features: &mut Vec) { + // It's intentional that this is done only for non-kernel version of hwaddress. This matches + // clang behavior. + if sess.sanitizers().contains(SanitizerSet::HWADDRESS) { + features.push("+tagged-globals".into()); + } +} + pub(crate) fn provide(providers: &mut Providers) { *providers = Providers { rust_target_features: |tcx, cnum| { diff --git a/src/doc/unstable-book/src/compiler-flags/sanitizer.md b/src/doc/unstable-book/src/compiler-flags/sanitizer.md index eb070c22dc288..b0f6c97ff5a73 100644 --- a/src/doc/unstable-book/src/compiler-flags/sanitizer.md +++ b/src/doc/unstable-book/src/compiler-flags/sanitizer.md @@ -552,10 +552,6 @@ HWAddressSanitizer is supported on the following targets: * `aarch64-linux-android` * `aarch64-unknown-linux-gnu` -HWAddressSanitizer requires `tagged-globals` target feature to instrument -globals. To enable this target feature compile with `-C -target-feature=+tagged-globals` - See the [Clang HWAddressSanitizer documentation][clang-hwasan] for more details. ## Example @@ -570,9 +566,8 @@ fn main() { ``` ```shell -$ rustc main.rs -Zsanitizer=hwaddress -C target-feature=+tagged-globals -C -linker=aarch64-linux-gnu-gcc -C link-arg=-fuse-ld=lld --target -aarch64-unknown-linux-gnu +$ rustc main.rs -Zsanitizer=hwaddress -Clinker=aarch64-linux-gnu-gcc +-Clink-arg=-fuse-ld=lld --target aarch64-unknown-linux-gnu ``` ```shell diff --git a/tests/codegen-llvm/sanitizer/hwasan-vs-khwasan.rs b/tests/codegen-llvm/sanitizer/hwasan-vs-khwasan.rs index 93932d86582fa..c34df8c3c5acd 100644 --- a/tests/codegen-llvm/sanitizer/hwasan-vs-khwasan.rs +++ b/tests/codegen-llvm/sanitizer/hwasan-vs-khwasan.rs @@ -18,6 +18,7 @@ extern crate minicore; // hwasan: @__hwasan_tls // hwasan: call void @llvm.hwasan.check.memaccess.shortgranules // hwasan: declare void @__hwasan_init() +// hwasan: attributes #0 {{.*"target-features"=".*\+tagged-globals.*"}} // The `__hwasan_tls` symbol is unconditionally declared by LLVM's `HWAddressSanitizer` pass. // However, in kernel mode KHWASAN does not actually use it (because shadow mapping is fixed @@ -33,6 +34,7 @@ extern crate minicore; // // khwasan-NOT: @__hwasan_init // khwasan: call void @llvm.hwasan.check.memaccess.shortgranules +// khwasan-NOT: attributes #0 {{.*"target-features"=".*\+tagged-globals.*"}} #[no_mangle] pub fn test(b: &mut u8) -> u8 { *b diff --git a/tests/ui/sanitizer/hwaddress.rs b/tests/ui/sanitizer/hwaddress.rs index 8666e7de44924..7557b0f53f7c8 100644 --- a/tests/ui/sanitizer/hwaddress.rs +++ b/tests/ui/sanitizer/hwaddress.rs @@ -1,7 +1,7 @@ //@ needs-sanitizer-support //@ needs-sanitizer-hwaddress // -//@ compile-flags: -Z sanitizer=hwaddress -O -g -C target-feature=+tagged-globals -C unsafe-allow-abi-mismatch=sanitizer +//@ compile-flags: -Z sanitizer=hwaddress -O -g -C unsafe-allow-abi-mismatch=sanitizer // //@ run-fail //@ error-pattern: HWAddressSanitizer: tag-mismatch @@ -15,5 +15,3 @@ fn main() { let code = unsafe { *xs.offset(4) }; std::process::exit(code); } - -//~? WARN unknown and unstable feature specified for `-Ctarget-feature`: `tagged-globals` diff --git a/tests/ui/sanitizer/hwaddress.stderr b/tests/ui/sanitizer/hwaddress.stderr deleted file mode 100644 index 37afe0bd779ec..0000000000000 --- a/tests/ui/sanitizer/hwaddress.stderr +++ /dev/null @@ -1,7 +0,0 @@ -warning: unknown and unstable feature specified for `-Ctarget-feature`: `tagged-globals` - | - = note: it is still passed through to the codegen backend, but use of this feature might be unsound and the behavior of this feature can change in the future - = help: consider filing a feature request - -warning: 1 warning emitted - From 3dab2d34bde29ead54e001927aef48906d67008b Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Wed, 1 Apr 2026 22:16:31 +0530 Subject: [PATCH 048/144] use factory variant of add_trait_assoc_items_to_impl in generate_impl --- .../crates/ide-assists/src/handlers/generate_impl.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs index 2d1235792dcf7..285b8eedd08ab 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs @@ -8,8 +8,8 @@ use syntax::{ use crate::{ AssistContext, AssistId, Assists, utils::{ - self, DefaultMethods, IgnoreAssocItems, generate_impl_with_factory, - generate_trait_impl_intransitive, + self, DefaultMethods, IgnoreAssocItems, add_trait_assoc_items_to_impl_with_factory, + generate_impl_with_factory, generate_trait_impl_intransitive, }, }; @@ -212,7 +212,8 @@ pub(crate) fn generate_impl_trait(acc: &mut Assists, ctx: &AssistContext<'_>) -> make_impl_(None) } else { let impl_ = make_impl_(None); - let assoc_items = utils::add_trait_assoc_items_to_impl( + let assoc_items = add_trait_assoc_items_to_impl_with_factory( + &make, &ctx.sema, ctx.config, &missing_items, From 07643406f9e7cfcafc3e35da16a26065fee7f5c0 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Wed, 1 Apr 2026 22:17:50 +0530 Subject: [PATCH 049/144] replace factory variant of add_trait_assoc_items_to_impl, as the main variant --- .../src/handlers/add_missing_impl_members.rs | 6 +- .../ide-assists/src/handlers/generate_impl.rs | 4 +- .../replace_derive_with_manual_impl.rs | 6 +- .../crates/ide-assists/src/utils.rs | 72 +------------------ 4 files changed, 9 insertions(+), 79 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs index 3689dc24b3601..e43adefe67209 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs @@ -9,8 +9,8 @@ use crate::{ AssistId, assist_context::{AssistContext, Assists}, utils::{ - DefaultMethods, IgnoreAssocItems, add_trait_assoc_items_to_impl_with_factory, - filter_assoc_items, gen_trait_fn_body, + DefaultMethods, IgnoreAssocItems, add_trait_assoc_items_to_impl, filter_assoc_items, + gen_trait_fn_body, }, }; @@ -149,7 +149,7 @@ fn add_missing_impl_members_inner( let target = impl_def.syntax().text_range(); acc.add(AssistId::quick_fix(assist_id), label, target, |edit| { let make = SyntaxFactory::with_mappings(); - let new_item = add_trait_assoc_items_to_impl_with_factory( + let new_item = add_trait_assoc_items_to_impl( &make, &ctx.sema, ctx.config, diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs index 285b8eedd08ab..af123eeaa0ce8 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs @@ -8,7 +8,7 @@ use syntax::{ use crate::{ AssistContext, AssistId, Assists, utils::{ - self, DefaultMethods, IgnoreAssocItems, add_trait_assoc_items_to_impl_with_factory, + self, DefaultMethods, IgnoreAssocItems, add_trait_assoc_items_to_impl, generate_impl_with_factory, generate_trait_impl_intransitive, }, }; @@ -212,7 +212,7 @@ pub(crate) fn generate_impl_trait(acc: &mut Assists, ctx: &AssistContext<'_>) -> make_impl_(None) } else { let impl_ = make_impl_(None); - let assoc_items = add_trait_assoc_items_to_impl_with_factory( + let assoc_items = add_trait_assoc_items_to_impl( &make, &ctx.sema, ctx.config, diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs index f281fdf513126..01299729bc864 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs @@ -12,8 +12,8 @@ use crate::{ AssistConfig, AssistId, assist_context::{AssistContext, Assists}, utils::{ - DefaultMethods, IgnoreAssocItems, add_trait_assoc_items_to_impl_with_factory, - filter_assoc_items, gen_trait_fn_body, generate_trait_impl, generate_trait_impl_with_item, + DefaultMethods, IgnoreAssocItems, add_trait_assoc_items_to_impl, filter_assoc_items, + gen_trait_fn_body, generate_trait_impl, generate_trait_impl_with_item, }, }; @@ -211,7 +211,7 @@ fn impl_def_from_trait( let trait_ty: ast::Type = make.ty_path(trait_path.clone()).into(); let impl_def = generate_trait_impl(&make, impl_is_unsafe, adt, trait_ty.clone()); - let assoc_items = add_trait_assoc_items_to_impl_with_factory( + let assoc_items = add_trait_assoc_items_to_impl( &make, sema, config, diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs index fee5014232921..c77321ebd1d7e 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs @@ -203,11 +203,9 @@ pub fn filter_assoc_items( /// [`filter_assoc_items()`]), clones each item for update and applies path transformation to it, /// then inserts into `impl_`. Returns the modified `impl_` and the first associated item that got /// inserted. -/// -/// Legacy: prefer [`add_trait_assoc_items_to_impl_with_factory`] when a [`SyntaxFactory`] is -/// available. #[must_use] pub fn add_trait_assoc_items_to_impl( + make: &SyntaxFactory, sema: &Semantics<'_, RootDatabase>, config: &AssistConfig, original_items: &[InFile], @@ -248,74 +246,6 @@ pub fn add_trait_assoc_items_to_impl( cloned_item.remove_attrs_and_docs(); cloned_item }) - .filter_map(|item| match item { - ast::AssocItem::Fn(fn_) if fn_.body().is_none() => { - let fn_ = fn_.clone_subtree(); - let new_body = make::block_expr(None, Some(expr_fill_default(config))); - let mut fn_editor = SyntaxEditor::new(fn_.syntax().clone()); - fn_.replace_or_insert_body(&mut fn_editor, new_body.clone_for_update()); - let new_fn_ = fn_editor.finish().new_root().clone(); - ast::AssocItem::cast(new_fn_) - } - ast::AssocItem::TypeAlias(type_alias) => { - let type_alias = type_alias.clone_subtree(); - if let Some(type_bound_list) = type_alias.type_bound_list() { - let mut type_alias_editor = SyntaxEditor::new(type_alias.syntax().clone()); - type_bound_list.remove(&mut type_alias_editor); - let type_alias = type_alias_editor.finish().new_root().clone(); - ast::AssocItem::cast(type_alias) - } else { - Some(ast::AssocItem::TypeAlias(type_alias)) - } - } - item => Some(item), - }) - .map(|item| AstNodeEdit::indent(&item, new_indent_level)) - .collect() -} - -/// [`SyntaxFactory`]-based variant of [`add_trait_assoc_items_to_impl`]. -#[must_use] -pub fn add_trait_assoc_items_to_impl_with_factory( - make: &SyntaxFactory, - sema: &Semantics<'_, RootDatabase>, - config: &AssistConfig, - original_items: &[InFile], - trait_: hir::Trait, - impl_: &ast::Impl, - target_scope: &hir::SemanticsScope<'_>, -) -> Vec { - let new_indent_level = IndentLevel::from_node(impl_.syntax()) + 1; - original_items - .iter() - .map(|InFile { file_id, value: original_item }| { - let mut cloned_item = { - if let Some(macro_file) = file_id.macro_file() { - let span_map = sema.db.expansion_span_map(macro_file); - let item_prettified = prettify_macro_expansion( - sema.db, - original_item.syntax().clone(), - &span_map, - target_scope.krate().into(), - ); - if let Some(formatted) = ast::AssocItem::cast(item_prettified) { - return formatted; - } else { - stdx::never!("formatted `AssocItem` could not be cast back to `AssocItem`"); - } - } - original_item - } - .reset_indent(); - - if let Some(source_scope) = sema.scope(original_item.syntax()) { - let transform = - PathTransform::trait_impl(target_scope, &source_scope, trait_, impl_.clone()); - cloned_item = ast::AssocItem::cast(transform.apply(cloned_item.syntax())).unwrap(); - } - cloned_item.remove_attrs_and_docs(); - cloned_item - }) .filter_map(|item| match item { ast::AssocItem::Fn(fn_) if fn_.body().is_none() => { let fn_ = fn_.clone_subtree(); From 23fe3afc3e1415c3caa818611bc9632abe7c140f Mon Sep 17 00:00:00 2001 From: Wilfred Hughes Date: Thu, 2 Apr 2026 17:20:50 +0100 Subject: [PATCH 050/144] internal: Ensure tracing is configured in slow tests rust-lang/rust-analyzer#16394 changed the logging initialisation to create a Config struct, but never did anything with it. Call `.init()` so slow tests have tracing configured. You can test this by adding a test failure to a slow test, e.g. `test_format_document_2018`, and then running it: ``` $ RA_LOG=trace RUN_SLOW_TESTS=1 cargo t test_format_document_2018 ``` Previously this didn't log anything, even though RA_LOG was set. --- .../crates/rust-analyzer/tests/slow-tests/support.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/support.rs b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/support.rs index 7ee31f3d53eab..73904036730b9 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/support.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/support.rs @@ -173,7 +173,8 @@ impl Project<'_> { chalk_filter: std::env::var("CHALK_DEBUG").ok(), profile_filter: std::env::var("RA_PROFILE").ok(), json_profile_filter: std::env::var("RA_PROFILE_JSON").ok(), - }; + } + .init(); }); let FixtureWithProjectMeta { From 2aefa3c8e9a7371022f35c366988e87d360d69b0 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Fri, 3 Apr 2026 00:05:40 +0300 Subject: [PATCH 051/144] Support cfg-ing array elements --- .../crates/hir-def/src/expr_store/lower.rs | 10 ++++++++- .../hir-def/src/expr_store/tests/body.rs | 21 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs index 74006c6037030..7fe91a3d02dba 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower.rs @@ -1465,7 +1465,15 @@ impl<'db> ExprCollector<'db> { match kind { ArrayExprKind::ElementList(e) => { - let elements = e.map(|expr| self.collect_expr(expr)).collect(); + let elements = e + .filter_map(|expr| { + if self.check_cfg(&expr) { + Some(self.collect_expr(expr)) + } else { + None + } + }) + .collect(); self.alloc_expr(Expr::Array(Array::ElementList { elements }), syntax_ptr) } ArrayExprKind::Repeat { initializer, repeat } => { diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/body.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/body.rs index 985cd9666267d..4e5f2ca89327e 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/body.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/body.rs @@ -660,3 +660,24 @@ async fn main(&self, param1: i32, ref mut param2: i32, _: i32, param4 @ _: i32, }"#]], ) } + +#[test] +fn array_element_cfg() { + pretty_print( + r#" +fn foo() { + [ + (), + #[cfg(false)] + () + ]; +} + "#, + expect![[r#" + fn foo() { + [ + (), + ]; + }"#]], + ); +} From 210298399ba11e3d736d3e469d102eb5839348a1 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Thu, 2 Apr 2026 10:13:24 +0530 Subject: [PATCH 052/144] make deep clones of insertion/replacement nodes in edit flow itself --- .../syntax/src/syntax_editor/edit_algo.rs | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edit_algo.rs b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edit_algo.rs index e697d97061d94..7bd1d7c755f10 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edit_algo.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edit_algo.rs @@ -192,11 +192,8 @@ pub(super) fn apply_edits(editor: SyntaxEditor) -> SyntaxEdit { } }; } - Change::Replace(SyntaxElement::Node(target), Some(SyntaxElement::Node(new_target))) => { + Change::Replace(SyntaxElement::Node(target), Some(SyntaxElement::Node(_))) => { *target = tree_mutator.make_syntax_mut(target); - if new_target.ancestors().any(|node| node == tree_mutator.immutable) { - *new_target = new_target.clone_for_update(); - } } Change::Replace(target, _) | Change::ReplaceWithMany(target, _) => { *target = tree_mutator.make_element_mut(target); @@ -209,6 +206,31 @@ pub(super) fn apply_edits(editor: SyntaxEditor) -> SyntaxEdit { } } + match &mut changes[index as usize] { + Change::Insert(_, SyntaxElement::Node(node)) + | Change::Replace(_, Some(SyntaxElement::Node(node))) => { + if node.parent().is_some() { + *node = node.clone_subtree().clone_for_update(); + } else if *node == tree_mutator.immutable { + *node = node.clone_for_update(); + } + } + Change::InsertAll(_, elements) + | Change::ReplaceWithMany(_, elements) + | Change::ReplaceAll(_, elements) => { + for element in elements { + if let SyntaxElement::Node(node) = element { + if node.parent().is_some() { + *node = node.clone_subtree().clone_for_update(); + } else if *node == tree_mutator.immutable { + *node = node.clone_for_update(); + } + } + } + } + _ => {} + } + match &mut changes[index as usize] { Change::Insert(_, element) | Change::Replace(_, Some(element)) => { deduplicate_node(element); From 5057a443a14385a3a679a2a3e20707046b22c54c Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Thu, 2 Apr 2026 10:13:55 +0530 Subject: [PATCH 053/144] remove clone_for_update from iniline_type_alias --- .../src/handlers/inline_type_alias.rs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_type_alias.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_type_alias.rs index f3ebe61078192..f5b5b228f30c5 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_type_alias.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_type_alias.rs @@ -170,7 +170,7 @@ impl Replacement { Replacement::Generic { lifetime_map, const_and_type_map } => { create_replacement(lifetime_map, const_and_type_map, concrete_type) } - Replacement::Plain => concrete_type.syntax().clone_subtree().clone_for_update(), + Replacement::Plain => concrete_type.syntax().clone(), } } } @@ -361,7 +361,7 @@ fn create_replacement( continue; } - replacements.push((syntax.clone(), new_lifetime.syntax().clone_for_update())); + replacements.push((syntax.clone(), new_lifetime.syntax().clone())); } } else if let Some(name_ref) = ast::NameRef::cast(syntax.clone()) { let Some(replacement_syntax) = const_and_type_map.0.get(&name_ref.to_string()) else { @@ -449,15 +449,12 @@ impl ConstOrTypeGeneric { } fn replacement_value(&self) -> Option { - Some( - match self { - ConstOrTypeGeneric::ConstArg(ca) => ca.expr()?.syntax().clone(), - ConstOrTypeGeneric::TypeArg(ta) => ta.syntax().clone(), - ConstOrTypeGeneric::ConstParam(cp) => cp.default_val()?.syntax().clone(), - ConstOrTypeGeneric::TypeParam(tp) => tp.default_type()?.syntax().clone(), - } - .clone_for_update(), - ) + Some(match self { + ConstOrTypeGeneric::ConstArg(ca) => ca.expr()?.syntax().clone(), + ConstOrTypeGeneric::TypeArg(ta) => ta.syntax().clone(), + ConstOrTypeGeneric::ConstParam(cp) => cp.default_val()?.syntax().clone(), + ConstOrTypeGeneric::TypeParam(tp) => tp.default_type()?.syntax().clone(), + }) } } From 03663231436d404ea16373297643210baf576c35 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Fri, 3 Apr 2026 13:25:40 +0800 Subject: [PATCH 054/144] fix: Fix extract variable on arg with comma Example --- ```rust fn main() { let x = 2; foo( x + x, $0x - x,$0 ) } ``` **Before this PR** ```rust fn main() { let x = 2; let $0var_name = x + x; foo( var_name, x - x, ) } ``` **After this PR** ```rust fn main() { let x = 2; let $0var_name = x - x; foo( x + x, var_name, ) } ``` --- .../src/handlers/extract_variable.rs | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs index e5ce02cf5357a..1556339d8df43 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs @@ -283,13 +283,17 @@ fn peel_parens(mut expr: ast::Expr) -> ast::Expr { /// Check whether the node is a valid expression which can be extracted to a variable. /// In general that's true for any expression, but in some cases that would produce invalid code. fn valid_target_expr(ctx: &AssistContext<'_>) -> impl Fn(SyntaxNode) -> Option { - |node| match node.kind() { + let selection = ctx.selection_trimmed(); + move |node| match node.kind() { SyntaxKind::LOOP_EXPR | SyntaxKind::LET_EXPR => None, SyntaxKind::BREAK_EXPR => ast::BreakExpr::cast(node).and_then(|e| e.expr()), SyntaxKind::RETURN_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()), SyntaxKind::BLOCK_EXPR => { ast::BlockExpr::cast(node).filter(|it| it.is_standalone()).map(ast::Expr::from) } + SyntaxKind::ARG_LIST => ast::ArgList::cast(node)? + .args() + .find(|expr| crate::utils::is_selected(expr, selection, false)), SyntaxKind::PATH_EXPR => { let path_expr = ast::PathExpr::cast(node)?; let path_resolution = ctx.sema.resolve_path(&path_expr.path()?)?; @@ -1285,6 +1289,33 @@ fn main() { ); } + #[test] + fn extract_var_in_arglist_with_comma() { + check_assist_by_label( + extract_variable, + r#" +fn main() { + let x = 2; + foo( + x + x, + $0x - x,$0 + ) +} +"#, + r#" +fn main() { + let x = 2; + let $0var_name = x - x; + foo( + x + x, + var_name, + ) +} +"#, + "Extract into variable", + ); + } + #[test] fn extract_var_path_simple() { check_assist_by_label( From 4b42f62d1448b5a1b8e17164f0247c4e624569af Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Fri, 3 Apr 2026 13:38:57 +0800 Subject: [PATCH 055/144] fix: wrap parentheses on guard for replace_if_let_with_match Example --- ```rust fn main() { match$0 Some(0) { Some(n) if n % 2 == 0 || n == 7 => (), _ => (), } } ``` **Before this PR** ```rust fn main() { if let Some(n) = Some(0) && n % 2 == 0 || n == 7 { () } } ``` **After this PR** ```rust fn main() { if let Some(n) = Some(0) && (n % 2 == 0 || n == 7) { () } } ``` --- .../src/handlers/replace_if_let_with_match.rs | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_if_let_with_match.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_if_let_with_match.rs index 8ff30fce5b5d4..2bbce2b2c0800 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_if_let_with_match.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_if_let_with_match.rs @@ -13,7 +13,10 @@ use syntax::{ use crate::{ AssistContext, AssistId, Assists, - utils::{does_pat_match_variant, does_pat_variant_nested_or_literal, unwrap_trivial_block}, + utils::{ + does_pat_match_variant, does_pat_variant_nested_or_literal, unwrap_trivial_block, + wrap_paren, + }, }; // Assist: replace_if_let_with_match @@ -289,6 +292,7 @@ pub(crate) fn replace_match_with_if_let(acc: &mut Assists, ctx: &AssistContext<' _ => make.expr_let(if_let_pat, scrutinee).into(), }; let condition = if let Some(guard) = guard { + let guard = wrap_paren(guard, &make, ast::prec::ExprPrecedence::LAnd); make.expr_bin(condition, ast::BinaryOp::LogicOp(ast::LogicOp::And), guard).into() } else { condition @@ -2268,14 +2272,35 @@ fn main() { "#, r#" fn main() { - if let Some(n) = Some(0) && n % 2 == 0 && n != 6 { + if let Some(n) = Some(0) && (n % 2 == 0 && n != 6) { () } else { code() } } "#, - ) + ); + + check_assist( + replace_match_with_if_let, + r#" +fn main() { + match$0 Some(0) { + Some(n) if n % 2 == 0 || n == 7 => (), + _ => code(), + } +} +"#, + r#" +fn main() { + if let Some(n) = Some(0) && (n % 2 == 0 || n == 7) { + () + } else { + code() + } +} +"#, + ); } #[test] From 84614569c9fa40973c42549f389583edda384ada Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Fri, 3 Apr 2026 13:57:40 +0800 Subject: [PATCH 056/144] fix: Fix indent for convert_let_else_to_match Example --- ```rust mod indent { fn foo() { let Ok(x) = f() else$0 { log(); unreachable!( "..." ); }; } } ``` **Before this PR** ```rust mod indent { fn foo() { let x = match f() { Ok(x) => x, _ => { log(); unreachable!( "..." ); } }; } } ``` **After this PR** ```rust mod indent { fn foo() { let x = match f() { Ok(x) => x, _ => { log(); unreachable!( "..." ); } }; } } ``` --- .../src/handlers/convert_let_else_to_match.rs | 65 +++++++++++++++++-- 1 file changed, 61 insertions(+), 4 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_let_else_to_match.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_let_else_to_match.rs index d2336a4a5d8d0..5874f66522fd3 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_let_else_to_match.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_let_else_to_match.rs @@ -33,9 +33,9 @@ pub(crate) fn convert_let_else_to_match(acc: &mut Assists, ctx: &AssistContext<' let let_stmt = LetStmt::cast(let_stmt)?; let else_block = let_stmt.let_else()?.block_expr()?; let else_expr = if else_block.statements().next().is_none() { - else_block.tail_expr()? + else_block.tail_expr()?.reset_indent() } else { - else_block.into() + else_block.reset_indent().into() }; let init = let_stmt.initializer()?; // Ignore let stmt with type annotation @@ -91,8 +91,8 @@ pub(crate) fn convert_let_else_to_match(acc: &mut Assists, ctx: &AssistContext<' }, ); let else_arm = make.match_arm(make.wildcard_pat().into(), None, else_expr); - let match_ = make.expr_match(init, make.match_arm_list([binding_arm, else_arm])); - let match_ = match_.reset_indent(); + let arms = [binding_arm, else_arm].map(|arm| arm.indent(1.into())); + let match_ = make.expr_match(init, make.match_arm_list(arms)); let match_ = match_.indent(let_stmt.indent_level()); if bindings.is_empty() { @@ -298,6 +298,63 @@ fn main() { ); } + #[test] + fn convert_let_else_to_match_with_some_indent() { + check_assist( + convert_let_else_to_match, + r#" +mod indent { + fn main() { + let Ok(x) = f() else$0 { + log(); + unreachable!( + "..." + ); + }; + } +}"#, + r#" +mod indent { + fn main() { + let x = match f() { + Ok(x) => x, + _ => { + log(); + unreachable!( + "..." + ); + } + }; + } +}"#, + ); + + check_assist( + convert_let_else_to_match, + r#" +mod indent { + fn main() { + let Ok(x) = f() else$0 { + unreachable!( + "..." + ) + }; + } +}"#, + r#" +mod indent { + fn main() { + let x = match f() { + Ok(x) => x, + _ => unreachable!( + "..." + ), + }; + } +}"#, + ); + } + #[test] fn convert_let_else_to_match_const_ref() { check_assist( From ad5baa476da98ce8ff83d11bbd4e05c4fd3ec797 Mon Sep 17 00:00:00 2001 From: Amit Singhmar Date: Fri, 3 Apr 2026 07:26:47 +0000 Subject: [PATCH 057/144] refactor: remove TestAttr and pass --include-ignored to test runnables --- .../crates/ide/src/annotations.rs | 3 --- .../crates/ide/src/hover/tests.rs | 9 --------- .../rust-analyzer/crates/ide/src/runnables.rs | 18 +++--------------- .../crates/rust-analyzer/src/target_spec.rs | 6 ++---- .../rust-analyzer/tests/slow-tests/main.rs | 2 +- 5 files changed, 6 insertions(+), 32 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide/src/annotations.rs b/src/tools/rust-analyzer/crates/ide/src/annotations.rs index 107977cb119b6..21b2339c722c7 100644 --- a/src/tools/rust-analyzer/crates/ide/src/annotations.rs +++ b/src/tools/rust-analyzer/crates/ide/src/annotations.rs @@ -898,9 +898,6 @@ mod tests { test_id: Path( "tests::my_cool_test", ), - attr: TestAttr { - ignore: false, - }, }, cfg: None, update_test: UpdateTest { 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 7a758cd4c1399..56cf959fa06d2 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs @@ -3526,9 +3526,6 @@ fn foo_$0test() {} test_id: Path( "foo_test", ), - attr: TestAttr { - ignore: false, - }, }, cfg: None, update_test: UpdateTest { @@ -10707,9 +10704,6 @@ macro_rules! str { test_id: Path( "test", ), - attr: TestAttr { - ignore: false, - }, }, cfg: None, update_test: UpdateTest { @@ -10778,9 +10772,6 @@ pub use expect_test; test_id: Path( "test", ), - attr: TestAttr { - ignore: false, - }, }, cfg: None, update_test: UpdateTest { diff --git a/src/tools/rust-analyzer/crates/ide/src/runnables.rs b/src/tools/rust-analyzer/crates/ide/src/runnables.rs index a0a6a245592c6..7b8313b4cb7bd 100644 --- a/src/tools/rust-analyzer/crates/ide/src/runnables.rs +++ b/src/tools/rust-analyzer/crates/ide/src/runnables.rs @@ -3,7 +3,7 @@ use std::{fmt, sync::OnceLock}; use arrayvec::ArrayVec; use ast::HasName; use cfg::{CfgAtom, CfgExpr}; -use hir::{AsAssocItem, HasAttrs, HasCrate, HasSource, Semantics, Symbol, db::HirDatabase, sym}; +use hir::{AsAssocItem, HasAttrs, HasCrate, HasSource, Semantics, Symbol, sym}; use ide_assists::utils::{has_test_related_attribute, test_related_attribute_syn}; use ide_db::impl_empty_upmap_from_ra_fixture; use ide_db::{ @@ -55,7 +55,7 @@ impl fmt::Display for TestId { #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub enum RunnableKind { TestMod { path: String }, - Test { test_id: TestId, attr: TestAttr }, + Test { test_id: TestId }, Bench { test_id: TestId }, DocTest { test_id: TestId }, Bin, @@ -334,8 +334,7 @@ pub(crate) fn runnable_fn( }; if def.is_test(sema.db) { - let attr = TestAttr::from_fn(sema.db, def); - RunnableKind::Test { test_id: test_id(), attr } + RunnableKind::Test { test_id: test_id() } } else if def.is_bench(sema.db) { RunnableKind::Bench { test_id: test_id() } } else { @@ -558,17 +557,6 @@ fn module_def_doctest(sema: &Semantics<'_, RootDatabase>, def: Definition) -> Op Some(res) } -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -pub struct TestAttr { - pub ignore: bool, -} - -impl TestAttr { - fn from_fn(db: &dyn HirDatabase, fn_def: hir::Function) -> TestAttr { - TestAttr { ignore: fn_def.is_ignore(db) } - } -} - fn has_runnable_doc_test(db: &RootDatabase, attrs: &hir::AttrsWithOwner) -> bool { const RUSTDOC_FENCES: [&str; 2] = ["```", "~~~"]; const RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUNNABLE: &[&str] = diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs index 8be061cacfa89..c1d52e4c9b40b 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs @@ -274,15 +274,13 @@ impl CargoTargetSpec { let mut executable_args = Vec::new(); match kind { - RunnableKind::Test { test_id, attr } => { + RunnableKind::Test { test_id } => { executable_args.push(test_id.to_string()); if let TestId::Path(_) = test_id { executable_args.push("--exact".to_owned()); } executable_args.extend(extra_test_binary_args); - if attr.ignore { - executable_args.push("--ignored".to_owned()); - } + executable_args.push("--include-ignored".to_owned()); } RunnableKind::TestMod { path } => { executable_args.push(path.clone()); diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs index fcdc8bb7cdd0d..3c57e36b4fe9f 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs @@ -262,7 +262,7 @@ fn main() {} { "args": { "cargoArgs": ["test", "--package", "foo", "--test", "spam"], - "executableArgs": ["test_eggs", "--exact", "--nocapture"], + "executableArgs": ["test_eggs", "--exact", "--nocapture", "--include-ignored"], "overrideCargo": null, "cwd": server.path().join("foo"), "workspaceRoot": server.path().join("foo") From b2c6beb7e0de107cc61c75c5ba3b92764a6dac0c Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Fri, 3 Apr 2026 15:17:50 +0800 Subject: [PATCH 058/144] Add ArgList::args_maybe_empty to node_ext --- .../crates/syntax/src/ast/node_ext.rs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs index 63e4608d0f630..3fc3b39feef08 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs @@ -77,6 +77,15 @@ fn text_of_first_token(node: &SyntaxNode) -> TokenText<'_> { } } +fn into_comma(it: NodeOrToken) -> Option { + let token = match it { + NodeOrToken::Token(it) => it, + NodeOrToken::Node(node) if node.kind() == SyntaxKind::ERROR => node.first_token()?, + NodeOrToken::Node(_) => return None, + }; + (token.kind() == T![,]).then_some(token) +} + impl ast::Abi { pub fn abi_string(&self) -> Option { support::token(&self.syntax, SyntaxKind::STRING).and_then(ast::String::cast) @@ -1037,6 +1046,21 @@ impl ast::GenericParamList { } } +impl ast::ArgList { + /// Comma separated args, argument may be empty + pub fn args_maybe_empty(&self) -> impl Iterator> { + // (Expr? ','?)* + let mut after_arg = false; + self.syntax().children_with_tokens().filter_map(move |it| { + if into_comma(it.clone()).is_some() { + if std::mem::take(&mut after_arg) { None } else { Some(None) } + } else { + Some(ast::Expr::cast(it.into_node()?).inspect(|_| after_arg = true)) + } + }) + } +} + impl ast::ForExpr { pub fn iterable(&self) -> Option { // If the iterable is a BlockExpr, check if the body is missing. From fcd9c36f3bd88748d760b893cd0fd00c8a199f3a Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Wed, 1 Apr 2026 11:09:56 +0800 Subject: [PATCH 059/144] fix: Fix param inlayHints on empty expr and comma Example --- ```rust pub fn test(a: i32, b: i32, c: i32) {} fn main() { test(, 2,); test(, , 3); } ``` **Before this PR** ```rust test(, 2,); //^ a test(, , 3); //^ a ``` **After this PR** ```rust test(, 2,); //^ b test(, , 3); //^ c ``` --- .../crates/ide/src/inlay_hints/param_name.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/param_name.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/param_name.rs index 08588bbed0901..8dddf9d37e4fb 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/param_name.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/param_name.rs @@ -37,8 +37,9 @@ pub(super) fn hints( let hints = callable .params() .into_iter() - .zip(arg_list.args()) + .zip(arg_list.args_maybe_empty()) .filter_map(|(p, arg)| { + let arg = arg?; // Only annotate hints for expressions that exist in the original file let range = sema.original_range_opt(arg.syntax())?; if range.file_id != file_id { @@ -561,6 +562,19 @@ fn main() { ) } + #[test] + fn param_name_hints_show_after_empty_arg() { + check_params( + r#"pub fn test(a: i32, b: i32, c: i32) {} +fn main() { + test(, 2,); + //^ b + test(, , 3); + //^ c +}"#, + ) + } + #[test] fn function_call_parameter_hint() { check_params( From f3a108a26f04133046975848e8d6a41a79654d17 Mon Sep 17 00:00:00 2001 From: so1ve Date: Fri, 3 Apr 2026 15:36:06 +0800 Subject: [PATCH 060/144] test: update hover test to include multiple lines of documentation --- src/tools/rust-analyzer/crates/ide/src/hover/tests.rs | 2 ++ 1 file changed, 2 insertions(+) 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 a57db032db2c9..d979269507168 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs @@ -11533,6 +11533,7 @@ struct Ba$0r; //- /docs.md Included docs from file. +Multiple lines of docs. "#, expect![[r#" *Bar* @@ -11552,6 +11553,7 @@ Included docs from file. --- Included docs from file. + Multiple lines of docs. "#]], ); } From 77d9613440fc12c2f4ae9a31100d0e05f040c2a7 Mon Sep 17 00:00:00 2001 From: Amit Singhmar Date: Fri, 3 Apr 2026 09:20:24 +0000 Subject: [PATCH 061/144] fix: support multiple snippet placeholders in VS Code extension --- src/tools/rust-analyzer/editors/code/src/snippets.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/editors/code/src/snippets.ts b/src/tools/rust-analyzer/editors/code/src/snippets.ts index a469a9cd1f45c..6d75428eaa3d4 100644 --- a/src/tools/rust-analyzer/editors/code/src/snippets.ts +++ b/src/tools/rust-analyzer/editors/code/src/snippets.ts @@ -53,7 +53,7 @@ export async function applySnippetTextEdits(editor: vscode.TextEditor, edits: vs } function hasSnippet(snip: string): boolean { - const m = snip.match(/\$\d+|\{\d+:[^}]*\}/); + const m = snip.match(/\$\d+|\$\{\d+:[^}]*\}/); return m != null; } From 1e90d90158b54688bf59abb11fecc21afadaa2b1 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 3 Apr 2026 16:33:48 +0530 Subject: [PATCH 062/144] update rowan from 0.15.17 to 0.15.18 --- src/tools/rust-analyzer/Cargo.lock | 4 ++-- src/tools/rust-analyzer/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tools/rust-analyzer/Cargo.lock b/src/tools/rust-analyzer/Cargo.lock index 5370127ddc8ed..3b354afe826b4 100644 --- a/src/tools/rust-analyzer/Cargo.lock +++ b/src/tools/rust-analyzer/Cargo.lock @@ -2283,9 +2283,9 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "rowan" -version = "0.15.17" +version = "0.15.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4f1e4a001f863f41ea8d0e6a0c34b356d5b733db50dadab3efef640bafb779b" +checksum = "62f509095fc8cc0c8c8564016771d458079c11a8d857e65861f045145c0d3208" dependencies = [ "countme", "hashbrown 0.14.5", diff --git a/src/tools/rust-analyzer/Cargo.toml b/src/tools/rust-analyzer/Cargo.toml index 9f31e1903af59..3b3929df0dfbd 100644 --- a/src/tools/rust-analyzer/Cargo.toml +++ b/src/tools/rust-analyzer/Cargo.toml @@ -132,7 +132,7 @@ process-wrap = { version = "8.2.1", features = ["std"] } pulldown-cmark-to-cmark = "10.0.4" pulldown-cmark = { version = "0.9.6", default-features = false } rayon = "1.10.0" -rowan = "=0.15.17" +rowan = "=0.15.18" # Ideally we'd not enable the macros feature but unfortunately the `tracked` attribute does not work # on impls without it salsa = { version = "0.25.2", default-features = false, features = [ From acb162e15894648da4b26e4afa708eb0e1b26233 Mon Sep 17 00:00:00 2001 From: Amit Singhmar Date: Fri, 3 Apr 2026 11:05:46 +0000 Subject: [PATCH 063/144] fix: silence type mismatch diagnostic when type is unknown --- .../src/handlers/type_mismatch.rs | 58 ++++++++++++++----- .../crates/ide-diagnostics/src/lib.rs | 5 +- 2 files changed, 47 insertions(+), 16 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs index f443dc08f5fd2..4b653709f06c8 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs @@ -20,7 +20,14 @@ use crate::{Assist, Diagnostic, DiagnosticCode, DiagnosticsContext, adjusted_dis // // This diagnostic is triggered when the type of an expression or pattern does not match // the expected type. -pub(crate) fn type_mismatch(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch<'_>) -> Diagnostic { +pub(crate) fn type_mismatch( + ctx: &DiagnosticsContext<'_>, + d: &hir::TypeMismatch<'_>, +) -> Option { + if d.expected.is_unknown() || d.actual.is_unknown() { + return None; + } + let display_range = adjusted_display_range(ctx, d.expr_or_pat, &|node| { let Either::Left(expr) = node else { return None }; let salient_token_range = match expr { @@ -39,21 +46,23 @@ pub(crate) fn type_mismatch(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch< cov_mark::hit!(type_mismatch_range_adjustment); Some(salient_token_range) }); - Diagnostic::new( - DiagnosticCode::RustcHardError("E0308"), - format!( - "expected {}, found {}", - d.expected - .display(ctx.sema.db, ctx.display_target) - .with_closure_style(ClosureStyle::ClosureWithId), - d.actual - .display(ctx.sema.db, ctx.display_target) - .with_closure_style(ClosureStyle::ClosureWithId), - ), - display_range, + Some( + Diagnostic::new( + DiagnosticCode::RustcHardError("E0308"), + format!( + "expected {}, found {}", + d.expected + .display(ctx.sema.db, ctx.display_target) + .with_closure_style(ClosureStyle::ClosureWithId), + d.actual + .display(ctx.sema.db, ctx.display_target) + .with_closure_style(ClosureStyle::ClosureWithId), + ), + display_range, + ) + .stable() + .with_fixes(fixes(ctx, d)), ) - .stable() - .with_fixes(fixes(ctx, d)) } fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch<'_>) -> Option> { @@ -1250,6 +1259,25 @@ fn main() { let E::V() = &S {}; // ^^^^^^ error: expected S, found E } +"#, + ); + } + + #[test] + fn test_ignore_unknown_mismatch() { + check_diagnostics( + r#" +pub trait Foo { + type Out; +} +impl Foo for [i32; 1] { + type Out = (); +} +pub fn foo(_: T) -> (T::Out,) { loop { } } + +fn main() { + let _x = foo(2); +} "#, ); } 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 0c6953419f7d5..a74a52ceec123 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs @@ -430,7 +430,10 @@ pub fn semantic_diagnostics( AnyDiagnostic::TraitImplRedundantAssocItems(d) => handlers::trait_impl_redundant_assoc_item::trait_impl_redundant_assoc_item(&ctx, &d), AnyDiagnostic::TraitImplOrphan(d) => handlers::trait_impl_orphan::trait_impl_orphan(&ctx, &d), AnyDiagnostic::TypedHole(d) => handlers::typed_hole::typed_hole(&ctx, &d), - AnyDiagnostic::TypeMismatch(d) => handlers::type_mismatch::type_mismatch(&ctx, &d), + AnyDiagnostic::TypeMismatch(d) => match handlers::type_mismatch::type_mismatch(&ctx, &d) { + Some(diag) => diag, + None => continue, + }, AnyDiagnostic::UndeclaredLabel(d) => handlers::undeclared_label::undeclared_label(&ctx, &d), AnyDiagnostic::UnimplementedBuiltinMacro(d) => handlers::unimplemented_builtin_macro::unimplemented_builtin_macro(&ctx, &d), AnyDiagnostic::UnreachableLabel(d) => handlers::unreachable_label::unreachable_label(&ctx, &d), From 9f5def0f236bf04dc348d631ca2af385cf6e6c55 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 3 Apr 2026 16:40:54 +0530 Subject: [PATCH 064/144] use is_mutable to check and update node accordingly --- .../crates/syntax/src/syntax_editor/edit_algo.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edit_algo.rs b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edit_algo.rs index 7bd1d7c755f10..f6bd992f23e34 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edit_algo.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edit_algo.rs @@ -211,7 +211,7 @@ pub(super) fn apply_edits(editor: SyntaxEditor) -> SyntaxEdit { | Change::Replace(_, Some(SyntaxElement::Node(node))) => { if node.parent().is_some() { *node = node.clone_subtree().clone_for_update(); - } else if *node == tree_mutator.immutable { + } else if !node.is_mutable() { *node = node.clone_for_update(); } } @@ -222,7 +222,7 @@ pub(super) fn apply_edits(editor: SyntaxEditor) -> SyntaxEdit { if let SyntaxElement::Node(node) = element { if node.parent().is_some() { *node = node.clone_subtree().clone_for_update(); - } else if *node == tree_mutator.immutable { + } else if !node.is_mutable() { *node = node.clone_for_update(); } } From 256ea68c4b8123d8c58f49f98f796a1f448b5d03 Mon Sep 17 00:00:00 2001 From: so1ve Date: Fri, 3 Apr 2026 19:18:10 +0800 Subject: [PATCH 065/144] fix: adjust review comments --- .../rust-analyzer/crates/hir-def/src/attrs.rs | 772 +----------------- .../crates/hir-def/src/attrs/docs.rs | 767 +++++++++++++++++ .../crates/ide/src/hover/tests.rs | 76 ++ 3 files changed, 880 insertions(+), 735 deletions(-) create mode 100644 src/tools/rust-analyzer/crates/hir-def/src/attrs/docs.rs diff --git a/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs b/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs index a5bc283330526..ddcfea2c219d4 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs @@ -12,27 +12,17 @@ //! its value. This way, queries are only called on items that have the attribute, which is //! usually only a few. //! -//! An exception to this model that is also defined in this module is documentation (doc -//! comments and `#[doc = "..."]` attributes). But it also has a more compact form than -//! the attribute: a concatenated string of the full docs as well as a source map -//! to map it back to AST (which is needed for things like resolving links in doc comments -//! and highlight injection). The lowering and upmapping of doc comments is a bit complicated, -//! but it is encapsulated in the [`Docs`] struct. - -use std::{ - convert::Infallible, - iter::Peekable, - ops::{ControlFlow, Range}, -}; +//! Documentation (doc comments and `#[doc = "..."]` attributes) is handled by the [`docs`] +//! submodule. + +use std::{convert::Infallible, iter::Peekable, ops::ControlFlow}; use base_db::Crate; use cfg::{CfgExpr, CfgOptions}; use either::Either; use hir_expand::{ - AstId, ExpandTo, HirFileId, InFile, Lookup, - attrs::{Meta, expand_cfg_attr, expand_cfg_attr_with_doc_comments}, - mod_path::ModPath, - span_map::SpanMap, + InFile, Lookup, + attrs::{Meta, expand_cfg_attr}, }; use intern::Symbol; use itertools::Itertools; @@ -40,24 +30,26 @@ use la_arena::ArenaMap; use rustc_abi::ReprOptions; use rustc_hash::FxHashSet; use smallvec::SmallVec; -use span::AstIdMap; use syntax::{ - AstNode, AstToken, NodeOrToken, SmolStr, SourceFile, SyntaxNode, SyntaxToken, T, - ast::{self, AttrDocCommentIter, HasAttrs, IsString, TokenTreeChildren}, + AstNode, AstToken, NodeOrToken, SmolStr, SourceFile, T, + ast::{self, HasAttrs, TokenTreeChildren}, }; -use tt::{TextRange, TextSize}; +use tt::TextSize; use crate::{ AdtId, AstIdLoc, AttrDefId, FieldId, FunctionId, GenericDefId, HasModule, LifetimeParamId, LocalFieldId, MacroId, ModuleId, TypeOrConstParamId, VariantId, db::DefDatabase, hir::generics::{GenericParams, LocalLifetimeParamId, LocalTypeOrConstParamId}, - macro_call_as_call_id, - nameres::{MacroSubNs, ModuleOrigin, crate_def_map}, + nameres::ModuleOrigin, resolver::{HasResolver, Resolver}, src::{HasChildSource, HasSource}, }; +pub mod docs; + +pub use self::docs::{Docs, IsInnerDoc}; + #[inline] fn attrs_from_ast_id_loc>( db: &dyn DefDatabase, @@ -502,322 +494,12 @@ pub struct RustcLayoutScalarValidRange { pub end: Option, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -struct DocsSourceMapLine { - /// The offset in [`Docs::docs`]. - string_offset: TextSize, - /// The offset in the AST of the text. `None` for macro-expanded doc strings - /// where we cannot provide a faithful source mapping. - ast_offset: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Docs { - /// The concatenated string of all `#[doc = "..."]` attributes and documentation comments. - docs: String, - /// A sorted map from an offset in `docs` to an offset in the source code. - docs_source_map: Vec, - /// If the item is an outlined module (`mod foo;`), `docs_source_map` store the concatenated - /// list of the outline and inline docs (outline first). Then, this field contains the [`HirFileId`] - /// of the outline declaration, and the index in `docs` from which the inline docs - /// begin. - outline_mod: Option<(HirFileId, usize)>, - inline_file: HirFileId, - /// The size the prepended prefix, which does not map to real doc comments. - prefix_len: TextSize, - /// The offset in `docs` from which the docs are inner attributes/comments. - inline_inner_docs_start: Option, - /// Like `inline_inner_docs_start`, but for `outline_mod`. This can happen only when merging `Docs` - /// (as outline modules don't have inner attributes). - outline_inner_docs_start: Option, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum IsInnerDoc { - No, - Yes, -} - -impl IsInnerDoc { - #[inline] - pub fn yes(self) -> bool { - self == IsInnerDoc::Yes - } -} - -impl Docs { - #[inline] - pub fn docs(&self) -> &str { - &self.docs - } - - #[inline] - pub fn into_docs(self) -> String { - self.docs - } - - pub fn find_ast_range( - &self, - mut string_range: TextRange, - ) -> Option<(InFile, IsInnerDoc)> { - if string_range.start() < self.prefix_len { - return None; - } - string_range -= self.prefix_len; - - let mut file = self.inline_file; - let mut inner_docs_start = self.inline_inner_docs_start; - // Check whether the range is from the outline, the inline, or both. - let source_map = if let Some((outline_mod_file, outline_mod_end)) = self.outline_mod { - if let Some(first_inline) = self.docs_source_map.get(outline_mod_end) { - if string_range.end() <= first_inline.string_offset { - // The range is completely in the outline. - file = outline_mod_file; - inner_docs_start = self.outline_inner_docs_start; - &self.docs_source_map[..outline_mod_end] - } else if string_range.start() >= first_inline.string_offset { - // The range is completely in the inline. - &self.docs_source_map[outline_mod_end..] - } else { - // The range is combined from the outline and the inline - cannot map it back. - return None; - } - } else { - // There is no inline. - file = outline_mod_file; - inner_docs_start = self.outline_inner_docs_start; - &self.docs_source_map - } - } else { - // There is no outline. - &self.docs_source_map - }; - - let after_range = - source_map.partition_point(|line| line.string_offset <= string_range.start()) - 1; - let after_range = &source_map[after_range..]; - let line = after_range.first()?; - // Unmapped lines (from macro-expanded docs) cannot be mapped back to AST. - let ast_offset = line.ast_offset?; - if after_range.get(1).is_some_and(|next_line| next_line.string_offset < string_range.end()) - { - // The range is combined from two lines - cannot map it back. - return None; - } - let ast_range = string_range - line.string_offset + ast_offset; - let is_inner = if inner_docs_start - .is_some_and(|inner_docs_start| string_range.start() >= inner_docs_start) - { - IsInnerDoc::Yes - } else { - IsInnerDoc::No - }; - Some((InFile::new(file, ast_range), is_inner)) - } - - #[inline] - pub fn shift_by(&mut self, offset: TextSize) { - self.prefix_len += offset; - } - - pub fn prepend_str(&mut self, s: &str) { - self.prefix_len += TextSize::of(s); - self.docs.insert_str(0, s); - } - - pub fn append_str(&mut self, s: &str) { - self.docs.push_str(s); - } - - pub fn append(&mut self, other: &Docs) { - let other_offset = TextSize::of(&self.docs); - - assert!( - self.outline_mod.is_none() && other.outline_mod.is_none(), - "cannot merge `Docs` that have `outline_mod` set" - ); - self.outline_mod = Some((self.inline_file, self.docs_source_map.len())); - self.inline_file = other.inline_file; - self.outline_inner_docs_start = self.inline_inner_docs_start; - self.inline_inner_docs_start = other.inline_inner_docs_start.map(|it| it + other_offset); - - self.docs.push_str(&other.docs); - self.docs_source_map.extend(other.docs_source_map.iter().map( - |&DocsSourceMapLine { string_offset, ast_offset }| DocsSourceMapLine { - ast_offset, - string_offset: string_offset + other_offset, - }, - )); - } - - fn extend_with_doc_comment(&mut self, comment: ast::Comment, indent: &mut usize) { - let Some((doc, offset)) = comment.doc_comment() else { return }; - self.extend_with_doc_str(doc, comment.syntax().text_range().start() + offset, indent); - } - - fn extend_with_doc_attr(&mut self, value: SyntaxToken, indent: &mut usize) { - let Some(value) = ast::String::cast(value) else { return }; - let Some(value_offset) = value.text_range_between_quotes() else { return }; - let value_offset = value_offset.start(); - let Ok(value) = value.value() else { return }; - // FIXME: Handle source maps for escaped text. - self.extend_with_doc_str(&value, value_offset, indent); - } - - fn extend_with_doc_str(&mut self, doc: &str, mut offset_in_ast: TextSize, indent: &mut usize) { - for line in doc.split('\n') { - self.docs_source_map.push(DocsSourceMapLine { - string_offset: TextSize::of(&self.docs), - ast_offset: Some(offset_in_ast), - }); - offset_in_ast += TextSize::of(line) + TextSize::of("\n"); - - let line = line.trim_end(); - if let Some(line_indent) = line.chars().position(|ch| !ch.is_whitespace()) { - // Empty lines are handled because `position()` returns `None` for them. - *indent = std::cmp::min(*indent, line_indent); - } - self.docs.push_str(line); - self.docs.push('\n'); - } - } - - fn extend_with_unmapped_doc_str(&mut self, doc: &str, indent: &mut usize) { - for line in doc.split('\n') { - self.docs_source_map.push(DocsSourceMapLine { - string_offset: TextSize::of(&self.docs), - ast_offset: None, - }); - let line = line.trim_end(); - if let Some(line_indent) = line.chars().position(|ch| !ch.is_whitespace()) { - *indent = std::cmp::min(*indent, line_indent); - } - self.docs.push_str(line); - self.docs.push('\n'); - } - } - - fn remove_indent(&mut self, indent: usize, start_source_map_index: usize) { - /// In case of panics, we want to avoid corrupted UTF-8 in `self.docs`, so we clear it. - struct Guard<'a>(&'a mut Docs); - impl Drop for Guard<'_> { - fn drop(&mut self) { - let Docs { - docs, - docs_source_map, - outline_mod, - inline_file: _, - prefix_len: _, - inline_inner_docs_start: _, - outline_inner_docs_start: _, - } = self.0; - // Don't use `String::clear()` here because it's not guaranteed to not do UTF-8-dependent things, - // and we may have temporarily broken the string's encoding. - unsafe { docs.as_mut_vec() }.clear(); - // This is just to avoid panics down the road. - docs_source_map.clear(); - *outline_mod = None; - } - } - - if self.docs.is_empty() { - return; - } - - let guard = Guard(self); - let source_map = &mut guard.0.docs_source_map[start_source_map_index..]; - let Some(&DocsSourceMapLine { string_offset: mut copy_into, .. }) = source_map.first() - else { - return; - }; - // We basically want to remove multiple ranges from a string. Doing this efficiently (without O(N^2) - // or allocations) requires unsafe. Basically, for each line, we copy the line minus the indent into - // consecutive to the previous line (which may have moved). Then at the end we truncate. - let mut accumulated_offset = TextSize::new(0); - for idx in 0..source_map.len() { - let string_end_offset = source_map - .get(idx + 1) - .map_or_else(|| TextSize::of(&guard.0.docs), |next_attr| next_attr.string_offset); - let line_source = &mut source_map[idx]; - let line_docs = - &guard.0.docs[TextRange::new(line_source.string_offset, string_end_offset)]; - let line_docs_len = TextSize::of(line_docs); - let indent_size = line_docs.char_indices().nth(indent).map_or_else( - || TextSize::of(line_docs) - TextSize::of("\n"), - |(offset, _)| TextSize::new(offset as u32), - ); - unsafe { guard.0.docs.as_bytes_mut() }.copy_within( - Range::::from(TextRange::new( - line_source.string_offset + indent_size, - string_end_offset, - )), - copy_into.into(), - ); - copy_into += line_docs_len - indent_size; - - if let Some(inner_attrs_start) = &mut guard.0.inline_inner_docs_start - && *inner_attrs_start == line_source.string_offset - { - *inner_attrs_start -= accumulated_offset; - } - // The removals in the string accumulate, but in the AST not, because it already points - // to the beginning of each attribute. - // Also, we need to shift the AST offset of every line, but the string offset of the first - // line should not get shifted (in general, the shift for the string offset is by the - // number of lines until the current one, excluding the current one). - line_source.string_offset -= accumulated_offset; - if let Some(ref mut ast_offset) = line_source.ast_offset { - *ast_offset += indent_size; - } - - accumulated_offset += indent_size; - } - // Don't use `String::truncate()` here because it's not guaranteed to not do UTF-8-dependent things, - // and we may have temporarily broken the string's encoding. - unsafe { guard.0.docs.as_mut_vec() }.truncate(copy_into.into()); - - std::mem::forget(guard); - } - - fn remove_last_newline(&mut self) { - self.docs.truncate(self.docs.len().saturating_sub(1)); - } - - fn shrink_to_fit(&mut self) { - let Docs { - docs, - docs_source_map, - outline_mod: _, - inline_file: _, - prefix_len: _, - inline_inner_docs_start: _, - outline_inner_docs_start: _, - } = self; - docs.shrink_to_fit(); - docs_source_map.shrink_to_fit(); - } -} - #[derive(Debug, PartialEq, Eq, Hash)] pub struct DeriveInfo { pub trait_name: Symbol, pub helpers: Box<[Symbol]>, } -struct DocMacroExpander<'db> { - db: &'db dyn DefDatabase, - krate: Crate, - recursion_depth: usize, - recursion_limit: usize, -} - -struct DocExprSourceCtx<'db> { - resolver: Resolver<'db>, - file_id: HirFileId, - ast_id_map: &'db AstIdMap, - span_map: SpanMap, -} - fn extract_doc_aliases(result: &mut Vec, attr: Meta) -> ControlFlow { if let Meta::TokenTree { path, tt } = attr && path.is1("doc") @@ -846,217 +528,6 @@ fn extract_cfgs(result: &mut Vec, attr: Meta) -> ControlFlow( - expander: &mut DocMacroExpander<'db>, - source_ctx: &DocExprSourceCtx<'db>, - expr: ast::Expr, -) -> Option { - match expr { - ast::Expr::ParenExpr(paren_expr) => { - expand_doc_expr_via_macro_pipeline(expander, source_ctx, paren_expr.expr()?) - } - ast::Expr::Literal(literal) => match literal.kind() { - ast::LiteralKind::String(string) => string.value().ok().map(Into::into), - _ => None, - }, - ast::Expr::MacroExpr(macro_expr) => { - let macro_call = macro_expr.macro_call()?; - let (expr, new_source_ctx) = expand_doc_macro_call(expander, source_ctx, macro_call)?; - // After expansion, the expr lives in the expansion file; use its source context. - expand_doc_expr_via_macro_pipeline(expander, &new_source_ctx, expr) - } - _ => None, - } -} - -fn expand_doc_macro_call<'db>( - expander: &mut DocMacroExpander<'db>, - source_ctx: &DocExprSourceCtx<'db>, - macro_call: ast::MacroCall, -) -> Option<(ast::Expr, DocExprSourceCtx<'db>)> { - if expander.recursion_depth >= expander.recursion_limit { - return None; - } - - let path = macro_call.path()?; - let mod_path = ModPath::from_src(expander.db, path, &mut |range| { - source_ctx.span_map.span_for_range(range).ctx - })?; - let call_site = source_ctx.span_map.span_for_range(macro_call.syntax().text_range()); - let ast_id = AstId::new(source_ctx.file_id, source_ctx.ast_id_map.ast_id(¯o_call)); - let call_id = macro_call_as_call_id( - expander.db, - ast_id, - &mod_path, - call_site.ctx, - ExpandTo::Expr, - expander.krate, - |path| { - source_ctx.resolver.resolve_path_as_macro_def(expander.db, path, Some(MacroSubNs::Bang)) - }, - &mut |_, _| (), - ) - .ok()? - .value?; - - expander.recursion_depth += 1; - let parse = expander.db.parse_macro_expansion(call_id).value.0; - let expr = parse.cast::().map(|parse| parse.tree())?; - expander.recursion_depth -= 1; - - // Build a new source context for the expansion file so that any further - // recursive expansion (e.g. a user macro expanding to `concat!(...)`) - // correctly resolves AstIds and spans in the expansion. - let expansion_file_id: HirFileId = call_id.into(); - let new_source_ctx = DocExprSourceCtx { - resolver: source_ctx.resolver.clone(), - file_id: expansion_file_id, - ast_id_map: expander.db.ast_id_map(expansion_file_id), - span_map: expander.db.span_map(expansion_file_id), - }; - Some((expr, new_source_ctx)) -} - -fn extend_with_attrs<'a, 'db>( - result: &mut Docs, - node: &SyntaxNode, - expect_inner_attrs: bool, - indent: &mut usize, - get_cfg_options: &dyn Fn() -> &'a CfgOptions, - cfg_options: &mut Option<&'a CfgOptions>, - mut expander: Option<&mut DocMacroExpander<'db>>, - source_ctx: Option<&DocExprSourceCtx<'db>>, -) { - expand_cfg_attr_with_doc_comments::<_, Infallible>( - AttrDocCommentIter::from_syntax_node(node).filter(|attr| match attr { - Either::Left(attr) => attr.kind().is_inner() == expect_inner_attrs, - Either::Right(comment) => comment - .kind() - .doc - .is_some_and(|kind| (kind == ast::CommentPlacement::Inner) == expect_inner_attrs), - }), - || *cfg_options.get_or_insert_with(get_cfg_options), - |attr| { - match attr { - Either::Right(doc_comment) => result.extend_with_doc_comment(doc_comment, indent), - Either::Left((attr, _, _, top_attr)) => match attr { - Meta::NamedKeyValue { name: Some(name), value: Some(value), .. } - if name.text() == "doc" => - { - result.extend_with_doc_attr(value, indent); - } - Meta::NamedKeyValue { name: Some(name), value: None, .. } - if name.text() == "doc" => - { - if let (Some(expander), Some(source_ctx)) = - (expander.as_deref_mut(), source_ctx) - && let Some(expr) = top_attr.expr() - && let Some(expanded) = - expand_doc_expr_via_macro_pipeline(expander, source_ctx, expr) - { - result.extend_with_unmapped_doc_str(&expanded, indent); - } - } - _ => {} - }, - } - ControlFlow::Continue(()) - }, - ); -} - -fn extract_docs<'a, 'db>( - mut expander: Option<&mut DocMacroExpander<'db>>, - resolver: Option<&Resolver<'db>>, - get_cfg_options: &dyn Fn() -> &'a CfgOptions, - source: InFile, - outer_mod_decl: Option>, - inner_attrs_node: Option, -) -> Option> { - let mut result = Docs { - docs: String::new(), - docs_source_map: Vec::new(), - outline_mod: None, - inline_file: source.file_id, - prefix_len: TextSize::new(0), - inline_inner_docs_start: None, - outline_inner_docs_start: None, - }; - - let mut cfg_options = None; - - if let Some(outer_mod_decl) = outer_mod_decl { - let mut indent = usize::MAX; - let outer_source_ctx = - if let (Some(expander), Some(resolver)) = (expander.as_deref(), resolver) { - Some(DocExprSourceCtx { - resolver: resolver.clone(), - file_id: outer_mod_decl.file_id, - ast_id_map: expander.db.ast_id_map(outer_mod_decl.file_id), - span_map: expander.db.span_map(outer_mod_decl.file_id), - }) - } else { - None - }; - extend_with_attrs( - &mut result, - outer_mod_decl.value.syntax(), - false, - &mut indent, - get_cfg_options, - &mut cfg_options, - expander.as_deref_mut(), - outer_source_ctx.as_ref(), - ); - result.remove_indent(indent, 0); - result.outline_mod = Some((outer_mod_decl.file_id, result.docs_source_map.len())); - } - - let inline_source_map_start = result.docs_source_map.len(); - let mut indent = usize::MAX; - let inline_source_ctx = - if let (Some(expander), Some(resolver)) = (expander.as_deref(), resolver) { - Some(DocExprSourceCtx { - resolver: resolver.clone(), - file_id: source.file_id, - ast_id_map: expander.db.ast_id_map(source.file_id), - span_map: expander.db.span_map(source.file_id), - }) - } else { - None - }; - extend_with_attrs( - &mut result, - source.value.syntax(), - false, - &mut indent, - get_cfg_options, - &mut cfg_options, - expander.as_deref_mut(), - inline_source_ctx.as_ref(), - ); - if let Some(inner_attrs_node) = &inner_attrs_node { - result.inline_inner_docs_start = Some(TextSize::of(&result.docs)); - extend_with_attrs( - &mut result, - inner_attrs_node, - true, - &mut indent, - get_cfg_options, - &mut cfg_options, - expander, - inline_source_ctx.as_ref(), - ); - } - result.remove_indent(indent, inline_source_map_start); - - result.remove_last_newline(); - - result.shrink_to_fit(); - - if result.docs.is_empty() { None } else { Some(Box::new(result)) } -} - #[salsa::tracked] impl AttrFlags { #[salsa::tracked] @@ -1494,20 +965,25 @@ impl AttrFlags { pub fn docs(db: &dyn DefDatabase, owner: AttrDefId) -> Option> { let (source, outer_mod_decl, _extra_crate_attrs, krate) = attrs_source(db, owner); let inner_attrs_node = source.value.inner_attributes_node(); - let resolver = resolver_for_attr_def_id(db, owner); - let def_map = crate_def_map(db, krate); - let recursion_limit = if cfg!(test) { - std::cmp::min(32, def_map.recursion_limit() as usize) + let outer_resolver = if outer_mod_decl.is_some() { + if let AttrDefId::ModuleId(module_id) = owner { + module_id + .containing_module(db) + .map(|parent| move || -> Resolver<'_> { parent.resolver(db) }) + } else { + None + } } else { - def_map.recursion_limit() as usize + None }; - let mut expander = DocMacroExpander { db, krate, recursion_depth: 0, recursion_limit }; // Note: we don't have to pass down `_extra_crate_attrs` here, since `extract_docs` // does not handle crate-level attributes related to docs. // See: https://doc.rust-lang.org/rustdoc/write-documentation/the-doc-attribute.html#at-the-crate-level - extract_docs( - Some(&mut expander), - Some(&resolver), + self::docs::extract_docs( + db, + krate, + outer_resolver, + || resolver_for_attr_def_id(db, owner), &|| krate.cfg_options(db), source, outer_mod_decl, @@ -1526,19 +1002,15 @@ impl AttrFlags { variant: VariantId, ) -> ArenaMap>> { let krate = variant.module(db).krate(db); - let resolver = variant.resolver(db); - let def_map = crate_def_map(db, krate); - let recursion_limit = if cfg!(test) { - std::cmp::min(32, def_map.recursion_limit() as usize) - } else { - def_map.recursion_limit() as usize - }; collect_field_attrs(db, variant, |cfg_options, field| { - let mut expander = - DocMacroExpander { db, krate, recursion_depth: 0, recursion_limit }; - extract_docs( - Some(&mut expander), - Some(&resolver), + fn none_resolver<'db>() -> Option Resolver<'db>> { + None + } + self::docs::extract_docs( + db, + krate, + none_resolver(), + || variant.resolver(db), &|| cfg_options, field, None, @@ -1771,182 +1243,12 @@ fn next_doc_expr(it: &mut Peekable) -> Option { #[cfg(test)] mod tests { - use expect_test::expect; - use hir_expand::InFile; use test_fixture::WithFixture; - use tt::{TextRange, TextSize}; use crate::AttrDefId; - use crate::attrs::{AttrFlags, Docs, IsInnerDoc}; + use crate::attrs::AttrFlags; use crate::test_db::TestDB; - #[test] - fn docs() { - let (_db, file_id) = TestDB::with_single_file(""); - let mut docs = Docs { - docs: String::new(), - docs_source_map: Vec::new(), - outline_mod: None, - inline_file: file_id.into(), - prefix_len: TextSize::new(0), - inline_inner_docs_start: None, - outline_inner_docs_start: None, - }; - let mut indent = usize::MAX; - - let outer = " foo\n\tbar baz"; - let mut ast_offset = TextSize::new(123); - for line in outer.split('\n') { - docs.extend_with_doc_str(line, ast_offset, &mut indent); - ast_offset += TextSize::of(line) + TextSize::of("\n"); - } - - docs.inline_inner_docs_start = Some(TextSize::of(&docs.docs)); - ast_offset += TextSize::new(123); - let inner = " bar \n baz"; - for line in inner.split('\n') { - docs.extend_with_doc_str(line, ast_offset, &mut indent); - ast_offset += TextSize::of(line) + TextSize::of("\n"); - } - - assert_eq!(indent, 1); - expect![[r#" - [ - DocsSourceMapLine { - string_offset: 0, - ast_offset: Some( - 123, - ), - }, - DocsSourceMapLine { - string_offset: 5, - ast_offset: Some( - 128, - ), - }, - DocsSourceMapLine { - string_offset: 15, - ast_offset: Some( - 261, - ), - }, - DocsSourceMapLine { - string_offset: 20, - ast_offset: Some( - 267, - ), - }, - ] - "#]] - .assert_debug_eq(&docs.docs_source_map); - - docs.remove_indent(indent, 0); - - assert_eq!(docs.inline_inner_docs_start, Some(TextSize::new(13))); - - assert_eq!(docs.docs, "foo\nbar baz\nbar\nbaz\n"); - expect![[r#" - [ - DocsSourceMapLine { - string_offset: 0, - ast_offset: Some( - 124, - ), - }, - DocsSourceMapLine { - string_offset: 4, - ast_offset: Some( - 129, - ), - }, - DocsSourceMapLine { - string_offset: 13, - ast_offset: Some( - 262, - ), - }, - DocsSourceMapLine { - string_offset: 17, - ast_offset: Some( - 268, - ), - }, - ] - "#]] - .assert_debug_eq(&docs.docs_source_map); - - docs.append(&docs.clone()); - docs.prepend_str("prefix---"); - assert_eq!(docs.docs, "prefix---foo\nbar baz\nbar\nbaz\nfoo\nbar baz\nbar\nbaz\n"); - expect![[r#" - [ - DocsSourceMapLine { - string_offset: 0, - ast_offset: Some( - 124, - ), - }, - DocsSourceMapLine { - string_offset: 4, - ast_offset: Some( - 129, - ), - }, - DocsSourceMapLine { - string_offset: 13, - ast_offset: Some( - 262, - ), - }, - DocsSourceMapLine { - string_offset: 17, - ast_offset: Some( - 268, - ), - }, - DocsSourceMapLine { - string_offset: 21, - ast_offset: Some( - 124, - ), - }, - DocsSourceMapLine { - string_offset: 25, - ast_offset: Some( - 129, - ), - }, - DocsSourceMapLine { - string_offset: 34, - ast_offset: Some( - 262, - ), - }, - DocsSourceMapLine { - string_offset: 38, - ast_offset: Some( - 268, - ), - }, - ] - "#]] - .assert_debug_eq(&docs.docs_source_map); - - let range = |start, end| TextRange::new(TextSize::new(start), TextSize::new(end)); - let in_file = |range| InFile::new(file_id.into(), range); - assert_eq!(docs.find_ast_range(range(0, 2)), None); - assert_eq!(docs.find_ast_range(range(8, 10)), None); - assert_eq!( - docs.find_ast_range(range(9, 10)), - Some((in_file(range(124, 125)), IsInnerDoc::No)) - ); - assert_eq!(docs.find_ast_range(range(20, 23)), None); - assert_eq!( - docs.find_ast_range(range(23, 25)), - Some((in_file(range(263, 265)), IsInnerDoc::Yes)) - ); - } - #[test] fn crate_attrs() { let fixture = r#" diff --git a/src/tools/rust-analyzer/crates/hir-def/src/attrs/docs.rs b/src/tools/rust-analyzer/crates/hir-def/src/attrs/docs.rs new file mode 100644 index 0000000000000..cf3a1845eae2f --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-def/src/attrs/docs.rs @@ -0,0 +1,767 @@ +//! Documentation extraction and source mapping. +//! +//! This module handles the extraction and processing of doc comments and `#[doc = "..."]` +//! attributes, including macro expansion for `#[doc = macro!()]` patterns. +//! It builds a concatenated string of the full docs as well as a source map +//! to map it back to AST (which is needed for things like resolving links in doc comments +//! and highlight injection). + +use std::{ + convert::Infallible, + ops::{ControlFlow, Range}, +}; + +use base_db::Crate; +use cfg::CfgOptions; +use either::Either; +use hir_expand::{ + AstId, ExpandTo, HirFileId, InFile, + attrs::{Meta, expand_cfg_attr_with_doc_comments}, + mod_path::ModPath, + span_map::SpanMap, +}; +use span::AstIdMap; +use syntax::{ + AstNode, AstToken, SyntaxNode, + ast::{self, AttrDocCommentIter, HasAttrs, IsString}, +}; +use tt::{TextRange, TextSize}; + +use crate::{db::DefDatabase, macro_call_as_call_id, nameres::MacroSubNs, resolver::Resolver}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct DocsSourceMapLine { + /// The offset in [`Docs::docs`]. + string_offset: TextSize, + /// The offset in the AST of the text. `None` for macro-expanded doc strings + /// where we cannot provide a faithful source mapping. + ast_offset: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Docs { + /// The concatenated string of all `#[doc = "..."]` attributes and documentation comments. + docs: String, + /// A sorted map from an offset in `docs` to an offset in the source code. + docs_source_map: Vec, + /// If the item is an outlined module (`mod foo;`), `docs_source_map` store the concatenated + /// list of the outline and inline docs (outline first). Then, this field contains the [`HirFileId`] + /// of the outline declaration, and the index in `docs` from which the inline docs + /// begin. + outline_mod: Option<(HirFileId, usize)>, + inline_file: HirFileId, + /// The size the prepended prefix, which does not map to real doc comments. + prefix_len: TextSize, + /// The offset in `docs` from which the docs are inner attributes/comments. + inline_inner_docs_start: Option, + /// Like `inline_inner_docs_start`, but for `outline_mod`. This can happen only when merging `Docs` + /// (as outline modules don't have inner attributes). + outline_inner_docs_start: Option, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum IsInnerDoc { + No, + Yes, +} + +impl IsInnerDoc { + #[inline] + pub fn yes(self) -> bool { + self == IsInnerDoc::Yes + } +} + +impl Docs { + #[inline] + pub fn docs(&self) -> &str { + &self.docs + } + + #[inline] + pub fn into_docs(self) -> String { + self.docs + } + + pub fn find_ast_range( + &self, + mut string_range: TextRange, + ) -> Option<(InFile, IsInnerDoc)> { + if string_range.start() < self.prefix_len { + return None; + } + string_range -= self.prefix_len; + + let mut file = self.inline_file; + let mut inner_docs_start = self.inline_inner_docs_start; + // Check whether the range is from the outline, the inline, or both. + let source_map = if let Some((outline_mod_file, outline_mod_end)) = self.outline_mod { + if let Some(first_inline) = self.docs_source_map.get(outline_mod_end) { + if string_range.end() <= first_inline.string_offset { + // The range is completely in the outline. + file = outline_mod_file; + inner_docs_start = self.outline_inner_docs_start; + &self.docs_source_map[..outline_mod_end] + } else if string_range.start() >= first_inline.string_offset { + // The range is completely in the inline. + &self.docs_source_map[outline_mod_end..] + } else { + // The range is combined from the outline and the inline - cannot map it back. + return None; + } + } else { + // There is no inline. + file = outline_mod_file; + inner_docs_start = self.outline_inner_docs_start; + &self.docs_source_map + } + } else { + // There is no outline. + &self.docs_source_map + }; + + let after_range = + source_map.partition_point(|line| line.string_offset <= string_range.start()) - 1; + let after_range = &source_map[after_range..]; + let line = after_range.first()?; + // Unmapped lines (from macro-expanded docs) cannot be mapped back to AST. + let ast_offset = line.ast_offset?; + if after_range.get(1).is_some_and(|next_line| next_line.string_offset < string_range.end()) + { + // The range is combined from two lines - cannot map it back. + return None; + } + let ast_range = string_range - line.string_offset + ast_offset; + let is_inner = if inner_docs_start + .is_some_and(|inner_docs_start| string_range.start() >= inner_docs_start) + { + IsInnerDoc::Yes + } else { + IsInnerDoc::No + }; + Some((InFile::new(file, ast_range), is_inner)) + } + + #[inline] + pub fn shift_by(&mut self, offset: TextSize) { + self.prefix_len += offset; + } + + pub fn prepend_str(&mut self, s: &str) { + self.prefix_len += TextSize::of(s); + self.docs.insert_str(0, s); + } + + pub fn append_str(&mut self, s: &str) { + self.docs.push_str(s); + } + + pub fn append(&mut self, other: &Docs) { + let other_offset = TextSize::of(&self.docs); + + assert!( + self.outline_mod.is_none() && other.outline_mod.is_none(), + "cannot merge `Docs` that have `outline_mod` set" + ); + self.outline_mod = Some((self.inline_file, self.docs_source_map.len())); + self.inline_file = other.inline_file; + self.outline_inner_docs_start = self.inline_inner_docs_start; + self.inline_inner_docs_start = other.inline_inner_docs_start.map(|it| it + other_offset); + + self.docs.push_str(&other.docs); + self.docs_source_map.extend(other.docs_source_map.iter().map( + |&DocsSourceMapLine { string_offset, ast_offset }| DocsSourceMapLine { + ast_offset, + string_offset: string_offset + other_offset, + }, + )); + } + + fn extend_with_doc_comment(&mut self, comment: ast::Comment, indent: &mut usize) { + let Some((doc, offset)) = comment.doc_comment() else { return }; + self.extend_with_doc_str(doc, comment.syntax().text_range().start() + offset, indent); + } + + fn extend_with_doc_attr(&mut self, value: syntax::SyntaxToken, indent: &mut usize) { + let Some(value) = ast::String::cast(value) else { return }; + let Some(value_offset) = value.text_range_between_quotes() else { return }; + let value_offset = value_offset.start(); + let Ok(value) = value.value() else { return }; + // FIXME: Handle source maps for escaped text. + self.extend_with_doc_str(&value, value_offset, indent); + } + + pub(crate) fn extend_with_doc_str( + &mut self, + doc: &str, + offset_in_ast: TextSize, + indent: &mut usize, + ) { + self.push_doc_lines(doc, Some(offset_in_ast), indent); + } + + fn extend_with_unmapped_doc_str(&mut self, doc: &str, indent: &mut usize) { + self.push_doc_lines(doc, None, indent); + } + + fn push_doc_lines(&mut self, doc: &str, mut ast_offset: Option, indent: &mut usize) { + for line in doc.split('\n') { + self.docs_source_map + .push(DocsSourceMapLine { string_offset: TextSize::of(&self.docs), ast_offset }); + if let Some(ref mut offset) = ast_offset { + *offset += TextSize::of(line) + TextSize::of("\n"); + } + + let line = line.trim_end(); + if let Some(line_indent) = line.chars().position(|ch| !ch.is_whitespace()) { + // Empty lines are handled because `position()` returns `None` for them. + *indent = std::cmp::min(*indent, line_indent); + } + self.docs.push_str(line); + self.docs.push('\n'); + } + } + + fn remove_indent(&mut self, indent: usize, start_source_map_index: usize) { + /// In case of panics, we want to avoid corrupted UTF-8 in `self.docs`, so we clear it. + struct Guard<'a>(&'a mut Docs); + impl Drop for Guard<'_> { + fn drop(&mut self) { + let Docs { + docs, + docs_source_map, + outline_mod, + inline_file: _, + prefix_len: _, + inline_inner_docs_start: _, + outline_inner_docs_start: _, + } = self.0; + // Don't use `String::clear()` here because it's not guaranteed to not do UTF-8-dependent things, + // and we may have temporarily broken the string's encoding. + unsafe { docs.as_mut_vec() }.clear(); + // This is just to avoid panics down the road. + docs_source_map.clear(); + *outline_mod = None; + } + } + + if self.docs.is_empty() { + return; + } + + let guard = Guard(self); + let source_map = &mut guard.0.docs_source_map[start_source_map_index..]; + let Some(&DocsSourceMapLine { string_offset: mut copy_into, .. }) = source_map.first() + else { + return; + }; + // We basically want to remove multiple ranges from a string. Doing this efficiently (without O(N^2) + // or allocations) requires unsafe. Basically, for each line, we copy the line minus the indent into + // consecutive to the previous line (which may have moved). Then at the end we truncate. + let mut accumulated_offset = TextSize::new(0); + for idx in 0..source_map.len() { + let string_end_offset = source_map + .get(idx + 1) + .map_or_else(|| TextSize::of(&guard.0.docs), |next_attr| next_attr.string_offset); + let line_source = &mut source_map[idx]; + let line_docs = + &guard.0.docs[TextRange::new(line_source.string_offset, string_end_offset)]; + let line_docs_len = TextSize::of(line_docs); + let indent_size = line_docs.char_indices().nth(indent).map_or_else( + || TextSize::of(line_docs) - TextSize::of("\n"), + |(offset, _)| TextSize::new(offset as u32), + ); + unsafe { guard.0.docs.as_bytes_mut() }.copy_within( + Range::::from(TextRange::new( + line_source.string_offset + indent_size, + string_end_offset, + )), + copy_into.into(), + ); + copy_into += line_docs_len - indent_size; + + if let Some(inner_attrs_start) = &mut guard.0.inline_inner_docs_start + && *inner_attrs_start == line_source.string_offset + { + *inner_attrs_start -= accumulated_offset; + } + // The removals in the string accumulate, but in the AST not, because it already points + // to the beginning of each attribute. + // Also, we need to shift the AST offset of every line, but the string offset of the first + // line should not get shifted (in general, the shift for the string offset is by the + // number of lines until the current one, excluding the current one). + line_source.string_offset -= accumulated_offset; + if let Some(ref mut ast_offset) = line_source.ast_offset { + *ast_offset += indent_size; + } + + accumulated_offset += indent_size; + } + // Don't use `String::truncate()` here because it's not guaranteed to not do UTF-8-dependent things, + // and we may have temporarily broken the string's encoding. + unsafe { guard.0.docs.as_mut_vec() }.truncate(copy_into.into()); + + std::mem::forget(guard); + } + + fn remove_last_newline(&mut self) { + self.docs.truncate(self.docs.len().saturating_sub(1)); + } + + fn shrink_to_fit(&mut self) { + let Docs { + docs, + docs_source_map, + outline_mod: _, + inline_file: _, + prefix_len: _, + inline_inner_docs_start: _, + outline_inner_docs_start: _, + } = self; + docs.shrink_to_fit(); + docs_source_map.shrink_to_fit(); + } +} + +struct DocMacroExpander<'db> { + db: &'db dyn DefDatabase, + krate: Crate, + recursion_depth: usize, + recursion_limit: usize, +} + +struct DocExprSourceCtx<'db> { + resolver: Resolver<'db>, + file_id: HirFileId, + ast_id_map: &'db AstIdMap, + span_map: SpanMap, +} + +fn expand_doc_expr_via_macro_pipeline<'db>( + expander: &mut DocMacroExpander<'db>, + source_ctx: &DocExprSourceCtx<'db>, + expr: ast::Expr, +) -> Option { + match expr { + ast::Expr::ParenExpr(paren_expr) => { + expand_doc_expr_via_macro_pipeline(expander, source_ctx, paren_expr.expr()?) + } + ast::Expr::Literal(literal) => match literal.kind() { + ast::LiteralKind::String(string) => string.value().ok().map(Into::into), + _ => None, + }, + ast::Expr::MacroExpr(macro_expr) => { + let macro_call = macro_expr.macro_call()?; + let (expr, new_source_ctx) = expand_doc_macro_call(expander, source_ctx, macro_call)?; + // After expansion, the expr lives in the expansion file; use its source context. + expand_doc_expr_via_macro_pipeline(expander, &new_source_ctx, expr) + } + _ => None, + } +} + +fn expand_doc_macro_call<'db>( + expander: &mut DocMacroExpander<'db>, + source_ctx: &DocExprSourceCtx<'db>, + macro_call: ast::MacroCall, +) -> Option<(ast::Expr, DocExprSourceCtx<'db>)> { + if expander.recursion_depth >= expander.recursion_limit { + return None; + } + + let path = macro_call.path()?; + let mod_path = ModPath::from_src(expander.db, path, &mut |range| { + source_ctx.span_map.span_for_range(range).ctx + })?; + let call_site = source_ctx.span_map.span_for_range(macro_call.syntax().text_range()); + let ast_id = AstId::new(source_ctx.file_id, source_ctx.ast_id_map.ast_id(¯o_call)); + let call_id = macro_call_as_call_id( + expander.db, + ast_id, + &mod_path, + call_site.ctx, + ExpandTo::Expr, + expander.krate, + |path| { + source_ctx.resolver.resolve_path_as_macro_def(expander.db, path, Some(MacroSubNs::Bang)) + }, + &mut |_, _| (), + ) + .ok()? + .value?; + + expander.recursion_depth += 1; + let parse = expander.db.parse_macro_expansion(call_id).value.0; + let expr = parse.cast::().map(|parse| parse.tree())?; + expander.recursion_depth -= 1; + + // Build a new source context for the expansion file so that any further + // recursive expansion (e.g. a user macro expanding to `concat!(...)`) + // correctly resolves AstIds and spans in the expansion. + let expansion_file_id: HirFileId = call_id.into(); + let new_source_ctx = DocExprSourceCtx { + resolver: source_ctx.resolver.clone(), + file_id: expansion_file_id, + ast_id_map: expander.db.ast_id_map(expansion_file_id), + span_map: expander.db.span_map(expansion_file_id), + }; + Some((expr, new_source_ctx)) +} + +/// Quick check: does this syntax node have any `#[doc = expr]` attributes where the +/// value is not a simple string literal (i.e., it needs macro expansion)? +fn has_doc_macro_attr(node: &SyntaxNode) -> bool { + ast::AnyHasAttrs::cast(node.clone()).is_some_and(|owner| { + owner.attrs().any(|attr| { + let Some(meta) = attr.meta() else { return false }; + // Check it's a `doc` attribute with an expression (e.g. `#[doc = expr]`), + // but NOT a simple string literal (which wouldn't need macro expansion). + meta.path().is_some_and(|path| { + path.as_single_name_ref().is_some_and(|name| name.text() == "doc") + }) && meta.expr().is_some_and(|expr| !matches!(expr, ast::Expr::Literal(_))) + }) + }) +} + +fn extend_with_attrs<'a, 'db>( + result: &mut Docs, + node: &SyntaxNode, + expect_inner_attrs: bool, + indent: &mut usize, + get_cfg_options: &dyn Fn() -> &'a CfgOptions, + cfg_options: &mut Option<&'a CfgOptions>, + mut expander: Option<&mut DocMacroExpander<'db>>, + source_ctx: Option<&DocExprSourceCtx<'db>>, +) { + // FIXME: `#[cfg_attr(..., doc = macro!())]` is not handled correctly here: + // macro expansion inside `cfg_attr`-wrapped doc attributes is not supported yet. + // Fixing this properly requires changes to `expand_cfg_attr()`. + // See https://github.com/rust-lang/rust-analyzer/issues/18444 + expand_cfg_attr_with_doc_comments::<_, Infallible>( + AttrDocCommentIter::from_syntax_node(node).filter(|attr| match attr { + Either::Left(attr) => attr.kind().is_inner() == expect_inner_attrs, + Either::Right(comment) => comment + .kind() + .doc + .is_some_and(|kind| (kind == ast::CommentPlacement::Inner) == expect_inner_attrs), + }), + || *cfg_options.get_or_insert_with(get_cfg_options), + |attr| { + match attr { + Either::Right(doc_comment) => result.extend_with_doc_comment(doc_comment, indent), + Either::Left((attr, _, _, top_attr)) => match attr { + Meta::NamedKeyValue { name: Some(name), value: Some(value), .. } + if name.text() == "doc" => + { + result.extend_with_doc_attr(value, indent); + } + Meta::NamedKeyValue { name: Some(name), value: None, .. } + if name.text() == "doc" => + { + if let (Some(expander), Some(source_ctx)) = + (expander.as_deref_mut(), source_ctx) + && let Some(expr) = top_attr.expr() + && let Some(expanded) = + expand_doc_expr_via_macro_pipeline(expander, source_ctx, expr) + { + result.extend_with_unmapped_doc_str(&expanded, indent); + } + } + _ => {} + }, + } + ControlFlow::Continue(()) + }, + ); +} + +pub(crate) fn extract_docs<'a, 'db>( + db: &'db dyn DefDatabase, + krate: Crate, + // For outer docs on an outlined module, use the parent module's resolver. + // For inline docs (and non-module items), use the item's own resolver. + outer_resolver: Option Resolver<'db>>, + inline_resolver: impl FnOnce() -> Resolver<'db>, + get_cfg_options: &dyn Fn() -> &'a CfgOptions, + source: InFile, + outer_mod_decl: Option>, + inner_attrs_node: Option, +) -> Option> { + let mut result = Docs { + docs: String::new(), + docs_source_map: Vec::new(), + outline_mod: None, + inline_file: source.file_id, + prefix_len: TextSize::new(0), + inline_inner_docs_start: None, + outline_inner_docs_start: None, + }; + + let mut cfg_options = None; + + if let Some(outer_mod_decl) = outer_mod_decl { + let mut indent = usize::MAX; + // For outer docs (the `mod foo;` declaration), use the parent module's resolver + // so that macros are resolved in the parent's scope. + let (mut outer_expander, outer_source_ctx) = + if has_doc_macro_attr(outer_mod_decl.value.syntax()) + && let Some(make) = outer_resolver + { + let resolver = make(); + let def_map = resolver.top_level_def_map(); + let recursion_limit = def_map.recursion_limit() as usize; + let expander = DocMacroExpander { db, krate, recursion_depth: 0, recursion_limit }; + let source_ctx = DocExprSourceCtx { + resolver, + file_id: outer_mod_decl.file_id, + ast_id_map: db.ast_id_map(outer_mod_decl.file_id), + span_map: db.span_map(outer_mod_decl.file_id), + }; + (Some(expander), Some(source_ctx)) + } else { + (None, None) + }; + extend_with_attrs( + &mut result, + outer_mod_decl.value.syntax(), + false, + &mut indent, + get_cfg_options, + &mut cfg_options, + outer_expander.as_mut(), + outer_source_ctx.as_ref(), + ); + result.remove_indent(indent, 0); + result.outline_mod = Some((outer_mod_decl.file_id, result.docs_source_map.len())); + } + + let inline_source_map_start = result.docs_source_map.len(); + let mut indent = usize::MAX; + // For inline docs, use the item's own resolver. + let needs_expansion = has_doc_macro_attr(source.value.syntax()) + || inner_attrs_node.as_ref().is_some_and(|n| has_doc_macro_attr(n)); + let (mut inline_expander, inline_source_ctx) = if needs_expansion { + let resolver = inline_resolver(); + let def_map = resolver.top_level_def_map(); + let recursion_limit = def_map.recursion_limit() as usize; + let expander = DocMacroExpander { db, krate, recursion_depth: 0, recursion_limit }; + let source_ctx = DocExprSourceCtx { + resolver, + file_id: source.file_id, + ast_id_map: db.ast_id_map(source.file_id), + span_map: db.span_map(source.file_id), + }; + (Some(expander), Some(source_ctx)) + } else { + (None, None) + }; + extend_with_attrs( + &mut result, + source.value.syntax(), + false, + &mut indent, + get_cfg_options, + &mut cfg_options, + inline_expander.as_mut(), + inline_source_ctx.as_ref(), + ); + if let Some(inner_attrs_node) = &inner_attrs_node { + result.inline_inner_docs_start = Some(TextSize::of(&result.docs)); + extend_with_attrs( + &mut result, + inner_attrs_node, + true, + &mut indent, + get_cfg_options, + &mut cfg_options, + inline_expander.as_mut(), + inline_source_ctx.as_ref(), + ); + } + result.remove_indent(indent, inline_source_map_start); + + result.remove_last_newline(); + + result.shrink_to_fit(); + + if result.docs.is_empty() { None } else { Some(Box::new(result)) } +} + +#[cfg(test)] +mod tests { + use expect_test::expect; + use hir_expand::InFile; + use test_fixture::WithFixture; + use tt::{TextRange, TextSize}; + + use crate::test_db::TestDB; + + use super::{Docs, IsInnerDoc}; + + #[test] + fn docs() { + let (_db, file_id) = TestDB::with_single_file(""); + let mut docs = Docs { + docs: String::new(), + docs_source_map: Vec::new(), + outline_mod: None, + inline_file: file_id.into(), + prefix_len: TextSize::new(0), + inline_inner_docs_start: None, + outline_inner_docs_start: None, + }; + let mut indent = usize::MAX; + + let outer = " foo\n\tbar baz"; + let mut ast_offset = TextSize::new(123); + for line in outer.split('\n') { + docs.extend_with_doc_str(line, ast_offset, &mut indent); + ast_offset += TextSize::of(line) + TextSize::of("\n"); + } + + docs.inline_inner_docs_start = Some(TextSize::of(&docs.docs)); + ast_offset += TextSize::new(123); + let inner = " bar \n baz"; + for line in inner.split('\n') { + docs.extend_with_doc_str(line, ast_offset, &mut indent); + ast_offset += TextSize::of(line) + TextSize::of("\n"); + } + + assert_eq!(indent, 1); + expect![[r#" + [ + DocsSourceMapLine { + string_offset: 0, + ast_offset: Some( + 123, + ), + }, + DocsSourceMapLine { + string_offset: 5, + ast_offset: Some( + 128, + ), + }, + DocsSourceMapLine { + string_offset: 15, + ast_offset: Some( + 261, + ), + }, + DocsSourceMapLine { + string_offset: 20, + ast_offset: Some( + 267, + ), + }, + ] + "#]] + .assert_debug_eq(&docs.docs_source_map); + + docs.remove_indent(indent, 0); + + assert_eq!(docs.inline_inner_docs_start, Some(TextSize::new(13))); + + assert_eq!(docs.docs, "foo\nbar baz\nbar\nbaz\n"); + expect![[r#" + [ + DocsSourceMapLine { + string_offset: 0, + ast_offset: Some( + 124, + ), + }, + DocsSourceMapLine { + string_offset: 4, + ast_offset: Some( + 129, + ), + }, + DocsSourceMapLine { + string_offset: 13, + ast_offset: Some( + 262, + ), + }, + DocsSourceMapLine { + string_offset: 17, + ast_offset: Some( + 268, + ), + }, + ] + "#]] + .assert_debug_eq(&docs.docs_source_map); + + docs.append(&docs.clone()); + docs.prepend_str("prefix---"); + assert_eq!(docs.docs, "prefix---foo\nbar baz\nbar\nbaz\nfoo\nbar baz\nbar\nbaz\n"); + expect![[r#" + [ + DocsSourceMapLine { + string_offset: 0, + ast_offset: Some( + 124, + ), + }, + DocsSourceMapLine { + string_offset: 4, + ast_offset: Some( + 129, + ), + }, + DocsSourceMapLine { + string_offset: 13, + ast_offset: Some( + 262, + ), + }, + DocsSourceMapLine { + string_offset: 17, + ast_offset: Some( + 268, + ), + }, + DocsSourceMapLine { + string_offset: 21, + ast_offset: Some( + 124, + ), + }, + DocsSourceMapLine { + string_offset: 25, + ast_offset: Some( + 129, + ), + }, + DocsSourceMapLine { + string_offset: 34, + ast_offset: Some( + 262, + ), + }, + DocsSourceMapLine { + string_offset: 38, + ast_offset: Some( + 268, + ), + }, + ] + "#]] + .assert_debug_eq(&docs.docs_source_map); + + let range = |start, end| TextRange::new(TextSize::new(start), TextSize::new(end)); + let in_file = |range| InFile::new(file_id.into(), range); + assert_eq!(docs.find_ast_range(range(0, 2)), None); + assert_eq!(docs.find_ast_range(range(8, 10)), None); + assert_eq!( + docs.find_ast_range(range(9, 10)), + Some((in_file(range(124, 125)), IsInnerDoc::No)) + ); + assert_eq!(docs.find_ast_range(range(20, 23)), None); + assert_eq!( + docs.find_ast_range(range(23, 25)), + Some((in_file(range(263, 265)), IsInnerDoc::Yes)) + ); + } +} 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 d979269507168..14ce51d08ae41 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs @@ -11658,3 +11658,79 @@ struct Bar { "#]], ); } + +#[test] +fn test_hover_doc_attr_macro_on_outlined_mod_resolves_from_parent() { + // Outer doc-macro on `mod foo;` should resolve from the parent module, + // and combine with inner `//!` docs from the module file. + check( + r#" +//- /main.rs +macro_rules! doc_str { + () => { "expanded from parent" }; +} + +/// plain outer doc +#[doc = doc_str!()] +mod foo$0; + +//- /foo.rs +//! inner module docs +pub struct Bar; +"#, + expect![[r#" + *foo* + + ```rust + ra_test_fixture + ``` + + ```rust + mod foo + ``` + + --- + + plain outer doc + expanded from parent + inner module docs + "#]], + ); +} + +#[test] +fn test_hover_doc_attr_macro_on_outlined_mod_combined_with_inner_docs() { + // Outer doc macro on `mod foo;` (resolved from parent) should combine with + // inner docs from the module file. + check( + r#" +//- /main.rs +macro_rules! doc_str { + () => { "outer doc from macro" }; +} + +#[doc = doc_str!()] +mod foo$0; + +//- /foo.rs +//! inner module docs +pub struct Bar; +"#, + expect![[r#" + *foo* + + ```rust + ra_test_fixture + ``` + + ```rust + mod foo + ``` + + --- + + outer doc from macro + inner module docs + "#]], + ); +} From a7790ba90a17d599b13884efbb36ee85aef1c508 Mon Sep 17 00:00:00 2001 From: so1ve Date: Fri, 3 Apr 2026 19:29:33 +0800 Subject: [PATCH 066/144] fix clippy --- src/tools/rust-analyzer/crates/hir-def/src/attrs/docs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/attrs/docs.rs b/src/tools/rust-analyzer/crates/hir-def/src/attrs/docs.rs index cf3a1845eae2f..2e8faf59cc3a4 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/attrs/docs.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/attrs/docs.rs @@ -539,7 +539,7 @@ pub(crate) fn extract_docs<'a, 'db>( let mut indent = usize::MAX; // For inline docs, use the item's own resolver. let needs_expansion = has_doc_macro_attr(source.value.syntax()) - || inner_attrs_node.as_ref().is_some_and(|n| has_doc_macro_attr(n)); + || inner_attrs_node.as_ref().is_some_and(has_doc_macro_attr); let (mut inline_expander, inline_source_ctx) = if needs_expansion { let resolver = inline_resolver(); let def_map = resolver.top_level_def_map(); From f1300e60ac16c484f0f292e9910f6008806096d4 Mon Sep 17 00:00:00 2001 From: so1ve Date: Fri, 3 Apr 2026 19:46:23 +0800 Subject: [PATCH 067/144] update --- .../crates/hir-def/src/attrs/docs.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/attrs/docs.rs b/src/tools/rust-analyzer/crates/hir-def/src/attrs/docs.rs index 2e8faf59cc3a4..4b4d9b1f6c183 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/attrs/docs.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/attrs/docs.rs @@ -433,10 +433,9 @@ fn extend_with_attrs<'a, 'db>( mut expander: Option<&mut DocMacroExpander<'db>>, source_ctx: Option<&DocExprSourceCtx<'db>>, ) { - // FIXME: `#[cfg_attr(..., doc = macro!())]` is not handled correctly here: - // macro expansion inside `cfg_attr`-wrapped doc attributes is not supported yet. - // Fixing this properly requires changes to `expand_cfg_attr()`. - // See https://github.com/rust-lang/rust-analyzer/issues/18444 + // FIXME: `#[cfg_attr(..., doc = macro!())]` skips macro expansion because + // `top_attr` points to the `cfg_attr` node, not the inner `doc = macro!()`. + // And expanding `cfg_attr` here or not is not decided yet. expand_cfg_attr_with_doc_comments::<_, Infallible>( AttrDocCommentIter::from_syntax_node(node).filter(|attr| match attr { Either::Left(attr) => attr.kind().is_inner() == expect_inner_attrs, @@ -458,8 +457,16 @@ fn extend_with_attrs<'a, 'db>( Meta::NamedKeyValue { name: Some(name), value: None, .. } if name.text() == "doc" => { - if let (Some(expander), Some(source_ctx)) = - (expander.as_deref_mut(), source_ctx) + // When the doc attribute comes from inside a `cfg_attr`, + // `top_attr` points to the `cfg_attr(...)` node, not the + // inner `doc = macro!()`. In that case `top_attr.expr()` + // would not yield the macro expression we need, so skip + // expansion (see FIXME above). + let is_from_cfg_attr = + top_attr.as_simple_call().is_some_and(|(name, _)| name == "cfg_attr"); + if !is_from_cfg_attr + && let (Some(expander), Some(source_ctx)) = + (expander.as_deref_mut(), source_ctx) && let Some(expr) = top_attr.expr() && let Some(expanded) = expand_doc_expr_via_macro_pipeline(expander, source_ctx, expr) From 1617d7339715980c001eeb584fea2e4671591cc5 Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 3 Apr 2026 20:19:00 +0800 Subject: [PATCH 068/144] Update crates/hir-def/src/attrs.rs Co-authored-by: Chayim Refael Friedman --- src/tools/rust-analyzer/crates/hir-def/src/attrs.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs b/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs index ddcfea2c219d4..f27b2d54a6848 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs @@ -965,17 +965,12 @@ impl AttrFlags { pub fn docs(db: &dyn DefDatabase, owner: AttrDefId) -> Option> { let (source, outer_mod_decl, _extra_crate_attrs, krate) = attrs_source(db, owner); let inner_attrs_node = source.value.inner_attributes_node(); - let outer_resolver = if outer_mod_decl.is_some() { - if let AttrDefId::ModuleId(module_id) = owner { - module_id - .containing_module(db) - .map(|parent| move || -> Resolver<'_> { parent.resolver(db) }) - } else { - None - } + let parent = if outer_mod_decl.is_some() && AttrDefId::ModuleId(module_id) = owner { + module_id.containing_module(db) } else { None }; + let outer_resolver = || parent.map(|it| it.resolver(db)); // Note: we don't have to pass down `_extra_crate_attrs` here, since `extract_docs` // does not handle crate-level attributes related to docs. // See: https://doc.rust-lang.org/rustdoc/write-documentation/the-doc-attribute.html#at-the-crate-level From c7963af6b72c2a87d4e56e6b3f1328f97a0cafeb Mon Sep 17 00:00:00 2001 From: so1ve Date: Fri, 3 Apr 2026 21:04:14 +0800 Subject: [PATCH 069/144] apply suggestions --- .../rust-analyzer/crates/hir-def/src/attrs.rs | 14 +-- .../crates/hir-def/src/attrs/docs.rs | 117 ++++++++---------- .../crates/ide/src/hover/tests.rs | 24 ++-- 3 files changed, 67 insertions(+), 88 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs b/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs index f27b2d54a6848..3dbbafdd51fdb 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs @@ -965,20 +965,20 @@ impl AttrFlags { pub fn docs(db: &dyn DefDatabase, owner: AttrDefId) -> Option> { let (source, outer_mod_decl, _extra_crate_attrs, krate) = attrs_source(db, owner); let inner_attrs_node = source.value.inner_attributes_node(); - let parent = if outer_mod_decl.is_some() && AttrDefId::ModuleId(module_id) = owner { + let parent = if outer_mod_decl.is_some() + && let AttrDefId::ModuleId(module_id) = owner + { module_id.containing_module(db) } else { None }; - let outer_resolver = || parent.map(|it| it.resolver(db)); // Note: we don't have to pass down `_extra_crate_attrs` here, since `extract_docs` // does not handle crate-level attributes related to docs. // See: https://doc.rust-lang.org/rustdoc/write-documentation/the-doc-attribute.html#at-the-crate-level self::docs::extract_docs( db, krate, - outer_resolver, - || resolver_for_attr_def_id(db, owner), + &|| (parent.map(|it| it.resolver(db)), resolver_for_attr_def_id(db, owner)), &|| krate.cfg_options(db), source, outer_mod_decl, @@ -998,14 +998,10 @@ impl AttrFlags { ) -> ArenaMap>> { let krate = variant.module(db).krate(db); collect_field_attrs(db, variant, |cfg_options, field| { - fn none_resolver<'db>() -> Option Resolver<'db>> { - None - } self::docs::extract_docs( db, krate, - none_resolver(), - || variant.resolver(db), + &|| (None, variant.resolver(db)), &|| cfg_options, field, None, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/attrs/docs.rs b/src/tools/rust-analyzer/crates/hir-def/src/attrs/docs.rs index 4b4d9b1f6c183..16e813bc5f971 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/attrs/docs.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/attrs/docs.rs @@ -23,7 +23,7 @@ use hir_expand::{ use span::AstIdMap; use syntax::{ AstNode, AstToken, SyntaxNode, - ast::{self, AttrDocCommentIter, HasAttrs, IsString}, + ast::{self, AttrDocCommentIter, IsString}, }; use tt::{TextRange, TextSize}; @@ -408,34 +408,25 @@ fn expand_doc_macro_call<'db>( Some((expr, new_source_ctx)) } -/// Quick check: does this syntax node have any `#[doc = expr]` attributes where the -/// value is not a simple string literal (i.e., it needs macro expansion)? -fn has_doc_macro_attr(node: &SyntaxNode) -> bool { - ast::AnyHasAttrs::cast(node.clone()).is_some_and(|owner| { - owner.attrs().any(|attr| { - let Some(meta) = attr.meta() else { return false }; - // Check it's a `doc` attribute with an expression (e.g. `#[doc = expr]`), - // but NOT a simple string literal (which wouldn't need macro expansion). - meta.path().is_some_and(|path| { - path.as_single_name_ref().is_some_and(|name| name.text() == "doc") - }) && meta.expr().is_some_and(|expr| !matches!(expr, ast::Expr::Literal(_))) - }) - }) -} - fn extend_with_attrs<'a, 'db>( result: &mut Docs, + db: &'db dyn DefDatabase, + krate: Crate, node: &SyntaxNode, + file_id: HirFileId, expect_inner_attrs: bool, indent: &mut usize, get_cfg_options: &dyn Fn() -> &'a CfgOptions, cfg_options: &mut Option<&'a CfgOptions>, - mut expander: Option<&mut DocMacroExpander<'db>>, - source_ctx: Option<&DocExprSourceCtx<'db>>, + make_resolver: &dyn Fn() -> Option>, ) { + // Lazily initialised when we first encounter a `#[doc = macro!()]`. + let mut expander: Option, DocExprSourceCtx<'db>)>> = None; + // FIXME: `#[cfg_attr(..., doc = macro!())]` skips macro expansion because // `top_attr` points to the `cfg_attr` node, not the inner `doc = macro!()`. - // And expanding `cfg_attr` here or not is not decided yet. + // Fixing this is difficult as we need an `Expr` that doesn't exist here for + // the ast id and for sanely parsing the macro call. expand_cfg_attr_with_doc_comments::<_, Infallible>( AttrDocCommentIter::from_syntax_node(node).filter(|attr| match attr { Either::Left(attr) => attr.kind().is_inner() == expect_inner_attrs, @@ -465,11 +456,31 @@ fn extend_with_attrs<'a, 'db>( let is_from_cfg_attr = top_attr.as_simple_call().is_some_and(|(name, _)| name == "cfg_attr"); if !is_from_cfg_attr - && let (Some(expander), Some(source_ctx)) = - (expander.as_deref_mut(), source_ctx) && let Some(expr) = top_attr.expr() + && let Some((exp, ctx)) = expander + .get_or_insert_with(|| { + make_resolver().map(|resolver| { + let def_map = resolver.top_level_def_map(); + let recursion_limit = def_map.recursion_limit() as usize; + ( + DocMacroExpander { + db, + krate, + recursion_depth: 0, + recursion_limit, + }, + DocExprSourceCtx { + resolver, + file_id, + ast_id_map: db.ast_id_map(file_id), + span_map: db.span_map(file_id), + }, + ) + }) + }) + .as_mut() && let Some(expanded) = - expand_doc_expr_via_macro_pipeline(expander, source_ctx, expr) + expand_doc_expr_via_macro_pipeline(exp, ctx, expr) { result.extend_with_unmapped_doc_str(&expanded, indent); } @@ -485,10 +496,10 @@ fn extend_with_attrs<'a, 'db>( pub(crate) fn extract_docs<'a, 'db>( db: &'db dyn DefDatabase, krate: Crate, - // For outer docs on an outlined module, use the parent module's resolver. - // For inline docs (and non-module items), use the item's own resolver. - outer_resolver: Option Resolver<'db>>, - inline_resolver: impl FnOnce() -> Resolver<'db>, + // Returns (outer_resolver, inline_resolver). + // `outer_resolver` is `Some` only for outlined modules (`mod foo;`) where outer docs + // should be resolved in the parent module's scope. + resolvers: &dyn Fn() -> (Option>, Resolver<'db>), get_cfg_options: &dyn Fn() -> &'a CfgOptions, source: InFile, outer_mod_decl: Option>, @@ -510,33 +521,17 @@ pub(crate) fn extract_docs<'a, 'db>( let mut indent = usize::MAX; // For outer docs (the `mod foo;` declaration), use the parent module's resolver // so that macros are resolved in the parent's scope. - let (mut outer_expander, outer_source_ctx) = - if has_doc_macro_attr(outer_mod_decl.value.syntax()) - && let Some(make) = outer_resolver - { - let resolver = make(); - let def_map = resolver.top_level_def_map(); - let recursion_limit = def_map.recursion_limit() as usize; - let expander = DocMacroExpander { db, krate, recursion_depth: 0, recursion_limit }; - let source_ctx = DocExprSourceCtx { - resolver, - file_id: outer_mod_decl.file_id, - ast_id_map: db.ast_id_map(outer_mod_decl.file_id), - span_map: db.span_map(outer_mod_decl.file_id), - }; - (Some(expander), Some(source_ctx)) - } else { - (None, None) - }; extend_with_attrs( &mut result, + db, + krate, outer_mod_decl.value.syntax(), + outer_mod_decl.file_id, false, &mut indent, get_cfg_options, &mut cfg_options, - outer_expander.as_mut(), - outer_source_ctx.as_ref(), + &|| resolvers().0, ); result.remove_indent(indent, 0); result.outline_mod = Some((outer_mod_decl.file_id, result.docs_source_map.len())); @@ -544,45 +539,33 @@ pub(crate) fn extract_docs<'a, 'db>( let inline_source_map_start = result.docs_source_map.len(); let mut indent = usize::MAX; + let inline_resolver = &|| Some(resolvers().1); // For inline docs, use the item's own resolver. - let needs_expansion = has_doc_macro_attr(source.value.syntax()) - || inner_attrs_node.as_ref().is_some_and(has_doc_macro_attr); - let (mut inline_expander, inline_source_ctx) = if needs_expansion { - let resolver = inline_resolver(); - let def_map = resolver.top_level_def_map(); - let recursion_limit = def_map.recursion_limit() as usize; - let expander = DocMacroExpander { db, krate, recursion_depth: 0, recursion_limit }; - let source_ctx = DocExprSourceCtx { - resolver, - file_id: source.file_id, - ast_id_map: db.ast_id_map(source.file_id), - span_map: db.span_map(source.file_id), - }; - (Some(expander), Some(source_ctx)) - } else { - (None, None) - }; extend_with_attrs( &mut result, + db, + krate, source.value.syntax(), + source.file_id, false, &mut indent, get_cfg_options, &mut cfg_options, - inline_expander.as_mut(), - inline_source_ctx.as_ref(), + inline_resolver, ); if let Some(inner_attrs_node) = &inner_attrs_node { result.inline_inner_docs_start = Some(TextSize::of(&result.docs)); extend_with_attrs( &mut result, + db, + krate, inner_attrs_node, + source.file_id, true, &mut indent, get_cfg_options, &mut cfg_options, - inline_expander.as_mut(), - inline_source_ctx.as_ref(), + inline_resolver, ); } result.remove_indent(indent, inline_source_map_start); 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 14ce51d08ae41..2ca43096dddeb 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs @@ -11699,22 +11699,21 @@ pub struct Bar; } #[test] -fn test_hover_doc_attr_macro_on_outlined_mod_combined_with_inner_docs() { - // Outer doc macro on `mod foo;` (resolved from parent) should combine with - // inner docs from the module file. +fn test_hover_doc_attr_inner_doc_macro() { + // Inner doc attribute with macro expansion (`#![doc = macro!()]`) check( r#" -//- /main.rs macro_rules! doc_str { - () => { "outer doc from macro" }; + () => { "inner doc from macro" }; } -#[doc = doc_str!()] -mod foo$0; +/// outer doc +/// +mod foo$0 { + #![doc = doc_str!()] -//- /foo.rs -//! inner module docs -pub struct Bar; + pub struct Bar; +} "#, expect![[r#" *foo* @@ -11729,8 +11728,9 @@ pub struct Bar; --- - outer doc from macro - inner module docs + outer doc + + inner doc from macro "#]], ); } From 9fb980e4922539b9f5a1658fbcf72c2d59cc59e0 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 3 Apr 2026 15:17:56 +0200 Subject: [PATCH 070/144] Port parse query to newstyle --- .../crates/base-db/src/editioned_file_id.rs | 57 +++++++++++++++++++ .../rust-analyzer/crates/base-db/src/lib.rs | 31 +--------- .../rust-analyzer/crates/hir-def/src/attrs.rs | 6 +- .../crates/hir-def/src/expr_store/scope.rs | 5 +- .../hir-def/src/macro_expansion_tests/mod.rs | 3 +- .../crates/hir-def/src/nameres.rs | 2 +- .../crates/hir-def/src/test_db.rs | 2 +- .../crates/hir-expand/src/builtin/fn_macro.rs | 2 +- .../rust-analyzer/crates/hir-expand/src/db.rs | 4 +- .../crates/hir-expand/src/files.rs | 8 +-- .../crates/hir-expand/src/span_map.rs | 2 +- .../rust-analyzer/crates/hir/src/semantics.rs | 8 +-- .../crates/ide-completion/src/context.rs | 2 +- .../crates/ide-diagnostics/src/lib.rs | 5 +- .../crates/ide-ssr/src/from_comment.rs | 4 +- src/tools/rust-analyzer/crates/ide/src/lib.rs | 10 ++-- .../rust-analyzer/crates/ide/src/typing.rs | 2 +- .../crates/ide/src/typing/on_enter.rs | 3 +- 18 files changed, 91 insertions(+), 65 deletions(-) diff --git a/src/tools/rust-analyzer/crates/base-db/src/editioned_file_id.rs b/src/tools/rust-analyzer/crates/base-db/src/editioned_file_id.rs index 8721f3a0ff3b9..a77b45f8ae68a 100644 --- a/src/tools/rust-analyzer/crates/base-db/src/editioned_file_id.rs +++ b/src/tools/rust-analyzer/crates/base-db/src/editioned_file_id.rs @@ -5,14 +5,71 @@ use std::hash::Hash; use salsa::Database; use span::Edition; +use syntax::{SyntaxError, ast}; use vfs::FileId; +use crate::SourceDatabase; + #[salsa::interned(debug, constructor = from_span_file_id, no_lifetime)] #[derive(PartialOrd, Ord)] pub struct EditionedFileId { field: span::EditionedFileId, } +// Currently does not work due to a salsa bug +// #[salsa::tracked] +// impl EditionedFileId { +// #[salsa::tracked(lru = 128)] +// pub fn parse(self, db: &dyn SourceDatabase) -> syntax::Parse { +// let _p = tracing::info_span!("parse", ?self).entered(); +// let (file_id, edition) = self.unpack(db); +// let text = db.file_text(file_id).text(db); +// ast::SourceFile::parse(text, edition) +// } + +// // firewall query +// #[salsa::tracked(returns(as_deref))] +// pub fn parse_errors(self, db: &dyn SourceDatabase) -> Option> { +// let errors = self.parse(db).errors(); +// match &*errors { +// [] => None, +// [..] => Some(errors.into()), +// } +// } +// } + +impl EditionedFileId { + pub fn parse(self, db: &dyn SourceDatabase) -> syntax::Parse { + #[salsa::tracked(lru = 128)] + pub fn parse( + db: &dyn SourceDatabase, + file_id: EditionedFileId, + ) -> syntax::Parse { + let _p = tracing::info_span!("parse", ?file_id).entered(); + let (file_id, edition) = file_id.unpack(db); + let text = db.file_text(file_id).text(db); + ast::SourceFile::parse(text, edition) + } + parse(db, self) + } + + // firewall query + pub fn parse_errors(self, db: &dyn SourceDatabase) -> Option<&[SyntaxError]> { + #[salsa::tracked(returns(as_deref))] + pub fn parse_errors( + db: &dyn SourceDatabase, + file_id: EditionedFileId, + ) -> Option> { + let errors = file_id.parse(db).errors(); + match &*errors { + [] => None, + [..] => Some(errors.into()), + } + } + parse_errors(db, self) + } +} + impl EditionedFileId { #[inline] pub fn new(db: &dyn Database, file_id: FileId, edition: Edition) -> Self { diff --git a/src/tools/rust-analyzer/crates/base-db/src/lib.rs b/src/tools/rust-analyzer/crates/base-db/src/lib.rs index 5baf4ce6f907f..b1cb1b32020e3 100644 --- a/src/tools/rust-analyzer/crates/base-db/src/lib.rs +++ b/src/tools/rust-analyzer/crates/base-db/src/lib.rs @@ -36,7 +36,6 @@ pub use query_group; use rustc_hash::{FxHashSet, FxHasher}; use salsa::{Durability, Setter}; pub use semver::{BuildMetadata, Prerelease, Version, VersionReq}; -use syntax::{Parse, SyntaxError, ast}; use triomphe::Arc; pub use vfs::{AnchoredPath, AnchoredPathBuf, FileId, VfsPath, file_set::FileSet}; @@ -239,16 +238,7 @@ pub struct SourceRootInput { /// Database which stores all significant input facts: source code and project /// model. Everything else in rust-analyzer is derived from these queries. #[query_group::query_group] -pub trait RootQueryDb: SourceDatabase + salsa::Database { - /// Parses the file into the syntax tree. - #[salsa::invoke(parse)] - #[salsa::lru(128)] - fn parse(&self, file_id: EditionedFileId) -> Parse; - - /// Returns the set of errors obtained from parsing the file including validation errors. - #[salsa::transparent] - fn parse_errors(&self, file_id: EditionedFileId) -> Option<&[SyntaxError]>; - +pub trait RootQueryDb: SourceDatabase { #[salsa::transparent] fn toolchain_channel(&self, krate: Crate) -> Option; @@ -357,25 +347,6 @@ fn toolchain_channel(db: &dyn RootQueryDb, krate: Crate) -> Option Parse { - let _p = tracing::info_span!("parse", ?file_id).entered(); - let (file_id, edition) = file_id.unpack(db.as_dyn_database()); - let text = db.file_text(file_id).text(db); - ast::SourceFile::parse(text, edition) -} - -fn parse_errors(db: &dyn RootQueryDb, file_id: EditionedFileId) -> Option<&[SyntaxError]> { - #[salsa_macros::tracked(returns(ref))] - fn parse_errors(db: &dyn RootQueryDb, file_id: EditionedFileId) -> Option> { - let errors = db.parse(file_id).errors(); - match &*errors { - [] => None, - [..] => Some(errors.into()), - } - } - parse_errors(db, file_id).as_ref().map(|it| &**it) -} - fn source_root_crates(db: &dyn RootQueryDb, id: SourceRootId) -> Arc<[Crate]> { let crates = db.all_crates(); crates diff --git a/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs b/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs index e3e1aac7090ae..013d4d2b130a8 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs @@ -354,13 +354,13 @@ fn attrs_source( let krate = def_map.krate(); let (definition, declaration, extra_crate_attrs) = match def_map[id].origin { ModuleOrigin::CrateRoot { definition } => { - let definition_source = db.parse(definition).tree(); + let definition_source = definition.parse(db).tree(); let definition = InFile::new(definition.into(), definition_source.into()); let extra_crate_attrs = parse_extra_crate_attrs(db, krate); (definition, None, extra_crate_attrs) } ModuleOrigin::File { declaration, declaration_tree_id, definition, .. } => { - let definition_source = db.parse(definition).tree(); + let definition_source = definition.parse(db).tree(); let definition = InFile::new(definition.into(), definition_source.into()); let declaration = InFile::new(declaration_tree_id.file_id(), declaration); let declaration = declaration.with_value(declaration.to_node(db)); @@ -1069,7 +1069,7 @@ impl AttrFlags { #[salsa::tracked(returns(ref))] pub fn doc_html_root_url(db: &dyn DefDatabase, krate: Crate) -> Option { let root_file_id = krate.root_file_id(db); - let syntax = db.parse(root_file_id).tree(); + let syntax = root_file_id.parse(db).tree(); let extra_crate_attrs = parse_extra_crate_attrs(db, krate).into_iter().flat_map(|src| src.attrs()); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/scope.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/scope.rs index 40ae0b7de4622..9738ac5c44c99 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/scope.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/scope.rs @@ -371,7 +371,6 @@ fn compute_expr_scopes( #[cfg(test)] mod tests { - use base_db::RootQueryDb; use hir_expand::{InFile, name::AsName}; use span::FileId; use syntax::{AstNode, algo::find_node_at_offset, ast}; @@ -414,7 +413,7 @@ mod tests { let (file_id, _) = editioned_file_id.unpack(&db); - let file_syntax = db.parse(editioned_file_id).syntax_node(); + let file_syntax = editioned_file_id.parse(&db).syntax_node(); let marker: ast::PathExpr = find_node_at_offset(&file_syntax, offset).unwrap(); let function = find_function(&db, file_id); @@ -570,7 +569,7 @@ fn foo() { let (file_id, _) = editioned_file_id.unpack(&db); - let file = db.parse(editioned_file_id).ok().unwrap(); + let file = editioned_file_id.parse(&db).ok().unwrap(); let expected_name = find_node_at_offset::(file.syntax(), expected_offset.into()) .expect("failed to find a name at the target offset"); let name_ref: ast::NameRef = find_node_at_offset(file.syntax(), offset).unwrap(); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mod.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mod.rs index 8317c56caf76e..eabdada67c2a7 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mod.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mod.rs @@ -16,7 +16,6 @@ mod proc_macros; use std::{any::TypeId, iter, ops::Range, sync}; -use base_db::RootQueryDb; use expect_test::Expect; use hir_expand::{ AstId, ExpansionInfo, InFile, MacroCallId, MacroCallKind, MacroKind, @@ -75,7 +74,7 @@ fn check_errors(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) let editioned_file_id = ast_id.file_id.file_id().expect("macros inside macros are not supported"); - let ast = db.parse(editioned_file_id).syntax_node(); + let ast = editioned_file_id.parse(&db).syntax_node(); let ast_id_map = db.ast_id_map(ast_id.file_id); let node = ast_id_map.get_erased(ast_id.value).to_node(&ast); Some((node.text_range(), errors)) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs index 5fda1beab4130..56b3f03f7b60b 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs @@ -346,7 +346,7 @@ impl ModuleOrigin { match self { &ModuleOrigin::File { definition: editioned_file_id, .. } | &ModuleOrigin::CrateRoot { definition: editioned_file_id } => { - let sf = db.parse(editioned_file_id).tree(); + let sf = editioned_file_id.parse(db).tree(); InFile::new(editioned_file_id.into(), ModuleSource::SourceFile(sf)) } &ModuleOrigin::Inline { definition, definition_tree_id } => InFile::new( diff --git a/src/tools/rust-analyzer/crates/hir-def/src/test_db.rs b/src/tools/rust-analyzer/crates/hir-def/src/test_db.rs index 0d260279f98cb..a616ef5b3f2d8 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/test_db.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/test_db.rs @@ -288,7 +288,7 @@ impl TestDB { let source_map = &Body::with_source_map(self, def_with_body).1; let scopes = ExprScopes::body_expr_scopes(self, def_with_body); - let root_syntax_node = self.parse(file_id).syntax_node(); + let root_syntax_node = file_id.parse(self).syntax_node(); let scope_iter = algo::ancestors_at_offset(&root_syntax_node, position.offset).filter_map(|node| { let block = ast::BlockExpr::cast(node)?; diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin/fn_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/fn_macro.rs index b3572a1cefcc1..9962677a9da6a 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/builtin/fn_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/fn_macro.rs @@ -828,7 +828,7 @@ fn include_expand( let span_map = db.real_span_map(editioned_file_id); // FIXME: Parse errors ExpandResult::ok(syntax_node_to_token_tree( - &db.parse(editioned_file_id).syntax_node(), + &editioned_file_id.parse(db).syntax_node(), SpanMap::RealSpanMap(span_map), span, syntax_bridge::DocCommentDesugarMode::ProcMacro, diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/db.rs b/src/tools/rust-analyzer/crates/hir-expand/src/db.rs index 020731cf9aca8..0c1c22fcb1a8e 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/db.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/db.rs @@ -343,7 +343,7 @@ fn ast_id_map(db: &dyn ExpandDatabase, file_id: HirFileId) -> AstIdMap { /// file or a macro expansion. fn parse_or_expand(db: &dyn ExpandDatabase, file_id: HirFileId) -> SyntaxNode { match file_id { - HirFileId::FileId(file_id) => db.parse(file_id).syntax_node(), + HirFileId::FileId(file_id) => file_id.parse(db).syntax_node(), HirFileId::MacroFile(macro_file) => { db.parse_macro_expansion(macro_file).value.0.syntax_node() } @@ -389,7 +389,7 @@ pub(crate) fn parse_with_map( ) -> (Parse, SpanMap) { match file_id { HirFileId::FileId(file_id) => { - (db.parse(file_id).to_syntax(), SpanMap::RealSpanMap(db.real_span_map(file_id))) + (file_id.parse(db).to_syntax(), SpanMap::RealSpanMap(db.real_span_map(file_id))) } HirFileId::MacroFile(macro_file) => { let (parse, map) = db.parse_macro_expansion(macro_file).value; diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/files.rs b/src/tools/rust-analyzer/crates/hir-expand/src/files.rs index fce92c8a3e5ea..71da560b15f1a 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/files.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/files.rs @@ -198,7 +198,7 @@ trait FileIdToSyntax: Copy { impl FileIdToSyntax for EditionedFileId { fn file_syntax(self, db: &dyn db::ExpandDatabase) -> SyntaxNode { - db.parse(self).syntax_node() + self.parse(db).syntax_node() } } impl FileIdToSyntax for MacroCallId { @@ -333,8 +333,8 @@ impl> InFile { )?; let kind = self.kind(); - let value = db - .parse(editioned_file_id) + let value = editioned_file_id + .parse(db) .syntax_node() .covering_element(range) .ancestors() @@ -521,7 +521,7 @@ impl InFile { )?; // FIXME: This heuristic is brittle and with the right macro may select completely unrelated nodes? - let anc = db.parse(editioned_file_id).syntax_node().covering_element(range); + let anc = editioned_file_id.parse(db).syntax_node().covering_element(range); let value = anc.ancestors().find_map(N::cast)?; Some(InRealFile::new(editioned_file_id, value)) } diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/span_map.rs b/src/tools/rust-analyzer/crates/hir-expand/src/span_map.rs index 71d0b880caa1e..aa8603341b3b2 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/span_map.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/span_map.rs @@ -86,7 +86,7 @@ pub(crate) fn real_span_map( let mut pairs = vec![(syntax::TextSize::new(0), span::ROOT_ERASED_FILE_AST_ID)]; let ast_id_map = db.ast_id_map(editioned_file_id.into()); - let tree = db.parse(editioned_file_id).tree(); + let tree = editioned_file_id.parse(db).tree(); // This is an incrementality layer. Basically we can't use absolute ranges for our spans as that // would mean we'd invalidate everything whenever we type. So instead we make the text ranges // relative to some AstIds reducing the risk of invalidation as typing somewhere no longer diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs index 4e9e3c44be113..65c6282f64293 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs @@ -458,7 +458,7 @@ impl<'db> SemanticsImpl<'db> { pub fn parse(&self, file_id: EditionedFileId) -> ast::SourceFile { let hir_file_id = file_id.into(); - let tree = self.db.parse(file_id).tree(); + let tree = file_id.parse(self.db).tree(); self.cache(tree.syntax().clone(), hir_file_id); tree } @@ -484,7 +484,7 @@ impl<'db> SemanticsImpl<'db> { pub fn parse_guess_edition(&self, file_id: FileId) -> ast::SourceFile { let file_id = self.attach_first_edition(file_id); - let tree = self.db.parse(file_id).tree(); + let tree = file_id.parse(self.db).tree(); self.cache(tree.syntax().clone(), file_id.into()); tree } @@ -2461,7 +2461,7 @@ fn macro_call_to_macro_id( Either::Left(it) => { let node = match it.file_id { HirFileId::FileId(file_id) => { - it.to_ptr(db).to_node(&db.parse(file_id).syntax_node()) + it.to_ptr(db).to_node(&file_id.parse(db).syntax_node()) } HirFileId::MacroFile(macro_file) => { let expansion_info = ctx.cache.get_or_insert_expansion(ctx.db, macro_file); @@ -2473,7 +2473,7 @@ fn macro_call_to_macro_id( Either::Right(it) => { let node = match it.file_id { HirFileId::FileId(file_id) => { - it.to_ptr(db).to_node(&db.parse(file_id).syntax_node()) + it.to_ptr(db).to_node(&file_id.parse(db).syntax_node()) } HirFileId::MacroFile(macro_file) => { let expansion_info = ctx.cache.get_or_insert_expansion(ctx.db, macro_file); 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 4fd0348156a59..4038eef3ecbfb 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/context.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/context.rs @@ -715,7 +715,7 @@ impl<'db> CompletionContext<'db> { // actual completion. let file_with_fake_ident = { let (_, edition) = editioned_file_id.unpack(db); - let parse = db.parse(editioned_file_id); + let parse = editioned_file_id.parse(db); parse.reparse(TextRange::empty(offset), COMPLETION_MARKER, edition).tree() }; 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 0c6953419f7d5..cc6bcb532a9dd 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs @@ -303,7 +303,8 @@ pub fn syntax_diagnostics( let (file_id, _) = editioned_file_id.unpack(db); // [#3434] Only take first 128 errors to prevent slowing down editor/ide, the number 128 is chosen arbitrarily. - db.parse_errors(editioned_file_id) + editioned_file_id + .parse_errors(db) .into_iter() .flatten() .take(128) @@ -375,7 +376,7 @@ pub fn semantic_diagnostics( // A bunch of parse errors in a file indicate some bigger structural parse changes in the // file, so we skip semantic diagnostics so we can show these faster. Some(m) => { - if db.parse_errors(editioned_file_id).is_none_or(|es| es.len() < 16) { + if editioned_file_id.parse_errors(db).is_none_or(|es| es.len() < 16) { m.diagnostics(db, &mut diags, config.style_lints); } } diff --git a/src/tools/rust-analyzer/crates/ide-ssr/src/from_comment.rs b/src/tools/rust-analyzer/crates/ide-ssr/src/from_comment.rs index 181cc74a51d4f..83b8c3dc81ea6 100644 --- a/src/tools/rust-analyzer/crates/ide-ssr/src/from_comment.rs +++ b/src/tools/rust-analyzer/crates/ide-ssr/src/from_comment.rs @@ -1,7 +1,7 @@ //! This module allows building an SSR MatchFinder by parsing the SSR rule //! from a comment. -use ide_db::{EditionedFileId, FilePosition, FileRange, RootDatabase, base_db::RootQueryDb}; +use ide_db::{EditionedFileId, FilePosition, FileRange, RootDatabase}; use syntax::{ TextRange, ast::{self, AstNode, AstToken}, @@ -19,7 +19,7 @@ pub fn ssr_from_comment( let comment = { let file_id = EditionedFileId::current_edition(db, frange.file_id); - let file = db.parse(file_id); + let file = file_id.parse(db); file.tree().syntax().token_at_offset(frange.range.start()).find_map(ast::Comment::cast) }?; let comment_text_without_prefix = comment.text().strip_prefix(comment.prefix()).unwrap(); diff --git a/src/tools/rust-analyzer/crates/ide/src/lib.rs b/src/tools/rust-analyzer/crates/ide/src/lib.rs index 196ada2a6eb1f..610420bc2baeb 100644 --- a/src/tools/rust-analyzer/crates/ide/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide/src/lib.rs @@ -342,7 +342,7 @@ impl Analysis { self.with_db(|db| { let editioned_file_id_wrapper = EditionedFileId::current_edition(&self.db, file_id); - db.parse(editioned_file_id_wrapper).tree() + editioned_file_id_wrapper.parse(db).tree() }) } @@ -370,7 +370,7 @@ impl Analysis { pub fn matching_brace(&self, position: FilePosition) -> Cancellable> { self.with_db(|db| { let file_id = EditionedFileId::current_edition(&self.db, position.file_id); - let parse = db.parse(file_id); + let parse = file_id.parse(db); let file = parse.tree(); matching_brace::matching_brace(&file, position.offset) }) @@ -431,7 +431,7 @@ impl Analysis { self.with_db(|db| { let editioned_file_id_wrapper = EditionedFileId::current_edition(&self.db, frange.file_id); - let parse = db.parse(editioned_file_id_wrapper); + let parse = editioned_file_id_wrapper.parse(db); join_lines::join_lines(config, &parse.tree(), frange.range) }) } @@ -472,7 +472,7 @@ impl Analysis { // FIXME: Edition self.with_db(|db| { let editioned_file_id_wrapper = EditionedFileId::current_edition(&self.db, file_id); - let source_file = db.parse(editioned_file_id_wrapper).tree(); + let source_file = editioned_file_id_wrapper.parse(db).tree(); file_structure::file_structure(&source_file, config) }) } @@ -505,7 +505,7 @@ impl Analysis { let editioned_file_id_wrapper = EditionedFileId::current_edition(&self.db, file_id); folding_ranges::folding_ranges( - &db.parse(editioned_file_id_wrapper).tree(), + &editioned_file_id_wrapper.parse(db).tree(), collapsed_text, ) }) diff --git a/src/tools/rust-analyzer/crates/ide/src/typing.rs b/src/tools/rust-analyzer/crates/ide/src/typing.rs index e8b0c92dcb207..9c8782cdb2dcc 100644 --- a/src/tools/rust-analyzer/crates/ide/src/typing.rs +++ b/src/tools/rust-analyzer/crates/ide/src/typing.rs @@ -76,7 +76,7 @@ pub(crate) fn on_char_typed( .copied() .map_or(Edition::CURRENT, |krate| krate.data(db).edition); let editioned_file_id_wrapper = EditionedFileId::new(db, position.file_id, edition); - let file = &db.parse(editioned_file_id_wrapper); + let file = &editioned_file_id_wrapper.parse(db); let char_matches_position = file.tree().syntax().text().char_at(position.offset) == Some(char_typed); if !stdx::always!(char_matches_position) { diff --git a/src/tools/rust-analyzer/crates/ide/src/typing/on_enter.rs b/src/tools/rust-analyzer/crates/ide/src/typing/on_enter.rs index fdc583a15cc71..82f12783980d2 100644 --- a/src/tools/rust-analyzer/crates/ide/src/typing/on_enter.rs +++ b/src/tools/rust-analyzer/crates/ide/src/typing/on_enter.rs @@ -1,7 +1,6 @@ //! Handles the `Enter` key press. At the momently, this only continues //! comments, but should handle indent some time in the future as well. -use ide_db::base_db::RootQueryDb; use ide_db::{FilePosition, RootDatabase}; use syntax::{ AstNode, SmolStr, SourceFile, @@ -52,7 +51,7 @@ use ide_db::text_edit::TextEdit; pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option { let editioned_file_id_wrapper = ide_db::base_db::EditionedFileId::current_edition(db, position.file_id); - let parse = db.parse(editioned_file_id_wrapper); + let parse = editioned_file_id_wrapper.parse(db); let file = parse.tree(); let token = file.syntax().token_at_offset(position.offset).left_biased()?; From bd8cb7bb22a71bfdab4a98414619a4f4e42b502a Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 3 Apr 2026 15:50:55 +0200 Subject: [PATCH 071/144] Remove `RootQueryDb` --- .../crates/base-db/src/change.rs | 5 +- .../rust-analyzer/crates/base-db/src/input.rs | 22 ++--- .../rust-analyzer/crates/base-db/src/lib.rs | 88 ++++++++++++------- .../rust-analyzer/crates/hir-def/src/db.rs | 4 +- .../crates/hir-def/src/import_map.rs | 6 +- .../crates/hir-def/src/nameres/tests.rs | 1 - .../hir-def/src/nameres/tests/incremental.rs | 12 +-- .../hir-def/src/nameres/tests/macros.rs | 5 +- .../crates/hir-def/src/test_db.rs | 11 +-- .../rust-analyzer/crates/hir-expand/src/db.rs | 4 +- .../crates/hir-ty/src/consteval/tests.rs | 4 +- .../crates/hir-ty/src/test_db.rs | 10 +-- src/tools/rust-analyzer/crates/hir/src/lib.rs | 18 ++-- .../rust-analyzer/crates/hir/src/semantics.rs | 6 +- .../crates/hir/src/semantics/source_to_def.rs | 5 +- .../crates/ide-completion/src/context.rs | 4 +- .../rust-analyzer/crates/ide-db/src/lib.rs | 8 +- .../crates/ide-db/src/prime_caches.rs | 11 +-- .../rust-analyzer/crates/ide-db/src/search.rs | 4 +- .../crates/ide-db/src/symbol_index.rs | 16 ++-- .../src/handlers/unlinked_file.rs | 6 +- .../crates/ide-diagnostics/src/lib.rs | 6 +- .../crates/ide-ssr/src/matching.rs | 4 +- .../rust-analyzer/crates/ide/src/doc_links.rs | 4 +- .../crates/ide/src/expand_macro.rs | 4 +- .../crates/ide/src/fetch_crates.rs | 4 +- src/tools/rust-analyzer/crates/ide/src/lib.rs | 5 +- .../crates/ide/src/navigation_target.rs | 5 +- .../crates/ide/src/parent_module.rs | 4 +- .../rust-analyzer/crates/ide/src/runnables.rs | 4 +- .../crates/ide/src/static_index.rs | 14 +-- .../crates/ide/src/test_explorer.rs | 8 +- .../rust-analyzer/crates/ide/src/typing.rs | 5 +- .../crates/ide/src/view_crate_graph.rs | 7 +- .../crates/load-cargo/src/lib.rs | 4 +- .../crates/test-fixture/src/lib.rs | 4 +- 36 files changed, 170 insertions(+), 162 deletions(-) diff --git a/src/tools/rust-analyzer/crates/base-db/src/change.rs b/src/tools/rust-analyzer/crates/base-db/src/change.rs index c728f3e5ca83c..4d4bf78cbc0b9 100644 --- a/src/tools/rust-analyzer/crates/base-db/src/change.rs +++ b/src/tools/rust-analyzer/crates/base-db/src/change.rs @@ -9,7 +9,8 @@ use triomphe::Arc; use vfs::FileId; use crate::{ - CrateGraphBuilder, CratesIdMap, LibraryRoots, LocalRoots, RootQueryDb, SourceRoot, SourceRootId, + CrateGraphBuilder, CratesIdMap, LibraryRoots, LocalRoots, SourceDatabase, SourceRoot, + SourceRootId, }; /// Encapsulate a bunch of raw `.set` calls on the database. @@ -49,7 +50,7 @@ impl FileChange { self.crate_graph = Some(graph); } - pub fn apply(self, db: &mut dyn RootQueryDb) -> Option { + pub fn apply(self, db: &mut dyn SourceDatabase) -> Option { let _p = tracing::info_span!("FileChange::apply").entered(); if let Some(roots) = self.roots { let mut local_roots = FxHashSet::default(); diff --git a/src/tools/rust-analyzer/crates/base-db/src/input.rs b/src/tools/rust-analyzer/crates/base-db/src/input.rs index 4f32abafd77d1..38f9c5a5a14c4 100644 --- a/src/tools/rust-analyzer/crates/base-db/src/input.rs +++ b/src/tools/rust-analyzer/crates/base-db/src/input.rs @@ -21,7 +21,10 @@ use span::Edition; use triomphe::Arc; use vfs::{AbsPathBuf, AnchoredPath, FileId, VfsPath, file_set::FileSet}; -use crate::{CrateWorkspaceData, EditionedFileId, FxIndexSet, RootQueryDb}; +use crate::{ + CrateWorkspaceData, EditionedFileId, FxIndexSet, SourceDatabase, all_crates, + set_all_crates_with_durability, +}; pub type ProcMacroPaths = FxHashMap>; @@ -490,13 +493,13 @@ impl Crate { /// including the crate itself. /// /// **Warning**: do not use this query in `hir-*` crates! It kills incrementality across crate metadata modifications. - pub fn transitive_rev_deps(self, db: &dyn RootQueryDb) -> Box<[Crate]> { + pub fn transitive_rev_deps(self, db: &dyn SourceDatabase) -> Box<[Crate]> { let mut worklist = vec![self]; let mut rev_deps = FxHashSet::default(); rev_deps.insert(self); let mut inverted_graph = FxHashMap::<_, Vec<_>>::default(); - db.all_crates().iter().for_each(|&krate| { + all_crates(db).iter().for_each(|&krate| { krate .data(db) .dependencies @@ -586,15 +589,15 @@ impl CrateGraphBuilder { Ok(()) } - pub fn set_in_db(self, db: &mut dyn RootQueryDb) -> CratesIdMap { + pub fn set_in_db(self, db: &mut dyn SourceDatabase) -> CratesIdMap { + let old_all_crates = all_crates(db); + // For some reason in some repositories we have duplicate crates, so we use a set and not `Vec`. // We use an `IndexSet` because the list needs to be topologically sorted. let mut all_crates = FxIndexSet::with_capacity_and_hasher(self.arena.len(), FxBuildHasher); let mut visited = FxHashMap::default(); let mut visited_root_files = FxHashSet::default(); - let old_all_crates = db.all_crates(); - let crates_map = db.crates_map(); // salsa doesn't compare new input to old input to see if they are the same, so here we are doing all the work ourselves. for krate in self.iter() { @@ -612,17 +615,14 @@ impl CrateGraphBuilder { if old_all_crates.len() != all_crates.len() || old_all_crates.iter().any(|&krate| !all_crates.contains(&krate)) { - db.set_all_crates_with_durability( - Arc::new(Vec::from_iter(all_crates).into_boxed_slice()), - Durability::MEDIUM, - ); + set_all_crates_with_durability(db, all_crates, Durability::MEDIUM); } return visited; fn go( graph: &CrateGraphBuilder, - db: &mut dyn RootQueryDb, + db: &mut dyn SourceDatabase, crates_map: &CratesMap, visited: &mut FxHashMap, visited_root_files: &mut FxHashSet, diff --git a/src/tools/rust-analyzer/crates/base-db/src/lib.rs b/src/tools/rust-analyzer/crates/base-db/src/lib.rs index b1cb1b32020e3..26eef9ac0d63f 100644 --- a/src/tools/rust-analyzer/crates/base-db/src/lib.rs +++ b/src/tools/rust-analyzer/crates/base-db/src/lib.rs @@ -235,27 +235,6 @@ pub struct SourceRootInput { pub source_root: Arc, } -/// Database which stores all significant input facts: source code and project -/// model. Everything else in rust-analyzer is derived from these queries. -#[query_group::query_group] -pub trait RootQueryDb: SourceDatabase { - #[salsa::transparent] - fn toolchain_channel(&self, krate: Crate) -> Option; - - /// Crates whose root file is in `id`. - #[salsa::invoke_interned(source_root_crates)] - fn source_root_crates(&self, id: SourceRootId) -> Arc<[Crate]>; - - #[salsa::transparent] - fn relevant_crates(&self, file_id: FileId) -> Arc<[Crate]>; - - /// Returns the crates in topological order. - /// - /// **Warning**: do not use this query in `hir-*` crates! It kills incrementality across crate metadata modifications. - #[salsa::input] - fn all_crates(&self) -> Arc>; -} - #[salsa_macros::db] pub trait SourceDatabase: salsa::Database { /// Text of the file. @@ -343,27 +322,68 @@ impl CrateWorkspaceData { } } -fn toolchain_channel(db: &dyn RootQueryDb, krate: Crate) -> Option { +pub fn toolchain_channel(db: &dyn salsa::Database, krate: Crate) -> Option { krate.workspace_data(db).toolchain.as_ref().and_then(|v| ReleaseChannel::from_str(&v.pre)) } -fn source_root_crates(db: &dyn RootQueryDb, id: SourceRootId) -> Arc<[Crate]> { - let crates = db.all_crates(); - crates - .iter() - .copied() - .filter(|&krate| { - let root_file = krate.data(db).root_file_id; - db.file_source_root(root_file).source_root_id(db) == id - }) - .collect() +#[salsa::input(singleton, debug)] +struct AllCrates { + crates: std::sync::Arc<[Crate]>, +} + +pub fn set_all_crates_with_durability( + db: &mut dyn salsa::Database, + crates: impl IntoIterator, + durability: Durability, +) { + AllCrates::try_get(db) + .unwrap_or_else(|| AllCrates::new(db, std::sync::Arc::default())) + .set_crates(db) + .with_durability(durability) + .to(crates.into_iter().collect()); +} + +/// Returns the crates in topological order. +/// +/// **Warning**: do not use this query in `hir-*` crates! It kills incrementality across crate metadata modifications. +pub fn all_crates(db: &dyn salsa::Database) -> std::sync::Arc<[Crate]> { + AllCrates::try_get(db) + .map_or(std::sync::Arc::default(), |all_crates| all_crates.crates(db).into()) +} + +// FIXME: VFS rewrite should allow us to get rid of this wrapper +#[doc(hidden)] +#[salsa::interned] +pub struct InternedSourceRootId { + pub id: SourceRootId, +} + +/// Crates whose root file is in `id`. +pub fn source_root_crates(db: &dyn SourceDatabase, id: SourceRootId) -> &[Crate] { + #[salsa::tracked(returns(deref))] + pub fn source_root_crates<'db>( + db: &'db dyn SourceDatabase, + id: InternedSourceRootId<'db>, + ) -> Box<[Crate]> { + let crates = AllCrates::get(db).crates(db); + let id = id.id(db); + crates + .iter() + .copied() + .filter(|&krate| { + let root_file = krate.data(db).root_file_id; + db.file_source_root(root_file).source_root_id(db) == id + }) + .collect() + } + source_root_crates(db, InternedSourceRootId::new(db, id)) } -fn relevant_crates(db: &dyn RootQueryDb, file_id: FileId) -> Arc<[Crate]> { +pub fn relevant_crates(db: &dyn SourceDatabase, file_id: FileId) -> &[Crate] { let _p = tracing::info_span!("relevant_crates").entered(); let source_root = db.file_source_root(file_id); - db.source_root_crates(source_root.source_root_id(db)) + source_root_crates(db, source_root.source_root_id(db)) } #[must_use] diff --git a/src/tools/rust-analyzer/crates/hir-def/src/db.rs b/src/tools/rust-analyzer/crates/hir-def/src/db.rs index 5d5d435398228..9dd7768ead868 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/db.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/db.rs @@ -1,5 +1,5 @@ //! Defines database & queries for name resolution. -use base_db::{Crate, RootQueryDb, SourceDatabase}; +use base_db::{Crate, SourceDatabase}; use hir_expand::{ EditionedFileId, HirFileId, InFile, Lookup, MacroCallId, MacroDefId, MacroDefKind, db::ExpandDatabase, @@ -22,7 +22,7 @@ use crate::{ use salsa::plumbing::AsId; #[query_group::query_group(InternDatabaseStorage)] -pub trait InternDatabase: RootQueryDb { +pub trait InternDatabase: SourceDatabase { // region: items #[salsa::interned] fn intern_use(&self, loc: UseLoc) -> UseId; diff --git a/src/tools/rust-analyzer/crates/hir-def/src/import_map.rs b/src/tools/rust-analyzer/crates/hir-def/src/import_map.rs index 0014e1af5c0d0..ba077b1b2ef5d 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/import_map.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/import_map.rs @@ -499,7 +499,7 @@ fn search_maps( #[cfg(test)] mod tests { - use base_db::RootQueryDb; + use base_db::all_crates; use expect_test::{Expect, expect}; use test_fixture::WithFixture; @@ -536,7 +536,7 @@ mod tests { expect: Expect, ) { let db = TestDB::with_files(ra_fixture); - let all_crates = db.all_crates(); + let all_crates = all_crates(&db); let krate = all_crates .iter() .copied() @@ -616,7 +616,7 @@ mod tests { fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) { let db = TestDB::with_files(ra_fixture); - let all_crates = db.all_crates(); + let all_crates = all_crates(&db); let actual = all_crates .iter() diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests.rs index fe55252e25404..08d98dff33d3c 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests.rs @@ -4,7 +4,6 @@ mod incremental; mod macros; mod mod_resolution; -use base_db::RootQueryDb; use expect_test::{Expect, expect}; use test_fixture::WithFixture; diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs index 5b75c078ecfad..82d7a7114ae73 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs @@ -1,6 +1,6 @@ use base_db::{ CrateDisplayName, CrateGraphBuilder, CrateName, CrateOrigin, CrateWorkspaceData, - DependencyBuilder, Env, RootQueryDb, SourceDatabase, + DependencyBuilder, Env, SourceDatabase, all_crates, }; use expect_test::{Expect, expect}; use intern::Symbol; @@ -56,11 +56,11 @@ pub const BAZ: u32 = 0; "#, ); - for &krate in db.all_crates().iter() { + for &krate in all_crates(&db).iter() { crate_def_map(&db, krate); } - let all_crates_before = db.all_crates(); + let all_crates_before = all_crates(&db); { // Add dependencies: c -> b, b -> a. @@ -100,15 +100,15 @@ pub const BAZ: u32 = 0; new_crate_graph.set_in_db(&mut db); } - let all_crates_after = db.all_crates(); + let all_crates_after = all_crates(&db); assert!( - Arc::ptr_eq(&all_crates_before, &all_crates_after), + std::sync::Arc::ptr_eq(&all_crates_before, &all_crates_after), "the all_crates list should not have been invalidated" ); execute_assert_events( &db, || { - for &krate in db.all_crates().iter() { + for &krate in all_crates(&db).iter() { crate_def_map(&db, krate); } }, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/macros.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/macros.rs index a013f8b2bc1a4..f073cf777dda7 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/macros.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/macros.rs @@ -1,3 +1,4 @@ +use base_db::all_crates; use expect_test::expect; use itertools::Itertools; @@ -1129,7 +1130,7 @@ pub fn derive_macro_2(_item: TokenStream) -> TokenStream { } "#, ); - let krate = *db.all_crates().last().expect("no crate graph present"); + let krate = *all_crates(&db).last().expect("no crate graph present"); let def_map = crate_def_map(&db, krate); assert_eq!(def_map.data.exported_derives.len(), 1); @@ -1497,7 +1498,7 @@ struct TokenStream; fn proc_attr(a: TokenStream, b: TokenStream) -> TokenStream { a } "#, ); - let krate = *db.all_crates().last().expect("no crate graph present"); + let krate = *all_crates(&db).last().expect("no crate graph present"); let def_map = crate_def_map(&db, krate); let root_module = &def_map[def_map.root].scope; diff --git a/src/tools/rust-analyzer/crates/hir-def/src/test_db.rs b/src/tools/rust-analyzer/crates/hir-def/src/test_db.rs index a616ef5b3f2d8..b854d2aa218de 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/test_db.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/test_db.rs @@ -3,8 +3,9 @@ use std::{fmt, panic, sync::Mutex}; use base_db::{ - Crate, CrateGraphBuilder, CratesMap, FileSourceRootInput, FileText, Nonce, RootQueryDb, - SourceDatabase, SourceRoot, SourceRootId, SourceRootInput, + Crate, CrateGraphBuilder, CratesMap, FileSourceRootInput, FileText, Nonce, SourceDatabase, + SourceRoot, SourceRootId, SourceRootInput, all_crates, relevant_crates, + set_all_crates_with_durability, }; use hir_expand::{InFile, files::FilePosition}; use salsa::Durability; @@ -49,7 +50,7 @@ impl Default for TestDB { }; this.set_expand_proc_attr_macros_with_durability(true, Durability::HIGH); // This needs to be here otherwise `CrateGraphBuilder` panics. - this.set_all_crates(Arc::new(Box::new([]))); + set_all_crates_with_durability(&mut this, std::iter::empty(), Durability::HIGH); _ = base_db::LibraryRoots::builder(Default::default()) .durability(Durability::MEDIUM) .new(&this); @@ -145,7 +146,7 @@ impl SourceDatabase for TestDB { impl TestDB { pub(crate) fn fetch_test_crate(&self) -> Crate { - let all_crates = self.all_crates(); + let all_crates = all_crates(self); all_crates .iter() .copied() @@ -157,7 +158,7 @@ impl TestDB { } pub(crate) fn module_for_file(&self, file_id: FileId) -> ModuleId { - for &krate in self.relevant_crates(file_id).iter() { + for &krate in relevant_crates(self, file_id).iter() { let crate_def_map = crate_def_map(self, krate); for (local_id, data) in crate_def_map.modules() { if data.origin.file_id().map(|file_id| file_id.file_id(self)) == Some(file_id) { diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/db.rs b/src/tools/rust-analyzer/crates/hir-expand/src/db.rs index 0c1c22fcb1a8e..8a6b56d93226d 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/db.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/db.rs @@ -1,6 +1,6 @@ //! Defines database & queries for macro expansion. -use base_db::{Crate, RootQueryDb}; +use base_db::{Crate, SourceDatabase}; use mbe::MatchedArmIndex; use span::{AstIdMap, Edition, Span, SyntaxContext}; use syntax::{AstNode, Parse, SyntaxError, SyntaxNode, SyntaxToken, T, ast}; @@ -48,7 +48,7 @@ pub enum TokenExpander { } #[query_group::query_group] -pub trait ExpandDatabase: RootQueryDb { +pub trait ExpandDatabase: SourceDatabase { /// The proc macros. Do not use this! Use `proc_macros_for_crate()` instead. #[salsa::input] fn proc_macros(&self) -> Arc; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests.rs b/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests.rs index 31cf86476f9a4..aee27dcfdef9e 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests.rs @@ -1,4 +1,4 @@ -use base_db::RootQueryDb; +use base_db::all_crates; use hir_def::signatures::ConstSignature; use hir_expand::EditionedFileId; use rustc_apfloat::{ @@ -108,7 +108,7 @@ fn pretty_print_err(e: ConstEvalError, db: &TestDB) -> String { let mut err = String::new(); let span_formatter = |file, range| format!("{file:?} {range:?}"); let display_target = - DisplayTarget::from_crate(db, *db.all_crates().last().expect("no crate graph present")); + DisplayTarget::from_crate(db, *all_crates(db).last().expect("no crate graph present")); match e { ConstEvalError::MirLowerError(e) => { e.pretty_print(&mut err, db, span_formatter, display_target) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/test_db.rs b/src/tools/rust-analyzer/crates/hir-ty/src/test_db.rs index 243456c85fc48..e19e26ebc4064 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/test_db.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/test_db.rs @@ -3,8 +3,8 @@ use std::{fmt, panic, sync::Mutex}; use base_db::{ - CrateGraphBuilder, CratesMap, FileSourceRootInput, FileText, Nonce, RootQueryDb, - SourceDatabase, SourceRoot, SourceRootId, SourceRootInput, + CrateGraphBuilder, CratesMap, FileSourceRootInput, FileText, Nonce, SourceDatabase, SourceRoot, + SourceRootId, SourceRootInput, all_crates, relevant_crates, set_all_crates_with_durability, }; use hir_def::{ModuleId, db::DefDatabase, nameres::crate_def_map}; @@ -45,7 +45,7 @@ impl Default for TestDB { }; this.set_expand_proc_attr_macros_with_durability(true, Durability::HIGH); // This needs to be here otherwise `CrateGraphBuilder` panics. - this.set_all_crates(Arc::new(Box::new([]))); + set_all_crates_with_durability(&mut this, std::iter::empty(), Durability::HIGH); _ = base_db::LibraryRoots::builder(Default::default()) .durability(Durability::MEDIUM) .new(&this); @@ -142,7 +142,7 @@ impl panic::RefUnwindSafe for TestDB {} impl TestDB { pub(crate) fn module_for_file_opt(&self, file_id: impl Into) -> Option { let file_id = file_id.into(); - for &krate in self.relevant_crates(file_id).iter() { + for &krate in relevant_crates(self, file_id).iter() { let crate_def_map = crate_def_map(self, krate); for (module_id, data) in crate_def_map.modules() { if data.origin.file_id().map(|file_id| file_id.file_id(self)) == Some(file_id) { @@ -161,7 +161,7 @@ impl TestDB { &self, ) -> FxHashMap> { let mut files = Vec::new(); - for &krate in self.all_crates().iter() { + for &krate in all_crates(self).iter() { let crate_def_map = crate_def_map(self, krate); for (module_id, _) in crate_def_map.modules() { let file_id = crate_def_map[module_id].origin.file_id(); diff --git a/src/tools/rust-analyzer/crates/hir/src/lib.rs b/src/tools/rust-analyzer/crates/hir/src/lib.rs index eb5b3b37a66db..89f3cfd140980 100644 --- a/src/tools/rust-analyzer/crates/hir/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir/src/lib.rs @@ -45,7 +45,7 @@ use std::{ }; use arrayvec::ArrayVec; -use base_db::{CrateDisplayName, CrateOrigin, LangCrateOrigin}; +use base_db::{CrateDisplayName, CrateOrigin, LangCrateOrigin, all_crates}; use either::Either; use hir_def::{ AdtId, AssocItemId, AssocItemLoc, BuiltinDeriveImplId, CallableDefId, ConstId, ConstParamId, @@ -243,7 +243,7 @@ impl Crate { } pub fn reverse_dependencies(self, db: &dyn HirDatabase) -> Vec { - let all_crates = db.all_crates(); + let all_crates = all_crates(db); all_crates .iter() .copied() @@ -310,7 +310,7 @@ impl Crate { } pub fn all(db: &dyn HirDatabase) -> Vec { - db.all_crates().iter().map(|&id| Crate { id }).collect() + all_crates(db).iter().map(|&id| Crate { id }).collect() } /// Try to get the root URL of the documentation of a crate. @@ -334,7 +334,7 @@ impl Crate { } fn core(db: &dyn HirDatabase) -> Option { - db.all_crates() + all_crates(db) .iter() .copied() .find(|&krate| { @@ -547,7 +547,7 @@ impl HasCrate for ModuleDef { fn krate(&self, db: &dyn HirDatabase) -> Crate { match self.module(db) { Some(module) => module.krate(db), - None => Crate::core(db).unwrap_or_else(|| db.all_crates()[0].into()), + None => Crate::core(db).unwrap_or_else(|| all_crates(db)[0].into()), } } } @@ -3394,7 +3394,7 @@ impl BuiltinType { } pub fn ty<'db>(self, db: &'db dyn HirDatabase) -> Type<'db> { - let core = Crate::core(db).map(|core| core.id).unwrap_or_else(|| db.all_crates()[0]); + let core = Crate::core(db).map(|core| core.id).unwrap_or_else(|| all_crates(db)[0]); let interner = DbInterner::new_no_crate(db); Type::new_for_crate(core, Ty::from_builtin_type(interner, self.inner)) } @@ -4898,12 +4898,12 @@ impl Impl { std::iter::successors(module.block(db), |block| block.loc(db).module.block(db)) .filter_map(|block| TraitImpls::for_block(db, block).as_deref()) .for_each(|impls| impls.for_self_ty(&simplified_ty, &mut extend_with_impls)); - for &krate in &**db.all_crates() { + for &krate in &*all_crates(db) { TraitImpls::for_crate(db, krate) .for_self_ty(&simplified_ty, &mut extend_with_impls); } } else { - for &krate in &**db.all_crates() { + for &krate in &*all_crates(db) { TraitImpls::for_crate(db, krate) .for_self_ty(&simplified_ty, &mut extend_with_impls); } @@ -7175,7 +7175,7 @@ pub fn resolve_absolute_path<'a, I: Iterator + Clone + 'a>( .next() .into_iter() .flat_map(move |crate_name| { - db.all_crates() + all_crates(db) .iter() .filter(|&krate| { krate diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs index 65c6282f64293..9a31a08ffb52d 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs @@ -10,7 +10,7 @@ use std::{ ops::{self, ControlFlow, Not}, }; -use base_db::FxIndexSet; +use base_db::{FxIndexSet, all_crates, toolchain_channel}; use either::Either; use hir_def::{ BuiltinDeriveImplId, DefWithBodyId, ExpressionStoreOwnerId, HasModule, MacroId, StructId, @@ -392,7 +392,7 @@ impl Semantics<'_, DB> { } pub fn is_nightly(&self, krate: Crate) -> bool { - let toolchain = self.db.toolchain_channel(krate.into()); + let toolchain = toolchain_channel(self.db.as_dyn_database(), krate.into()); // `toolchain == None` means we're in some detached files. Since we have no information on // the toolchain being used, let's just allow unstable items to be listed. matches!(toolchain, Some(base_db::ReleaseChannel::Nightly) | None) @@ -467,7 +467,7 @@ impl<'db> SemanticsImpl<'db> { pub fn first_crate(&self, file: FileId) -> Option { match self.file_to_module_defs(file).next() { Some(module) => Some(module.krate(self.db)), - None => self.db.all_crates().last().copied().map(Into::into), + None => all_crates(self.db).last().copied().map(Into::into), } } diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs b/src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs index a9a779a287d6a..59bccc22d8de6 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs @@ -85,6 +85,7 @@ //! active crate for a given position, and then provide an API to resolve all //! syntax nodes against this specific crate. +use base_db::relevant_crates; use either::Either; use hir_def::{ AdtId, BlockId, BuiltinDeriveImplId, ConstId, ConstParamId, DefWithBodyId, EnumId, @@ -145,7 +146,7 @@ impl SourceToDefCache { return m; } self.included_file_cache.insert(file, None); - for &crate_id in db.relevant_crates(file.file_id(db)).iter() { + for &crate_id in relevant_crates(db, file.file_id(db)).iter() { db.include_macro_invoc(crate_id).iter().for_each(|&(macro_call_id, file_id)| { self.included_file_cache.insert(file_id, Some(macro_call_id)); }); @@ -180,7 +181,7 @@ impl SourceToDefCtx<'_, '_> { self.cache.file_to_def_cache.entry(file).or_insert_with(|| { let mut mods = SmallVec::new(); - for &crate_id in self.db.relevant_crates(file).iter() { + for &crate_id in relevant_crates(self.db, file).iter() { // Note: `mod` declarations in block modules cannot be supported here let crate_def_map = crate_def_map(self.db, crate_id); let n_mods = mods.len(); 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 4038eef3ecbfb..a91f123176e0c 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/context.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/context.rs @@ -6,7 +6,7 @@ mod tests; use std::iter; -use base_db::RootQueryDb as _; +use base_db::toolchain_channel; use hir::{ DisplayTarget, HasAttrs, InFile, Local, ModuleDef, ModuleSource, Name, PathResolution, ScopeDef, Semantics, SemanticsScope, Symbol, Type, TypeInfo, @@ -768,7 +768,7 @@ impl<'db> CompletionContext<'db> { let containing_function = scope.containing_function(); let edition = krate.edition(db); - let toolchain = db.toolchain_channel(krate.into()); + let toolchain = toolchain_channel(db, krate.into()); // `toolchain == None` means we're in some detached files. Since we have no information on // the toolchain being used, let's just allow unstable items to be listed. let is_nightly = matches!(toolchain, Some(base_db::ReleaseChannel::Nightly) | None); diff --git a/src/tools/rust-analyzer/crates/ide-db/src/lib.rs b/src/tools/rust-analyzer/crates/ide-db/src/lib.rs index cde0705d8ac2e..8d16826e191da 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/lib.rs @@ -60,8 +60,8 @@ use salsa::Durability; use std::{fmt, mem::ManuallyDrop}; use base_db::{ - CrateGraphBuilder, CratesMap, FileSourceRootInput, FileText, Files, Nonce, RootQueryDb, - SourceDatabase, SourceRoot, SourceRootId, SourceRootInput, query_group, + CrateGraphBuilder, CratesMap, FileSourceRootInput, FileText, Files, Nonce, SourceDatabase, + SourceRoot, SourceRootId, SourceRootInput, query_group, set_all_crates_with_durability, }; use hir::{ FilePositionWrapper, FileRangeWrapper, @@ -197,7 +197,7 @@ impl RootDatabase { nonce: Nonce::new(), }; // This needs to be here otherwise `CrateGraphBuilder` will panic. - db.set_all_crates(Arc::new(Box::new([]))); + set_all_crates_with_durability(&mut db, std::iter::empty(), Durability::HIGH); CrateGraphBuilder::default().set_in_db(&mut db); db.set_proc_macros_with_durability(Default::default(), Durability::MEDIUM); _ = base_db::LibraryRoots::builder(Default::default()) @@ -253,7 +253,7 @@ impl RootDatabase { } #[query_group::query_group] -pub trait LineIndexDatabase: base_db::RootQueryDb { +pub trait LineIndexDatabase: base_db::SourceDatabase { #[salsa::invoke_interned(line_index)] fn line_index(&self, file_id: FileId) -> Arc; } diff --git a/src/tools/rust-analyzer/crates/ide-db/src/prime_caches.rs b/src/tools/rust-analyzer/crates/ide-db/src/prime_caches.rs index d264428212cb0..12a48d65ac8c5 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/prime_caches.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/prime_caches.rs @@ -4,15 +4,12 @@ //! various caches, it's not really advanced at the moment. use std::panic::AssertUnwindSafe; +use base_db::all_crates; use hir::{Symbol, import_map::ImportMap}; use rustc_hash::FxHashMap; use salsa::{Cancelled, Database}; -use crate::{ - FxIndexMap, RootDatabase, - base_db::{Crate, RootQueryDb}, - symbol_index::SymbolIndex, -}; +use crate::{FxIndexMap, RootDatabase, base_db::Crate, symbol_index::SymbolIndex}; /// We're indexing many crates. #[derive(Debug)] @@ -56,7 +53,7 @@ pub fn parallel_prime_caches( // to compute the symbols/import map of an already computed def map in that time. let (reverse_deps, mut to_be_done_deps) = { - let all_crates = db.all_crates(); + let all_crates = all_crates(db); let to_be_done_deps = all_crates .iter() .map(|&krate| (krate, krate.data(db).dependencies.len() as u32)) @@ -200,7 +197,7 @@ pub fn parallel_prime_caches( ) }; - let crate_def_maps_total = db.all_crates().len(); + let crate_def_maps_total = all_crates(db).len(); let mut crate_def_maps_done = 0; let (mut crate_import_maps_total, mut crate_import_maps_done) = (0usize, 0usize); let (mut module_symbols_total, mut module_symbols_done) = (0usize, 0usize); diff --git a/src/tools/rust-analyzer/crates/ide-db/src/search.rs b/src/tools/rust-analyzer/crates/ide-db/src/search.rs index 25acb47f7b4c8..69459a4b72dac 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/search.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/search.rs @@ -7,7 +7,7 @@ use std::mem; use std::{cell::LazyCell, cmp::Reverse}; -use base_db::{RootQueryDb, SourceDatabase}; +use base_db::{SourceDatabase, all_crates}; use either::Either; use hir::{ Adt, AsAssocItem, DefWithBody, EditionedFileId, ExpressionStoreOwner, FileRange, @@ -161,7 +161,7 @@ impl SearchScope { fn crate_graph(db: &RootDatabase) -> SearchScope { let mut entries = FxHashMap::default(); - let all_crates = db.all_crates(); + let all_crates = all_crates(db); for &krate in all_crates.iter() { let crate_data = krate.data(db); let source_root = db.file_source_root(crate_data.root_file_id).source_root_id(db); diff --git a/src/tools/rust-analyzer/crates/ide-db/src/symbol_index.rs b/src/tools/rust-analyzer/crates/ide-db/src/symbol_index.rs index 183f6b6495375..2ad3a51c3d9a3 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/symbol_index.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/symbol_index.rs @@ -27,7 +27,10 @@ use std::{ ops::ControlFlow, }; -use base_db::{CrateOrigin, LangCrateOrigin, LibraryRoots, LocalRoots, RootQueryDb, SourceRootId}; +use base_db::{ + CrateOrigin, InternedSourceRootId, LangCrateOrigin, LibraryRoots, LocalRoots, SourceRootId, + source_root_crates, +}; use fst::{Automaton, Streamer, raw::IndexedValue}; use hir::{ Crate, Module, @@ -255,7 +258,7 @@ pub fn world_symbols(db: &RootDatabase, mut query: Query) -> Vec> let mut crates = Vec::new(); for &root in LocalRoots::get(db).roots(db).iter() { - crates.extend(db.source_root_crates(root).iter().copied()) + crates.extend(source_root_crates(db, root).iter().copied()) } crates .par_iter() @@ -322,7 +325,7 @@ fn resolve_path_to_modules( // If not anchored to crate, also search for modules matching first segment in local crates if !anchor_to_crate { for &root in LocalRoots::get(db).roots(db).iter() { - for &krate in db.source_root_crates(root).iter() { + for &krate in source_root_crates(db, root).iter() { let root_module = Crate::from(krate).root_module(db); for child in root_module.children(db) { if let Some(name) = child.name(db) @@ -369,11 +372,6 @@ impl<'db> SymbolIndex<'db> { db: &'db dyn HirDatabase, source_root_id: SourceRootId, ) -> &'db SymbolIndex<'db> { - // FIXME: - #[salsa::interned] - struct InternedSourceRootId { - id: SourceRootId, - } #[salsa::tracked(returns(ref))] fn library_symbols<'db>( db: &'db dyn HirDatabase, @@ -385,7 +383,7 @@ impl<'db> SymbolIndex<'db> { hir::attach_db(db, || { let mut symbol_collector = SymbolCollector::new(db, true); - db.source_root_crates(source_root_id.id(db)) + source_root_crates(db, source_root_id.id(db)) .iter() .flat_map(|&krate| Crate::from(krate).modules(db)) // we specifically avoid calling other SymbolsDatabase queries here, even though they do the same thing, diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unlinked_file.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unlinked_file.rs index 1283a11700e14..a67c0ede5690e 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unlinked_file.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unlinked_file.rs @@ -4,7 +4,7 @@ use std::iter; use hir::crate_def_map; use hir::{InFile, ModuleSource}; -use ide_db::base_db::RootQueryDb; +use ide_db::base_db; use ide_db::text_edit::TextEdit; use ide_db::{ FileId, FileRange, LineIndexDatabase, base_db::SourceDatabase, source_change::SourceChange, @@ -101,7 +101,7 @@ fn fixes( }; // check crate roots, i.e. main.rs, lib.rs, ... - let relevant_crates = db.relevant_crates(file_id); + let relevant_crates = base_db::relevant_crates(db, file_id); 'crates: for &krate in &*relevant_crates { // FIXME: This shouldnt need to access the crate def map directly let crate_def_map = crate_def_map(ctx.sema.db, krate); @@ -157,7 +157,7 @@ fn fixes( paths.into_iter().find_map(|path| source_root.file_for_path(&path)) })?; stack.pop(); - let relevant_crates = db.relevant_crates(parent_id); + let relevant_crates = base_db::relevant_crates(db, parent_id); 'crates: for &krate in relevant_crates.iter() { let crate_def_map = crate_def_map(ctx.sema.db, krate); let Some((_, module)) = crate_def_map.modules().find(|(_, module)| { 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 cc6bcb532a9dd..7d555435bb4ae 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs @@ -96,7 +96,7 @@ use hir::{ use ide_db::{ FileId, FileRange, FxHashMap, FxHashSet, RootDatabase, Severity, SnippetCap, assists::{Assist, AssistId, AssistResolveStrategy, ExprFillDefaultMode}, - base_db::{ReleaseChannel, RootQueryDb as _}, + base_db::{ReleaseChannel, all_crates, toolchain_channel}, generated::lints::{CLIPPY_LINT_GROUPS, DEFAULT_LINT_GROUPS, DEFAULT_LINTS, Lint, LintGroup}, imports::insert_use::InsertUseConfig, label::Label, @@ -354,14 +354,14 @@ pub fn semantic_diagnostics( let module = sema.file_to_module_def(file_id); let is_nightly = matches!( - module.and_then(|m| db.toolchain_channel(m.krate(db).into())), + module.and_then(|m| toolchain_channel(db, m.krate(db).into())), Some(ReleaseChannel::Nightly) | None ); let krate = match module { Some(module) => module.krate(db), None => { - match db.all_crates().last() { + match all_crates(db).last() { Some(last) => (*last).into(), // short-circuit, return an empty vec of diagnostics None => return vec![], diff --git a/src/tools/rust-analyzer/crates/ide-ssr/src/matching.rs b/src/tools/rust-analyzer/crates/ide-ssr/src/matching.rs index 264f0660d7f29..ab5a0f70f5a69 100644 --- a/src/tools/rust-analyzer/crates/ide-ssr/src/matching.rs +++ b/src/tools/rust-analyzer/crates/ide-ssr/src/matching.rs @@ -7,7 +7,7 @@ use crate::{ resolving::{ResolvedPattern, ResolvedRule, UfcsCallInfo}, }; use hir::{FileRange, FindPathConfig, Semantics}; -use ide_db::{FxHashMap, base_db::RootQueryDb}; +use ide_db::{FxHashMap, base_db::all_crates}; use std::{cell::Cell, iter::Peekable}; use syntax::{ SmolStr, SyntaxElement, SyntaxElementChildren, SyntaxKind, SyntaxNode, SyntaxToken, @@ -621,7 +621,7 @@ impl<'db, 'sema> Matcher<'db, 'sema> { })? .original; let krate = self.sema.scope(expr.syntax()).map(|it| it.krate()).unwrap_or_else(|| { - hir::Crate::from(*self.sema.db.all_crates().last().expect("no crate graph present")) + hir::Crate::from(*all_crates(self.sema.db).last().expect("no crate graph present")) }); code_type diff --git a/src/tools/rust-analyzer/crates/ide/src/doc_links.rs b/src/tools/rust-analyzer/crates/ide/src/doc_links.rs index 33bed9501a399..fd462d003d7dd 100644 --- a/src/tools/rust-analyzer/crates/ide/src/doc_links.rs +++ b/src/tools/rust-analyzer/crates/ide/src/doc_links.rs @@ -17,7 +17,7 @@ use hir::{ }; use ide_db::{ RootDatabase, - base_db::{CrateOrigin, LangCrateOrigin, ReleaseChannel, RootQueryDb}, + base_db::{CrateOrigin, LangCrateOrigin, ReleaseChannel, toolchain_channel}, defs::{Definition, NameClass, NameRefClass}, documentation::{Documentation, HasDocs}, helpers::pick_best_token, @@ -552,7 +552,7 @@ fn get_doc_base_urls( .and_then(|it| Url::parse(&it).ok()); let krate = def.krate(db); let channel = krate - .and_then(|krate| db.toolchain_channel(krate.into())) + .and_then(|krate| toolchain_channel(db, krate.into())) .unwrap_or(ReleaseChannel::Nightly) .as_str(); diff --git a/src/tools/rust-analyzer/crates/ide/src/expand_macro.rs b/src/tools/rust-analyzer/crates/ide/src/expand_macro.rs index 44285d9315af4..6f4ea70e0adc4 100644 --- a/src/tools/rust-analyzer/crates/ide/src/expand_macro.rs +++ b/src/tools/rust-analyzer/crates/ide/src/expand_macro.rs @@ -235,7 +235,7 @@ fn _format( file_id: FileId, expansion: &str, ) -> Option { - use ide_db::base_db::RootQueryDb; + use ide_db::base_db::relevant_crates; // hack until we get hygiene working (same character amount to preserve formatting as much as possible) const DOLLAR_CRATE_REPLACE: &str = "__r_a_"; @@ -250,7 +250,7 @@ fn _format( }; let expansion = format!("{prefix}{expansion}{suffix}"); - let &crate_id = db.relevant_crates(file_id).iter().next()?; + let &crate_id = relevant_crates(db, file_id).iter().next()?; let edition = crate_id.data(db).edition; #[allow(clippy::disallowed_methods)] diff --git a/src/tools/rust-analyzer/crates/ide/src/fetch_crates.rs b/src/tools/rust-analyzer/crates/ide/src/fetch_crates.rs index 956379e722d53..ad5af8bfe1526 100644 --- a/src/tools/rust-analyzer/crates/ide/src/fetch_crates.rs +++ b/src/tools/rust-analyzer/crates/ide/src/fetch_crates.rs @@ -1,6 +1,6 @@ use ide_db::{ FileId, FxIndexSet, RootDatabase, - base_db::{CrateOrigin, RootQueryDb}, + base_db::{CrateOrigin, all_crates}, }; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -20,7 +20,7 @@ pub struct CrateInfo { // // ![Show Dependency Tree](https://user-images.githubusercontent.com/5748995/229394139-2625beab-f4c9-484b-84ed-ad5dee0b1e1a.png) pub(crate) fn fetch_crates(db: &RootDatabase) -> FxIndexSet { - db.all_crates() + all_crates(db) .iter() .copied() .map(|crate_id| (crate_id.data(db), crate_id.extra_data(db))) diff --git a/src/tools/rust-analyzer/crates/ide/src/lib.rs b/src/tools/rust-analyzer/crates/ide/src/lib.rs index 610420bc2baeb..776523cee7e9a 100644 --- a/src/tools/rust-analyzer/crates/ide/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide/src/lib.rs @@ -63,11 +63,12 @@ use std::panic::{AssertUnwindSafe, UnwindSafe}; use cfg::CfgOptions; use fetch_crates::CrateInfo; use hir::{ChangeWithProcMacros, EditionedFileId, crate_def_map, sym}; +use ide_db::base_db::relevant_crates; use ide_db::ra_fixture::RaFixtureAnalysis; use ide_db::{ FxHashMap, FxIndexSet, LineIndexDatabase, base_db::{ - CrateOrigin, CrateWorkspaceData, Env, FileSet, RootQueryDb, SourceDatabase, VfsPath, + CrateOrigin, CrateWorkspaceData, Env, FileSet, SourceDatabase, VfsPath, salsa::{Cancelled, Database}, }, prime_caches, symbol_index, @@ -658,7 +659,7 @@ impl Analysis { /// Returns crates that this file *might* belong to. pub fn relevant_crates_for(&self, file_id: FileId) -> Cancellable> { - self.with_db(|db| db.relevant_crates(file_id).iter().copied().collect()) + self.with_db(|db| relevant_crates(db, file_id).iter().copied().collect()) } /// Returns the edition of the given crate. diff --git a/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs b/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs index 92020321f453e..99f8634bcb05a 100644 --- a/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs +++ b/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs @@ -11,7 +11,7 @@ use hir::{ }; use ide_db::{ FileId, FileRange, RootDatabase, SymbolKind, - base_db::{CrateOrigin, LangCrateOrigin, RootQueryDb}, + base_db::{CrateOrigin, LangCrateOrigin, all_crates}, defs::{Definition, find_std_module}, documentation::{Documentation, HasDocs}, famous_defs::FamousDefs, @@ -861,8 +861,7 @@ impl TryToNav for hir::BuiltinType { sema: &Semantics<'_, RootDatabase>, ) -> Option> { let db = sema.db; - let krate = db - .all_crates() + let krate = all_crates(db) .iter() .copied() .find(|&krate| matches!(krate.data(db).origin, CrateOrigin::Lang(LangCrateOrigin::Std))) diff --git a/src/tools/rust-analyzer/crates/ide/src/parent_module.rs b/src/tools/rust-analyzer/crates/ide/src/parent_module.rs index 96d829d1260bd..509ec2ab4051d 100644 --- a/src/tools/rust-analyzer/crates/ide/src/parent_module.rs +++ b/src/tools/rust-analyzer/crates/ide/src/parent_module.rs @@ -1,7 +1,7 @@ use hir::{Semantics, crate_def_map}; use ide_db::{ FileId, FilePosition, RootDatabase, - base_db::{Crate, RootQueryDb}, + base_db::{Crate, relevant_crates}, }; use itertools::Itertools; use syntax::{ @@ -53,7 +53,7 @@ pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec Vec { - db.relevant_crates(file_id) + relevant_crates(db, file_id) .iter() .copied() .filter(|&crate_id| { diff --git a/src/tools/rust-analyzer/crates/ide/src/runnables.rs b/src/tools/rust-analyzer/crates/ide/src/runnables.rs index a0a6a245592c6..098ffe49faca2 100644 --- a/src/tools/rust-analyzer/crates/ide/src/runnables.rs +++ b/src/tools/rust-analyzer/crates/ide/src/runnables.rs @@ -5,10 +5,10 @@ use ast::HasName; use cfg::{CfgAtom, CfgExpr}; use hir::{AsAssocItem, HasAttrs, HasCrate, HasSource, Semantics, Symbol, db::HirDatabase, sym}; use ide_assists::utils::{has_test_related_attribute, test_related_attribute_syn}; +use ide_db::base_db::all_crates; use ide_db::impl_empty_upmap_from_ra_fixture; use ide_db::{ FilePosition, FxHashMap, FxIndexMap, FxIndexSet, RootDatabase, SymbolKind, - base_db::RootQueryDb, defs::Definition, helpers::visit_file_defs, search::{FileReferenceNode, SearchScope}, @@ -506,7 +506,7 @@ fn module_def_doctest(sema: &Semantics<'_, RootDatabase>, def: Definition) -> Op let krate = def.krate(db); let edition = krate.map(|it| it.edition(db)).unwrap_or(Edition::CURRENT); let display_target = krate - .unwrap_or_else(|| (*db.all_crates().last().expect("no crate graph present")).into()) + .unwrap_or_else(|| (*all_crates(db).last().expect("no crate graph present")).into()) .to_display_target(db); if !has_runnable_doc_test(db, &attrs) { return None; diff --git a/src/tools/rust-analyzer/crates/ide/src/static_index.rs b/src/tools/rust-analyzer/crates/ide/src/static_index.rs index 3192c4c136982..4b2c9ceef9fff 100644 --- a/src/tools/rust-analyzer/crates/ide/src/static_index.rs +++ b/src/tools/rust-analyzer/crates/ide/src/static_index.rs @@ -5,7 +5,7 @@ use arrayvec::ArrayVec; use hir::{Crate, Module, Semantics, db::HirDatabase}; use ide_db::{ FileId, FileRange, FxHashMap, FxHashSet, RootDatabase, - base_db::{RootQueryDb, SourceDatabase, VfsPath}, + base_db::{SourceDatabase, VfsPath}, defs::{Definition, IdentClass}, documentation::Documentation, famous_defs::FamousDefs, @@ -124,16 +124,8 @@ fn documentation_for_definition( _ => None, }; - def.docs( - sema.db, - famous_defs.as_ref(), - def.krate(sema.db) - .unwrap_or_else(|| { - (*sema.db.all_crates().last().expect("no crate graph present")).into() - }) - .to_display_target(sema.db), - ) - .map(Documentation::into_owned) + def.docs(sema.db, famous_defs.as_ref(), def.krate(sema.db)?.to_display_target(sema.db)) + .map(Documentation::into_owned) } // FIXME: This is a weird function diff --git a/src/tools/rust-analyzer/crates/ide/src/test_explorer.rs b/src/tools/rust-analyzer/crates/ide/src/test_explorer.rs index 4792566f5f5b7..02040ef1388b8 100644 --- a/src/tools/rust-analyzer/crates/ide/src/test_explorer.rs +++ b/src/tools/rust-analyzer/crates/ide/src/test_explorer.rs @@ -1,8 +1,8 @@ //! Discovers tests use hir::{Crate, Module, ModuleDef, Semantics}; -use ide_db::base_db; -use ide_db::{FileId, RootDatabase, base_db::RootQueryDb}; +use ide_db::base_db::{self, all_crates}; +use ide_db::{FileId, RootDatabase}; use syntax::TextRange; use crate::{NavigationTarget, Runnable, TryToNav, runnables::runnable_fn}; @@ -26,7 +26,7 @@ pub struct TestItem { } pub(crate) fn discover_test_roots(db: &RootDatabase) -> Vec { - db.all_crates() + all_crates(db) .iter() .copied() .filter(|&id| id.data(db).origin.is_local()) @@ -48,7 +48,7 @@ pub(crate) fn discover_test_roots(db: &RootDatabase) -> Vec { fn find_crate_by_id(db: &RootDatabase, crate_id: &str) -> Option { // here, we use display_name as the crate id. This is not super ideal, but it works since we // only show tests for the local crates. - db.all_crates().iter().copied().find(|&id| { + all_crates(db).iter().copied().find(|&id| { id.data(db).origin.is_local() && id.extra_data(db).display_name.as_ref().is_some_and(|x| x.to_string() == crate_id) }) diff --git a/src/tools/rust-analyzer/crates/ide/src/typing.rs b/src/tools/rust-analyzer/crates/ide/src/typing.rs index 9c8782cdb2dcc..ec620982ff08f 100644 --- a/src/tools/rust-analyzer/crates/ide/src/typing.rs +++ b/src/tools/rust-analyzer/crates/ide/src/typing.rs @@ -17,7 +17,7 @@ mod on_enter; use either::Either; use hir::EditionedFileId; -use ide_db::{FilePosition, RootDatabase, base_db::RootQueryDb}; +use ide_db::{FilePosition, RootDatabase, base_db::relevant_crates}; use span::Edition; use std::iter; @@ -70,8 +70,7 @@ pub(crate) fn on_char_typed( if !TRIGGER_CHARS.contains(&char_typed) { return None; } - let edition = db - .relevant_crates(position.file_id) + let edition = relevant_crates(db, position.file_id) .first() .copied() .map_or(Edition::CURRENT, |krate| krate.data(db).edition); diff --git a/src/tools/rust-analyzer/crates/ide/src/view_crate_graph.rs b/src/tools/rust-analyzer/crates/ide/src/view_crate_graph.rs index 25deffe10eb89..e1670b7187968 100644 --- a/src/tools/rust-analyzer/crates/ide/src/view_crate_graph.rs +++ b/src/tools/rust-analyzer/crates/ide/src/view_crate_graph.rs @@ -1,10 +1,9 @@ use dot::{Id, LabelText}; +use ide_db::base_db::all_crates; use ide_db::base_db::salsa::plumbing::AsId; use ide_db::{ FxHashMap, RootDatabase, - base_db::{ - BuiltCrateData, BuiltDependency, Crate, ExtraCrateData, RootQueryDb, SourceDatabase, - }, + base_db::{BuiltCrateData, BuiltDependency, Crate, ExtraCrateData, SourceDatabase}, }; // Feature: View Crate Graph @@ -18,7 +17,7 @@ use ide_db::{ // |---------|-------------| // | VS Code | **rust-analyzer: View Crate Graph** | pub(crate) fn view_crate_graph(db: &RootDatabase, full: bool) -> Result { - let all_crates = db.all_crates(); + let all_crates = all_crates(db); let crates_to_render = all_crates .iter() .copied() diff --git a/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs b/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs index 8753eab43a8ca..297e37f1f6056 100644 --- a/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs +++ b/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs @@ -738,7 +738,7 @@ fn resolve_sub_span( #[cfg(test)] mod tests { - use ide_db::base_db::RootQueryDb; + use ide_db::base_db::all_crates; use vfs::file_set::FileSetConfigBuilder; use super::*; @@ -766,7 +766,7 @@ mod tests { let (db, _vfs, _proc_macro) = load_workspace(workspace, &cargo_config.extra_env, &load_cargo_config).unwrap(); - let n_crates = db.all_crates().len(); + let n_crates = all_crates(&db).len(); // RA has quite a few crates, but the exact count doesn't matter assert!(n_crates > 20); } diff --git a/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs b/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs index e271c32c86261..f346535ca19c0 100644 --- a/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs +++ b/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs @@ -11,7 +11,7 @@ use base_db::target::TargetData; use base_db::{ Crate, CrateDisplayName, CrateGraphBuilder, CrateName, CrateOrigin, CrateWorkspaceData, DependencyBuilder, Env, FileChange, FileSet, FxIndexMap, LangCrateOrigin, SourceDatabase, - SourceRoot, Version, VfsPath, + SourceRoot, Version, VfsPath, all_crates, }; use cfg::CfgOptions; use hir_expand::{ @@ -227,7 +227,7 @@ pub trait WithFixture: Default + ExpandDatabase + SourceDatabase + 'static { } fn test_crate(&self) -> Crate { - self.all_crates().iter().copied().find(|&krate| !krate.data(self).origin.is_lang()).unwrap() + all_crates(self).iter().copied().find(|&krate| !krate.data(self).origin.is_lang()).unwrap() } } From 964481b47619e6515e26cf889c59d3200cafa2fb Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 3 Apr 2026 15:55:51 +0200 Subject: [PATCH 072/144] Update fixtures --- .../rust-analyzer/crates/base-db/src/lib.rs | 3 +- .../src/expr_store/tests/body/block.rs | 6 +- .../hir-def/src/macro_expansion_tests/mbe.rs | 16 +- .../hir-def/src/nameres/tests/incremental.rs | 48 +++--- .../crates/hir-ty/src/tests/incremental.rs | 42 +++--- .../ide-db/src/test_data/test_doc_alias.txt | 30 ++-- .../test_symbol_index_collection.txt | 138 +++++++++--------- .../test_symbols_exclude_imports.txt | 4 +- .../test_data/test_symbols_with_imports.txt | 8 +- .../src/handlers/unlinked_file.rs | 2 +- src/tools/rust-analyzer/crates/ide/src/lib.rs | 2 +- .../crates/ide/src/signature_help.rs | 4 +- 12 files changed, 151 insertions(+), 152 deletions(-) diff --git a/src/tools/rust-analyzer/crates/base-db/src/lib.rs b/src/tools/rust-analyzer/crates/base-db/src/lib.rs index 26eef9ac0d63f..e438505c07e4b 100644 --- a/src/tools/rust-analyzer/crates/base-db/src/lib.rs +++ b/src/tools/rust-analyzer/crates/base-db/src/lib.rs @@ -347,8 +347,7 @@ pub fn set_all_crates_with_durability( /// /// **Warning**: do not use this query in `hir-*` crates! It kills incrementality across crate metadata modifications. pub fn all_crates(db: &dyn salsa::Database) -> std::sync::Arc<[Crate]> { - AllCrates::try_get(db) - .map_or(std::sync::Arc::default(), |all_crates| all_crates.crates(db).into()) + AllCrates::try_get(db).map_or(std::sync::Arc::default(), |all_crates| all_crates.crates(db)) } // FIXME: VFS rewrite should allow us to get rid of this wrapper diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/body/block.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/body/block.rs index 83594ee02169a..71fcced2d85b9 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/body/block.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/tests/body/block.rs @@ -190,13 +190,13 @@ fn f() { "#, expect![[r#" ModuleIdLt { - [salsa id]: Id(3803), + [salsa id]: Id(3403), krate: Crate( - Id(2400), + Id(2000), ), block: Some( BlockId( - 4801, + 4401, ), ), }"#]], diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs index d93df7af6a731..7b5d0103e66ee 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe.rs @@ -35,9 +35,9 @@ macro_rules! f { }; } -struct#0:MacroRules[BE8F, 0]@58..64#18432# MyTraitMap2#0:MacroCall[BE8F, 0]@31..42#ROOT2024# {#0:MacroRules[BE8F, 0]@72..73#18432# - map#0:MacroRules[BE8F, 0]@86..89#18432#:#0:MacroRules[BE8F, 0]@89..90#18432# #0:MacroRules[BE8F, 0]@89..90#18432#::#0:MacroRules[BE8F, 0]@91..93#18432#std#0:MacroRules[BE8F, 0]@93..96#18432#::#0:MacroRules[BE8F, 0]@96..98#18432#collections#0:MacroRules[BE8F, 0]@98..109#18432#::#0:MacroRules[BE8F, 0]@109..111#18432#HashSet#0:MacroRules[BE8F, 0]@111..118#18432#<#0:MacroRules[BE8F, 0]@118..119#18432#(#0:MacroRules[BE8F, 0]@119..120#18432#)#0:MacroRules[BE8F, 0]@120..121#18432#>#0:MacroRules[BE8F, 0]@121..122#18432#,#0:MacroRules[BE8F, 0]@122..123#18432# -}#0:MacroRules[BE8F, 0]@132..133#18432# +struct#0:MacroRules[BE8F, 0]@58..64#17408# MyTraitMap2#0:MacroCall[BE8F, 0]@31..42#ROOT2024# {#0:MacroRules[BE8F, 0]@72..73#17408# + map#0:MacroRules[BE8F, 0]@86..89#17408#:#0:MacroRules[BE8F, 0]@89..90#17408# #0:MacroRules[BE8F, 0]@89..90#17408#::#0:MacroRules[BE8F, 0]@91..93#17408#std#0:MacroRules[BE8F, 0]@93..96#17408#::#0:MacroRules[BE8F, 0]@96..98#17408#collections#0:MacroRules[BE8F, 0]@98..109#17408#::#0:MacroRules[BE8F, 0]@109..111#17408#HashSet#0:MacroRules[BE8F, 0]@111..118#17408#<#0:MacroRules[BE8F, 0]@118..119#17408#(#0:MacroRules[BE8F, 0]@119..120#17408#)#0:MacroRules[BE8F, 0]@120..121#17408#>#0:MacroRules[BE8F, 0]@121..122#17408#,#0:MacroRules[BE8F, 0]@122..123#17408# +}#0:MacroRules[BE8F, 0]@132..133#17408# "#]], ); } @@ -197,7 +197,7 @@ macro_rules! mk_struct { #[macro_use] mod foo; -struct#1:MacroRules[DB0C, 0]@59..65#18432# Foo#0:MacroCall[DB0C, 0]@32..35#ROOT2024#(#1:MacroRules[DB0C, 0]@70..71#18432#u32#0:MacroCall[DB0C, 0]@41..44#ROOT2024#)#1:MacroRules[DB0C, 0]@74..75#18432#;#1:MacroRules[DB0C, 0]@75..76#18432# +struct#1:MacroRules[DB0C, 0]@59..65#17408# Foo#0:MacroCall[DB0C, 0]@32..35#ROOT2024#(#1:MacroRules[DB0C, 0]@70..71#17408#u32#0:MacroCall[DB0C, 0]@41..44#ROOT2024#)#1:MacroRules[DB0C, 0]@74..75#17408#;#1:MacroRules[DB0C, 0]@75..76#17408# "#]], ); } @@ -423,10 +423,10 @@ m! { foo, bar } macro_rules! m { ($($i:ident),*) => ( impl Bar { $(fn $i() {})* } ); } -impl#\18432# Bar#\18432# {#\18432# - fn#\18432# foo#\ROOT2024#(#\18432#)#\18432# {#\18432#}#\18432# - fn#\18432# bar#\ROOT2024#(#\18432#)#\18432# {#\18432#}#\18432# -}#\18432# +impl#\17408# Bar#\17408# {#\17408# + fn#\17408# foo#\ROOT2024#(#\17408#)#\17408# {#\17408#}#\17408# + fn#\17408# bar#\ROOT2024#(#\17408#)#\17408# {#\17408#}#\17408# +}#\17408# "#]], ); } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs index 82d7a7114ae73..0f1828abceadb 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs @@ -167,22 +167,22 @@ fn no() {} "crate_local_def_map", "file_item_tree_query", "ast_id_map", - "parse_shim", + "parse", "real_span_map_shim", "file_item_tree_query", "ast_id_map", - "parse_shim", + "parse", "real_span_map_shim", "file_item_tree_query", "ast_id_map", - "parse_shim", + "parse", "real_span_map_shim", "EnumVariants::of_", ] "#]], expect![[r#" [ - "parse_shim", + "parse", "ast_id_map", "file_item_tree_query", "real_span_map_shim", @@ -225,16 +225,16 @@ pub struct S {} "crate_local_def_map", "file_item_tree_query", "ast_id_map", - "parse_shim", + "parse", "real_span_map_shim", "decl_macro_expander_shim", "file_item_tree_query", "ast_id_map", - "parse_shim", + "parse", "real_span_map_shim", "file_item_tree_query", "ast_id_map", - "parse_shim", + "parse", "real_span_map_shim", "macro_def_shim", "file_item_tree_query", @@ -245,7 +245,7 @@ pub struct S {} "#]], expect![[r#" [ - "parse_shim", + "parse", "ast_id_map", "file_item_tree_query", "real_span_map_shim", @@ -283,21 +283,21 @@ fn f() { foo } "crate_local_def_map", "file_item_tree_query", "ast_id_map", - "parse_shim", + "parse", "real_span_map_shim", "crate_local_def_map", "proc_macros_for_crate_shim", "file_item_tree_query", "ast_id_map", - "parse_shim", + "parse", "real_span_map_shim", "file_item_tree_query", "ast_id_map", - "parse_shim", + "parse", "real_span_map_shim", "file_item_tree_query", "ast_id_map", - "parse_shim", + "parse", "real_span_map_shim", "macro_def_shim", "file_item_tree_query", @@ -310,7 +310,7 @@ fn f() { foo } "#]], expect![[r#" [ - "parse_shim", + "parse", "ast_id_map", "file_item_tree_query", "real_span_map_shim", @@ -407,22 +407,22 @@ pub struct S {} "crate_local_def_map", "file_item_tree_query", "ast_id_map", - "parse_shim", + "parse", "real_span_map_shim", "crate_local_def_map", "proc_macros_for_crate_shim", "file_item_tree_query", "ast_id_map", - "parse_shim", + "parse", "real_span_map_shim", "decl_macro_expander_shim", "file_item_tree_query", "ast_id_map", - "parse_shim", + "parse", "real_span_map_shim", "file_item_tree_query", "ast_id_map", - "parse_shim", + "parse", "real_span_map_shim", "macro_def_shim", "file_item_tree_query", @@ -446,7 +446,7 @@ pub struct S {} "#]], expect![[r#" [ - "parse_shim", + "parse", "ast_id_map", "file_item_tree_query", "real_span_map_shim", @@ -524,16 +524,16 @@ m!(Z); "crate_local_def_map", "file_item_tree_query", "ast_id_map", - "parse_shim", + "parse", "real_span_map_shim", "decl_macro_expander_shim", "file_item_tree_query", "ast_id_map", - "parse_shim", + "parse", "real_span_map_shim", "file_item_tree_query", "ast_id_map", - "parse_shim", + "parse", "real_span_map_shim", "macro_def_shim", "file_item_tree_query", @@ -571,7 +571,7 @@ m!(Z); &[("file_item_tree_query", 1), ("parse_macro_expansion_shim", 0)], expect![[r#" [ - "parse_shim", + "parse", "ast_id_map", "file_item_tree_query", "real_span_map_shim", @@ -611,7 +611,7 @@ pub type Ty = (); [ "file_item_tree_query", "ast_id_map", - "parse_shim", + "parse", "real_span_map_shim", ] "#]], @@ -629,7 +629,7 @@ pub type Ty = (); &[("file_item_tree_query", 1), ("parse", 1)], expect![[r#" [ - "parse_shim", + "parse", "ast_id_map", "file_item_tree_query", "real_span_map_shim", diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/incremental.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/incremental.rs index e806999cb44e8..7cda259664c10 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/incremental.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/incremental.rs @@ -31,11 +31,11 @@ fn foo() -> i32 { &[("InferenceResult::for_body_", 1)], expect_test::expect![[r#" [ - "source_root_crates_shim", + "source_root_crates", "crate_local_def_map", "file_item_tree_query", "ast_id_map", - "parse_shim", + "parse", "real_span_map_shim", "InferenceResult::for_body_", "FunctionSignature::of_", @@ -76,7 +76,7 @@ fn foo() -> i32 { &[("InferenceResult::for_body_", 0)], expect_test::expect![[r#" [ - "parse_shim", + "parse", "ast_id_map", "file_item_tree_query", "real_span_map_shim", @@ -119,11 +119,11 @@ fn baz() -> i32 { &[("InferenceResult::for_body_", 3)], expect_test::expect![[r#" [ - "source_root_crates_shim", + "source_root_crates", "crate_local_def_map", "file_item_tree_query", "ast_id_map", - "parse_shim", + "parse", "real_span_map_shim", "InferenceResult::for_body_", "FunctionSignature::of_", @@ -189,7 +189,7 @@ fn baz() -> i32 { &[("InferenceResult::for_body_", 1)], expect_test::expect![[r#" [ - "parse_shim", + "parse", "ast_id_map", "file_item_tree_query", "real_span_map_shim", @@ -239,11 +239,11 @@ $0", &[("TraitImpls::for_crate_", 1)], expect_test::expect![[r#" [ - "source_root_crates_shim", + "source_root_crates", "crate_local_def_map", "file_item_tree_query", "ast_id_map", - "parse_shim", + "parse", "real_span_map_shim", "TraitImpls::for_crate_", "lang_items", @@ -278,7 +278,7 @@ pub struct NewStruct { &[("TraitImpls::for_crate_", 1)], expect_test::expect![[r#" [ - "parse_shim", + "parse", "ast_id_map", "file_item_tree_query", "real_span_map_shim", @@ -314,11 +314,11 @@ $0", &[("TraitImpls::for_crate_", 1)], expect_test::expect![[r#" [ - "source_root_crates_shim", + "source_root_crates", "crate_local_def_map", "file_item_tree_query", "ast_id_map", - "parse_shim", + "parse", "real_span_map_shim", "TraitImpls::for_crate_", "lang_items", @@ -354,7 +354,7 @@ pub enum SomeEnum { &[("TraitImpls::for_crate_", 1)], expect_test::expect![[r#" [ - "parse_shim", + "parse", "ast_id_map", "file_item_tree_query", "real_span_map_shim", @@ -390,11 +390,11 @@ $0", &[("TraitImpls::for_crate_", 1)], expect_test::expect![[r#" [ - "source_root_crates_shim", + "source_root_crates", "crate_local_def_map", "file_item_tree_query", "ast_id_map", - "parse_shim", + "parse", "real_span_map_shim", "TraitImpls::for_crate_", "lang_items", @@ -427,7 +427,7 @@ fn bar() -> f32 { &[("TraitImpls::for_crate_", 1)], expect_test::expect![[r#" [ - "parse_shim", + "parse", "ast_id_map", "file_item_tree_query", "real_span_map_shim", @@ -467,11 +467,11 @@ $0", &[("TraitImpls::for_crate_", 1)], expect_test::expect![[r#" [ - "source_root_crates_shim", + "source_root_crates", "crate_local_def_map", "file_item_tree_query", "ast_id_map", - "parse_shim", + "parse", "real_span_map_shim", "TraitImpls::for_crate_", "lang_items", @@ -512,7 +512,7 @@ impl SomeStruct { &[("TraitImpls::for_crate_", 1)], expect_test::expect![[r#" [ - "parse_shim", + "parse", "ast_id_map", "file_item_tree_query", "real_span_map_shim", @@ -568,11 +568,11 @@ fn main() { &[("trait_solve_shim", 0)], expect_test::expect![[r#" [ - "source_root_crates_shim", + "source_root_crates", "crate_local_def_map", "file_item_tree_query", "ast_id_map", - "parse_shim", + "parse", "real_span_map_shim", "TraitItems::query_with_diagnostics_", "Body::of_", @@ -664,7 +664,7 @@ fn main() { &[("trait_solve_shim", 0)], expect_test::expect![[r#" [ - "parse_shim", + "parse", "ast_id_map", "file_item_tree_query", "real_span_map_shim", diff --git a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_doc_alias.txt b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_doc_alias.txt index fc98ebb06921d..17d002e8bf4cf 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_doc_alias.txt +++ b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_doc_alias.txt @@ -2,7 +2,7 @@ ( Module { id: ModuleIdLt { - [salsa id]: Id(3400), + [salsa id]: Id(3000), }, }, [ @@ -12,7 +12,7 @@ Struct( Struct { id: StructId( - 3c01, + 3801, ), }, ), @@ -20,7 +20,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3000), + Id(2c00), ), ), ptr: SyntaxNodePtr { @@ -49,7 +49,7 @@ Struct( Struct { id: StructId( - 3c00, + 3800, ), }, ), @@ -57,7 +57,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3000), + Id(2c00), ), ), ptr: SyntaxNodePtr { @@ -86,7 +86,7 @@ Struct( Struct { id: StructId( - 3c00, + 3800, ), }, ), @@ -94,7 +94,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3000), + Id(2c00), ), ), ptr: SyntaxNodePtr { @@ -123,7 +123,7 @@ Struct( Struct { id: StructId( - 3c00, + 3800, ), }, ), @@ -131,7 +131,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3000), + Id(2c00), ), ), ptr: SyntaxNodePtr { @@ -160,7 +160,7 @@ Struct( Struct { id: StructId( - 3c00, + 3800, ), }, ), @@ -168,7 +168,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3000), + Id(2c00), ), ), ptr: SyntaxNodePtr { @@ -197,7 +197,7 @@ Struct( Struct { id: StructId( - 3c01, + 3801, ), }, ), @@ -205,7 +205,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3000), + Id(2c00), ), ), ptr: SyntaxNodePtr { @@ -234,7 +234,7 @@ Struct( Struct { id: StructId( - 3c00, + 3800, ), }, ), @@ -242,7 +242,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3000), + Id(2c00), ), ), ptr: SyntaxNodePtr { diff --git a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbol_index_collection.txt b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbol_index_collection.txt index 02a023038a61d..1b20a574bd1b1 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbol_index_collection.txt +++ b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbol_index_collection.txt @@ -2,7 +2,7 @@ ( Module { id: ModuleIdLt { - [salsa id]: Id(3400), + [salsa id]: Id(3000), }, }, [ @@ -11,14 +11,14 @@ def: EnumVariant( EnumVariant { id: EnumVariantId( - 7c00, + 7800, ), }, ), loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3000), + Id(2c00), ), ), ptr: SyntaxNodePtr { @@ -48,14 +48,14 @@ def: TypeAlias( TypeAlias { id: TypeAliasId( - 7000, + 6c00, ), }, ), loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3000), + Id(2c00), ), ), ptr: SyntaxNodePtr { @@ -83,14 +83,14 @@ def: EnumVariant( EnumVariant { id: EnumVariantId( - 7c01, + 7801, ), }, ), loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3000), + Id(2c00), ), ), ptr: SyntaxNodePtr { @@ -120,14 +120,14 @@ def: Const( Const { id: ConstId( - 6800, + 6400, ), }, ), loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3000), + Id(2c00), ), ), ptr: SyntaxNodePtr { @@ -155,14 +155,14 @@ def: Const( Const { id: ConstId( - 6802, + 6402, ), }, ), loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3000), + Id(2c00), ), ), ptr: SyntaxNodePtr { @@ -191,7 +191,7 @@ Enum( Enum { id: EnumId( - 5400, + 5000, ), }, ), @@ -199,7 +199,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3000), + Id(2c00), ), ), ptr: SyntaxNodePtr { @@ -228,7 +228,7 @@ Macro { id: Macro2Id( Macro2Id( - 5000, + 4c00, ), ), }, @@ -236,7 +236,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3000), + Id(2c00), ), ), ptr: SyntaxNodePtr { @@ -265,7 +265,7 @@ Macro { id: Macro2Id( Macro2Id( - 5000, + 4c00, ), ), }, @@ -273,7 +273,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3000), + Id(2c00), ), ), ptr: SyntaxNodePtr { @@ -301,14 +301,14 @@ def: Static( Static { id: StaticId( - 6c00, + 6800, ), }, ), loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3000), + Id(2c00), ), ), ptr: SyntaxNodePtr { @@ -337,7 +337,7 @@ Struct( Struct { id: StructId( - 4c01, + 4801, ), }, ), @@ -345,7 +345,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3000), + Id(2c00), ), ), ptr: SyntaxNodePtr { @@ -374,7 +374,7 @@ Struct( Struct { id: StructId( - 4c00, + 4800, ), }, ), @@ -382,7 +382,7 @@ loc: DeclarationLocation { hir_file_id: MacroFile( MacroCallId( - Id(4400), + Id(4000), ), ), ptr: SyntaxNodePtr { @@ -411,7 +411,7 @@ Struct( Struct { id: StructId( - 4c05, + 4805, ), }, ), @@ -419,7 +419,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3000), + Id(2c00), ), ), ptr: SyntaxNodePtr { @@ -450,7 +450,7 @@ Struct( Struct { id: StructId( - 4c06, + 4806, ), }, ), @@ -458,7 +458,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3000), + Id(2c00), ), ), ptr: SyntaxNodePtr { @@ -489,7 +489,7 @@ Struct( Struct { id: StructId( - 4c07, + 4807, ), }, ), @@ -497,7 +497,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3000), + Id(2c00), ), ), ptr: SyntaxNodePtr { @@ -526,7 +526,7 @@ Struct( Struct { id: StructId( - 4c02, + 4802, ), }, ), @@ -534,7 +534,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3000), + Id(2c00), ), ), ptr: SyntaxNodePtr { @@ -562,14 +562,14 @@ def: Trait( Trait { id: TraitId( - 6000, + 5c00, ), }, ), loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3000), + Id(2c00), ), ), ptr: SyntaxNodePtr { @@ -598,7 +598,7 @@ Macro { id: Macro2Id( Macro2Id( - 5000, + 4c00, ), ), }, @@ -606,7 +606,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3000), + Id(2c00), ), ), ptr: SyntaxNodePtr { @@ -635,7 +635,7 @@ Union( Union { id: UnionId( - 5800, + 5400, ), }, ), @@ -643,7 +643,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3000), + Id(2c00), ), ), ptr: SyntaxNodePtr { @@ -671,14 +671,14 @@ def: Module( Module { id: ModuleIdLt { - [salsa id]: Id(3401), + [salsa id]: Id(3001), }, }, ), loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3000), + Id(2c00), ), ), ptr: SyntaxNodePtr { @@ -706,14 +706,14 @@ def: Module( Module { id: ModuleIdLt { - [salsa id]: Id(3402), + [salsa id]: Id(3002), }, }, ), loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3000), + Id(2c00), ), ), ptr: SyntaxNodePtr { @@ -742,7 +742,7 @@ Macro { id: MacroRulesId( MacroRulesId( - 4001, + 3c01, ), ), }, @@ -750,7 +750,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3000), + Id(2c00), ), ), ptr: SyntaxNodePtr { @@ -778,14 +778,14 @@ def: Function( FunctionId( FunctionId( - 6402, + 6002, ), ), ), loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3000), + Id(2c00), ), ), ptr: SyntaxNodePtr { @@ -815,14 +815,14 @@ def: Function( FunctionId( FunctionId( - 6401, + 6001, ), ), ), loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3000), + Id(2c00), ), ), ptr: SyntaxNodePtr { @@ -853,7 +853,7 @@ Macro { id: MacroRulesId( MacroRulesId( - 4000, + 3c00, ), ), }, @@ -861,7 +861,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3000), + Id(2c00), ), ), ptr: SyntaxNodePtr { @@ -889,14 +889,14 @@ def: Function( FunctionId( FunctionId( - 6400, + 6000, ), ), ), loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3000), + Id(2c00), ), ), ptr: SyntaxNodePtr { @@ -925,7 +925,7 @@ Macro { id: MacroRulesId( MacroRulesId( - 4001, + 3c01, ), ), }, @@ -933,7 +933,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3000), + Id(2c00), ), ), ptr: SyntaxNodePtr { @@ -961,14 +961,14 @@ def: Function( FunctionId( FunctionId( - 6403, + 6003, ), ), ), loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3000), + Id(2c00), ), ), ptr: SyntaxNodePtr { @@ -998,7 +998,7 @@ ( Module { id: ModuleIdLt { - [salsa id]: Id(3401), + [salsa id]: Id(3001), }, }, [ @@ -1008,7 +1008,7 @@ Struct( Struct { id: StructId( - 4c03, + 4803, ), }, ), @@ -1016,7 +1016,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3000), + Id(2c00), ), ), ptr: SyntaxNodePtr { @@ -1044,7 +1044,7 @@ ( Module { id: ModuleIdLt { - [salsa id]: Id(3402), + [salsa id]: Id(3002), }, }, [ @@ -1053,14 +1053,14 @@ def: Trait( Trait { id: TraitId( - 6000, + 5c00, ), }, ), loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3001), + Id(2c01), ), ), ptr: SyntaxNodePtr { @@ -1089,7 +1089,7 @@ Macro { id: Macro2Id( Macro2Id( - 5000, + 4c00, ), ), }, @@ -1097,7 +1097,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3001), + Id(2c01), ), ), ptr: SyntaxNodePtr { @@ -1126,7 +1126,7 @@ Struct( Struct { id: StructId( - 4c04, + 4804, ), }, ), @@ -1134,7 +1134,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3001), + Id(2c01), ), ), ptr: SyntaxNodePtr { @@ -1163,7 +1163,7 @@ Macro { id: Macro2Id( Macro2Id( - 5000, + 4c00, ), ), }, @@ -1171,7 +1171,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3001), + Id(2c01), ), ), ptr: SyntaxNodePtr { @@ -1200,7 +1200,7 @@ Struct( Struct { id: StructId( - 4c04, + 4804, ), }, ), @@ -1208,7 +1208,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3001), + Id(2c01), ), ), ptr: SyntaxNodePtr { diff --git a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbols_exclude_imports.txt b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbols_exclude_imports.txt index aff1d56c56a32..f8ae687b784c4 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbols_exclude_imports.txt +++ b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbols_exclude_imports.txt @@ -5,7 +5,7 @@ Struct( Struct { id: StructId( - 4000, + 3c00, ), }, ), @@ -13,7 +13,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3001), + Id(2c01), ), ), ptr: SyntaxNodePtr { diff --git a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbols_with_imports.txt b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbols_with_imports.txt index bf5d81cfb149f..2282815a6103f 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbols_with_imports.txt +++ b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbols_with_imports.txt @@ -5,7 +5,7 @@ Struct( Struct { id: StructId( - 4000, + 3c00, ), }, ), @@ -13,7 +13,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3001), + Id(2c01), ), ), ptr: SyntaxNodePtr { @@ -42,7 +42,7 @@ Struct( Struct { id: StructId( - 4000, + 3c00, ), }, ), @@ -50,7 +50,7 @@ loc: DeclarationLocation { hir_file_id: FileId( EditionedFileId( - Id(3000), + Id(2c00), ), ), ptr: SyntaxNodePtr { diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unlinked_file.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unlinked_file.rs index a67c0ede5690e..d7a0a3b0f59d4 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unlinked_file.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unlinked_file.rs @@ -102,7 +102,7 @@ fn fixes( // check crate roots, i.e. main.rs, lib.rs, ... let relevant_crates = base_db::relevant_crates(db, file_id); - 'crates: for &krate in &*relevant_crates { + 'crates: for &krate in relevant_crates { // FIXME: This shouldnt need to access the crate def map directly let crate_def_map = crate_def_map(ctx.sema.db, krate); diff --git a/src/tools/rust-analyzer/crates/ide/src/lib.rs b/src/tools/rust-analyzer/crates/ide/src/lib.rs index 776523cee7e9a..f3e51e191929b 100644 --- a/src/tools/rust-analyzer/crates/ide/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide/src/lib.rs @@ -659,7 +659,7 @@ impl Analysis { /// Returns crates that this file *might* belong to. pub fn relevant_crates_for(&self, file_id: FileId) -> Cancellable> { - self.with_db(|db| relevant_crates(db, file_id).iter().copied().collect()) + self.with_db(|db| relevant_crates(db, file_id).to_vec()) } /// Returns the edition of the given crate. diff --git a/src/tools/rust-analyzer/crates/ide/src/signature_help.rs b/src/tools/rust-analyzer/crates/ide/src/signature_help.rs index 9eb01b12f2bd0..cf796b27150ec 100644 --- a/src/tools/rust-analyzer/crates/ide/src/signature_help.rs +++ b/src/tools/rust-analyzer/crates/ide/src/signature_help.rs @@ -1975,8 +1975,8 @@ trait Sub: Super + Super { fn f() -> impl Sub<$0 "#, expect![[r#" - trait Sub - ^^^^^^^^^^^ --------- + trait Sub + ^^^^^^^^^ ----------- "#]], ); } From 3d3865484574a694eb3edfecc7d6d1c4bcd4889c Mon Sep 17 00:00:00 2001 From: so1ve Date: Fri, 3 Apr 2026 22:26:05 +0800 Subject: [PATCH 073/144] align with rustc behavior --- .../rust-analyzer/crates/hir-def/src/attrs.rs | 11 +-- .../crates/hir-def/src/attrs/docs.rs | 68 +++++++++---------- .../crates/ide/src/hover/tests.rs | 54 +++------------ 3 files changed, 43 insertions(+), 90 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs b/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs index 3dbbafdd51fdb..aa7dad8bf1ca7 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs @@ -965,20 +965,13 @@ impl AttrFlags { pub fn docs(db: &dyn DefDatabase, owner: AttrDefId) -> Option> { let (source, outer_mod_decl, _extra_crate_attrs, krate) = attrs_source(db, owner); let inner_attrs_node = source.value.inner_attributes_node(); - let parent = if outer_mod_decl.is_some() - && let AttrDefId::ModuleId(module_id) = owner - { - module_id.containing_module(db) - } else { - None - }; // Note: we don't have to pass down `_extra_crate_attrs` here, since `extract_docs` // does not handle crate-level attributes related to docs. // See: https://doc.rust-lang.org/rustdoc/write-documentation/the-doc-attribute.html#at-the-crate-level self::docs::extract_docs( db, krate, - &|| (parent.map(|it| it.resolver(db)), resolver_for_attr_def_id(db, owner)), + &|| resolver_for_attr_def_id(db, owner), &|| krate.cfg_options(db), source, outer_mod_decl, @@ -1001,7 +994,7 @@ impl AttrFlags { self::docs::extract_docs( db, krate, - &|| (None, variant.resolver(db)), + &|| variant.resolver(db), &|| cfg_options, field, None, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/attrs/docs.rs b/src/tools/rust-analyzer/crates/hir-def/src/attrs/docs.rs index 16e813bc5f971..a0665dfecd9af 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/attrs/docs.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/attrs/docs.rs @@ -418,10 +418,10 @@ fn extend_with_attrs<'a, 'db>( indent: &mut usize, get_cfg_options: &dyn Fn() -> &'a CfgOptions, cfg_options: &mut Option<&'a CfgOptions>, - make_resolver: &dyn Fn() -> Option>, + make_resolver: &dyn Fn() -> Resolver<'db>, ) { // Lazily initialised when we first encounter a `#[doc = macro!()]`. - let mut expander: Option, DocExprSourceCtx<'db>)>> = None; + let mut expander: Option<(DocMacroExpander<'db>, DocExprSourceCtx<'db>)> = None; // FIXME: `#[cfg_attr(..., doc = macro!())]` skips macro expansion because // `top_attr` points to the `cfg_attr` node, not the inner `doc = macro!()`. @@ -457,32 +457,31 @@ fn extend_with_attrs<'a, 'db>( top_attr.as_simple_call().is_some_and(|(name, _)| name == "cfg_attr"); if !is_from_cfg_attr && let Some(expr) = top_attr.expr() - && let Some((exp, ctx)) = expander - .get_or_insert_with(|| { - make_resolver().map(|resolver| { - let def_map = resolver.top_level_def_map(); - let recursion_limit = def_map.recursion_limit() as usize; - ( - DocMacroExpander { - db, - krate, - recursion_depth: 0, - recursion_limit, - }, - DocExprSourceCtx { - resolver, - file_id, - ast_id_map: db.ast_id_map(file_id), - span_map: db.span_map(file_id), - }, - ) - }) - }) - .as_mut() - && let Some(expanded) = - expand_doc_expr_via_macro_pipeline(exp, ctx, expr) { - result.extend_with_unmapped_doc_str(&expanded, indent); + let (exp, ctx) = expander.get_or_insert_with(|| { + let resolver = make_resolver(); + let def_map = resolver.top_level_def_map(); + let recursion_limit = def_map.recursion_limit() as usize; + ( + DocMacroExpander { + db, + krate, + recursion_depth: 0, + recursion_limit, + }, + DocExprSourceCtx { + resolver, + file_id, + ast_id_map: db.ast_id_map(file_id), + span_map: db.span_map(file_id), + }, + ) + }); + if let Some(expanded) = + expand_doc_expr_via_macro_pipeline(exp, ctx, expr) + { + result.extend_with_unmapped_doc_str(&expanded, indent); + } } } _ => {} @@ -496,10 +495,7 @@ fn extend_with_attrs<'a, 'db>( pub(crate) fn extract_docs<'a, 'db>( db: &'db dyn DefDatabase, krate: Crate, - // Returns (outer_resolver, inline_resolver). - // `outer_resolver` is `Some` only for outlined modules (`mod foo;`) where outer docs - // should be resolved in the parent module's scope. - resolvers: &dyn Fn() -> (Option>, Resolver<'db>), + resolver: &dyn Fn() -> Resolver<'db>, get_cfg_options: &dyn Fn() -> &'a CfgOptions, source: InFile, outer_mod_decl: Option>, @@ -519,8 +515,7 @@ pub(crate) fn extract_docs<'a, 'db>( if let Some(outer_mod_decl) = outer_mod_decl { let mut indent = usize::MAX; - // For outer docs (the `mod foo;` declaration), use the parent module's resolver - // so that macros are resolved in the parent's scope. + // For outer docs (the `mod foo;` declaration), use the module's own resolver. extend_with_attrs( &mut result, db, @@ -531,7 +526,7 @@ pub(crate) fn extract_docs<'a, 'db>( &mut indent, get_cfg_options, &mut cfg_options, - &|| resolvers().0, + resolver, ); result.remove_indent(indent, 0); result.outline_mod = Some((outer_mod_decl.file_id, result.docs_source_map.len())); @@ -539,7 +534,6 @@ pub(crate) fn extract_docs<'a, 'db>( let inline_source_map_start = result.docs_source_map.len(); let mut indent = usize::MAX; - let inline_resolver = &|| Some(resolvers().1); // For inline docs, use the item's own resolver. extend_with_attrs( &mut result, @@ -551,7 +545,7 @@ pub(crate) fn extract_docs<'a, 'db>( &mut indent, get_cfg_options, &mut cfg_options, - inline_resolver, + resolver, ); if let Some(inner_attrs_node) = &inner_attrs_node { result.inline_inner_docs_start = Some(TextSize::of(&result.docs)); @@ -565,7 +559,7 @@ pub(crate) fn extract_docs<'a, 'db>( &mut indent, get_cfg_options, &mut cfg_options, - inline_resolver, + resolver, ); } result.remove_indent(indent, inline_source_map_start); 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 2ca43096dddeb..882aa041ceb3e 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs @@ -11660,18 +11660,21 @@ struct Bar { } #[test] -fn test_hover_doc_attr_macro_on_outlined_mod_resolves_from_parent() { - // Outer doc-macro on `mod foo;` should resolve from the parent module, - // and combine with inner `//!` docs from the module file. +fn test_hover_doc_attr_macro_on_outlined_mod() { + // Outer doc-macro on `mod foo;` resolves from inside the module's scope + // (matching rustc behavior), and combines with inner `//!` docs from the module file. check( r#" //- /main.rs -macro_rules! doc_str { - () => { "expanded from parent" }; +mod mac { + macro_rules! doc_str { + () => { "expanded from macro" }; + } + pub(crate) use doc_str; } /// plain outer doc -#[doc = doc_str!()] +#[doc = super::mac::doc_str!()] mod foo$0; //- /foo.rs @@ -11692,45 +11695,8 @@ pub struct Bar; --- plain outer doc - expanded from parent + expanded from macro inner module docs "#]], ); } - -#[test] -fn test_hover_doc_attr_inner_doc_macro() { - // Inner doc attribute with macro expansion (`#![doc = macro!()]`) - check( - r#" -macro_rules! doc_str { - () => { "inner doc from macro" }; -} - -/// outer doc -/// -mod foo$0 { - #![doc = doc_str!()] - - pub struct Bar; -} -"#, - expect![[r#" - *foo* - - ```rust - ra_test_fixture - ``` - - ```rust - mod foo - ``` - - --- - - outer doc - - inner doc from macro - "#]], - ); -} From f44ca841175563bcbd883ccab86d7a2c10949ea9 Mon Sep 17 00:00:00 2001 From: so1ve Date: Fri, 3 Apr 2026 22:27:38 +0800 Subject: [PATCH 074/144] chore: fmt --- src/tools/rust-analyzer/crates/hir-def/src/attrs/docs.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/attrs/docs.rs b/src/tools/rust-analyzer/crates/hir-def/src/attrs/docs.rs index a0665dfecd9af..8c14808c71957 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/attrs/docs.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/attrs/docs.rs @@ -455,9 +455,7 @@ fn extend_with_attrs<'a, 'db>( // expansion (see FIXME above). let is_from_cfg_attr = top_attr.as_simple_call().is_some_and(|(name, _)| name == "cfg_attr"); - if !is_from_cfg_attr - && let Some(expr) = top_attr.expr() - { + if !is_from_cfg_attr && let Some(expr) = top_attr.expr() { let (exp, ctx) = expander.get_or_insert_with(|| { let resolver = make_resolver(); let def_map = resolver.top_level_def_map(); From 65c27e2996c350e20c0e3aeab19efb932bbd096f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Apr 2026 14:50:53 +0000 Subject: [PATCH 075/144] chore(deps): bump lodash from 4.17.23 to 4.18.1 in /editors/code Bumps [lodash](https://github.com/lodash/lodash) from 4.17.23 to 4.18.1. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.23...4.18.1) --- updated-dependencies: - dependency-name: lodash dependency-version: 4.18.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- src/tools/rust-analyzer/editors/code/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tools/rust-analyzer/editors/code/package-lock.json b/src/tools/rust-analyzer/editors/code/package-lock.json index 1c626e392c910..2c9ce1d948ef7 100644 --- a/src/tools/rust-analyzer/editors/code/package-lock.json +++ b/src/tools/rust-analyzer/editors/code/package-lock.json @@ -5055,9 +5055,9 @@ } }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "dev": true, "license": "MIT" }, From 221c39a8b0154f559a8fed16f49b633b819bdc74 Mon Sep 17 00:00:00 2001 From: Marco Liebel Date: Fri, 3 Apr 2026 11:04:13 -0700 Subject: [PATCH 076/144] Add myself as co-maintainer for hexagon-unknown-linux-musl Two dedicated target maintainers are needed for tier 2 promotion. Coordinated with the existing maintainer @androm3da. --- src/doc/rustc/src/platform-support/hexagon-unknown-linux-musl.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/doc/rustc/src/platform-support/hexagon-unknown-linux-musl.md b/src/doc/rustc/src/platform-support/hexagon-unknown-linux-musl.md index eefe821339060..9f4e8239cc7d2 100644 --- a/src/doc/rustc/src/platform-support/hexagon-unknown-linux-musl.md +++ b/src/doc/rustc/src/platform-support/hexagon-unknown-linux-musl.md @@ -12,6 +12,7 @@ DSP architecture. ## Target maintainers [@androm3da](https://github.com/androm3da) +[@quic-mliebel](https://github.com/quic-mliebel) ## Requirements The target is cross-compiled. This target supports `std`. By default, code From 44c71398cde84ae24ad6c7bd568e8f91a20cf2d0 Mon Sep 17 00:00:00 2001 From: Amit Singhmar Date: Sat, 4 Apr 2026 12:28:23 +0000 Subject: [PATCH 077/144] fix: report `expected type, found {` in parser --- src/tools/rust-analyzer/crates/parser/src/grammar/types.rs | 3 +++ .../crates/parser/test_data/parser/err/0025_nope.rast | 2 +- .../parser/test_data/parser/err/0027_incomplete_where_for.rast | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/types.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/types.rs index c62356d5c9561..667bb68c649c5 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/types.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/types.rs @@ -59,6 +59,9 @@ fn type_with_bounds_cond(p: &mut Parser<'_>, allow_bounds: bool) { } _ if paths::is_path_start(p) => path_or_macro_type(p, allow_bounds), LIFETIME_IDENT if p.nth_at(1, T![+]) => bare_dyn_trait_type(p), + T!['{'] => { + p.err_recover("expected type, found `{`", TYPE_RECOVERY_SET); + } _ => { p.err_recover("expected type", TYPE_RECOVERY_SET); } diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0025_nope.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0025_nope.rast index b6bc0088374fb..23964ab9d9b3f 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0025_nope.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0025_nope.rast @@ -194,7 +194,7 @@ SOURCE_FILE WHITESPACE "\n" R_CURLY "}" WHITESPACE "\n" -error 95: expected type +error 95: expected type, found `{` error 95: expected COMMA error 96: expected field error 98: expected field declaration diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0027_incomplete_where_for.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0027_incomplete_where_for.rast index 3768a55d5308b..31db794d9f12d 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0027_incomplete_where_for.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0027_incomplete_where_for.rast @@ -26,5 +26,5 @@ SOURCE_FILE L_CURLY "{" R_CURLY "}" WHITESPACE "\n" -error 26: expected type +error 26: expected type, found `{` error 26: expected colon From 7a6617669b3aa4f7cd90c1cf7ad18d1fb233a389 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sat, 4 Apr 2026 20:44:00 +0800 Subject: [PATCH 078/144] fix: offer 'type_mismatch' some fixes inside macro - Supports macro for `add_missing_ok_or_some` and `str_ref_to_owned` Example --- ```rust macro_rules! identity { ($($t:tt)*) => ($($t)*) } identity! { fn test() -> String { "a"$0 } } ``` **Before this PR** Invalid trigger range and edit range **After this PR** ```rust macro_rules! identity { ($($t:tt)*) => ($($t)*) } identity! { fn test() -> String { "a".to_owned() } } ``` --- ```rust macro_rules! identity { ($($t:tt)*) => ($($t)*) } identity! { fn div(x: i32, y: i32) -> Result { if y == 0 { return Err(()); } x / y$0 } } ``` **Before this PR** Invalid trigger range and edit range **After this PR** ```rust macro_rules! identity { ($($t:tt)*) => ($($t)*) } identity! { fn div(x: i32, y: i32) -> Result { if y == 0 { return Err(()); } Ok(x / y) } } ``` --- .../src/handlers/type_mismatch.rs | 75 +++++++++++++++---- 1 file changed, 61 insertions(+), 14 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs index f443dc08f5fd2..90b2f24d0cdfe 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs @@ -101,7 +101,7 @@ fn add_missing_ok_or_some( ) -> Option<()> { let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id); let expr = expr_ptr.value.to_node(&root); - let expr_range = expr.syntax().text_range(); + let expr_range = ctx.sema.original_range_opt(expr.syntax())?.range; let scope = ctx.sema.scope(expr.syntax())?; let expected_adt = d.expected.as_adt()?; @@ -135,13 +135,13 @@ fn add_missing_ok_or_some( // Empty block let indent = block_indent + 1; builder.insert( - block.syntax().text_range().start() + TextSize::from(1), + expr_range.start() + TextSize::from(1), format!("\n{indent}{variant_name}(())\n{block_indent}"), ); } else { let indent = IndentLevel::from(1); builder.insert( - block.syntax().text_range().end() - TextSize::from(1), + expr_range.end() - TextSize::from(1), format!("{indent}{variant_name}(())\n{block_indent}"), ); } @@ -158,8 +158,7 @@ fn add_missing_ok_or_some( // Fix for forms like `fn foo() -> Result<(), String> { return; }` if ret_expr.expr().is_none() { let mut builder = TextEdit::builder(); - builder - .insert(ret_expr.syntax().text_range().end(), format!(" {variant_name}(())")); + builder.insert(expr_range.end(), format!(" {variant_name}(())")); let source_change = SourceChange::from_text_edit( expr_ptr.file_id.original_file(ctx.sema.db).file_id(ctx.sema.db), builder.finish(), @@ -172,8 +171,8 @@ fn add_missing_ok_or_some( } let mut builder = TextEdit::builder(); - builder.insert(expr.syntax().text_range().start(), format!("{variant_name}(")); - builder.insert(expr.syntax().text_range().end(), ")".to_owned()); + builder.insert(expr_range.start(), format!("{variant_name}(")); + builder.insert(expr_range.end(), ")".to_owned()); let source_change = SourceChange::from_text_edit( expr_ptr.file_id.original_file(ctx.sema.db).file_id(ctx.sema.db), builder.finish(), @@ -192,6 +191,7 @@ fn remove_unnecessary_wrapper( let db = ctx.sema.db; let root = db.parse_or_expand(expr_ptr.file_id); let expr = expr_ptr.value.to_node(&root); + // FIXME: support inside MacroCall? let expr = ctx.sema.original_ast_node(expr)?; let Expr::CallExpr(call_expr) = expr else { @@ -278,6 +278,7 @@ fn remove_semicolon( return None; } let block = BlockExpr::cast(expr.syntax().clone())?; + // FIXME: support inside MacroCall? let expr_before_semi = block.statements().last().and_then(|s| ExprStmt::cast(s.syntax().clone()))?; let type_before_semi = ctx.sema.type_of_expr(&expr_before_semi.expr()?)?.original(); @@ -311,16 +312,13 @@ fn str_ref_to_owned( let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id); let expr = expr_ptr.value.to_node(&root); - let expr_range = expr.syntax().text_range(); + let hir::FileRange { file_id, range } = ctx.sema.original_range_opt(expr.syntax())?; let to_owned = ".to_owned()".to_owned(); - let edit = TextEdit::insert(expr.syntax().text_range().end(), to_owned); - let source_change = SourceChange::from_text_edit( - expr_ptr.file_id.original_file(ctx.sema.db).file_id(ctx.sema.db), - edit, - ); - acc.push(fix("str_ref_to_owned", "Add .to_owned() here", source_change, expr_range)); + let edit = TextEdit::insert(range.end(), to_owned); + let source_change = SourceChange::from_text_edit(file_id.file_id(ctx.sema.db), edit); + acc.push(fix("str_ref_to_owned", "Add .to_owned() here", source_change, range)); Some(()) } @@ -566,6 +564,32 @@ fn div(x: i32, y: i32) -> Result { } Ok(x / y) } +"#, + ); + + check_fix( + r#" +//- minicore: option, result +macro_rules! identity { ($($t:tt)*) => ($($t)*) } +identity! { + fn div(x: i32, y: i32) -> Result { + if y == 0 { + return Err(()); + } + x / y$0 + } +} +"#, + r#" +macro_rules! identity { ($($t:tt)*) => ($($t)*) } +identity! { + fn div(x: i32, y: i32) -> Result { + if y == 0 { + return Err(()); + } + Ok(x / y) + } +} "#, ); } @@ -1037,6 +1061,29 @@ struct String; fn test() -> String { "a".to_owned() +} + "#, + ); + + check_fix( + r#" +macro_rules! identity { ($($t:tt)*) => ($($t)*) } +struct String; + +identity! { + fn test() -> String { + "a"$0 + } +} + "#, + r#" +macro_rules! identity { ($($t:tt)*) => ($($t)*) } +struct String; + +identity! { + fn test() -> String { + "a".to_owned() + } } "#, ); From 0aee25f826177c3f507bcab4d36c88896bdd76a8 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sat, 4 Apr 2026 21:06:47 +0800 Subject: [PATCH 079/144] fix: offer on empty else block for 'convert_let_else_to_match' When editing, there are situations where the else block has not been filled in yet but needs to be converted Example --- ```rust fn main() { let Ok(x) = f() else$0 {}; } ``` **Before this PR** Assist not applicable **After this PR** ```rust fn main() { let x = match f() { Ok(x) => x, _ => {} }; } ``` --- .../src/handlers/convert_let_else_to_match.rs | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_let_else_to_match.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_let_else_to_match.rs index 5874f66522fd3..fcb4edf12e0ff 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_let_else_to_match.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_let_else_to_match.rs @@ -32,8 +32,10 @@ pub(crate) fn convert_let_else_to_match(acc: &mut Assists, ctx: &AssistContext<' .or_else(|| ctx.find_token_syntax_at_offset(T![let])?.parent())?; let let_stmt = LetStmt::cast(let_stmt)?; let else_block = let_stmt.let_else()?.block_expr()?; - let else_expr = if else_block.statements().next().is_none() { - else_block.tail_expr()?.reset_indent() + let else_expr = if else_block.statements().next().is_none() + && let Some(tail_expr) = else_block.tail_expr() + { + tail_expr.reset_indent() } else { else_block.reset_indent().into() }; @@ -298,6 +300,24 @@ fn main() { ); } + #[test] + fn convert_let_else_to_match_with_empty_else_block() { + check_assist( + convert_let_else_to_match, + r" +fn main() { + let Ok(x) = f() else$0 {}; +}", + r" +fn main() { + let x = match f() { + Ok(x) => x, + _ => {} + }; +}", + ); + } + #[test] fn convert_let_else_to_match_with_some_indent() { check_assist( From 72f00dd40fd26566d6a2d37923d9df4aabbaddf8 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sat, 4 Apr 2026 21:38:56 +0800 Subject: [PATCH 080/144] fix: add semicolon for postfix format unit like snippets Example --- ```rust fn main() { "{ 2+2 } { SomeStruct { val: 1, other: 32 } :?}".println } ``` **Before this PR** ```rust fn main() { println!("{} {:?}", 2 + 2, SomeStruct { val: 1, other: 32 }) } ``` **After this PR** ```rust fn main() { println!("{} {:?}", 2 + 2, SomeStruct { val: 1, other: 32 }); } ``` --- .../ide-completion/src/completions/postfix.rs | 26 ++++++++++++------- .../src/completions/postfix/format_like.rs | 7 +++-- 2 files changed, 22 insertions(+), 11 deletions(-) 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 f1ccdd4c731de..82baf885ddc68 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 @@ -310,7 +310,7 @@ pub(crate) fn complete_postfix( if let ast::Expr::Literal(literal) = dot_receiver.clone() && let Some(literal_text) = ast::String::cast(literal.token()) { - add_format_like_completions(acc, ctx, &dot_receiver_including_refs, cap, &literal_text); + add_format_like_completions(acc, ctx, dot_receiver, cap, &literal_text, semi); } postfix_snippet("return", "return expr", &format!("return {receiver_text}{semi}")) @@ -1302,34 +1302,42 @@ fn main() { check_edit( "panic", r#"fn main() { "Panic with {a}".$0 }"#, - r#"fn main() { panic!("Panic with {a}") }"#, + r#"fn main() { panic!("Panic with {a}"); }"#, ); check_edit( "println", r#"fn main() { "{ 2+2 } { SomeStruct { val: 1, other: 32 } :?}".$0 }"#, - r#"fn main() { println!("{} {:?}", 2+2, SomeStruct { val: 1, other: 32 }) }"#, + r#"fn main() { println!("{} {:?}", 2+2, SomeStruct { val: 1, other: 32 }); }"#, ); check_edit( "loge", r#"fn main() { "{2+2}".$0 }"#, - r#"fn main() { log::error!("{}", 2+2) }"#, + r#"fn main() { log::error!("{}", 2+2); }"#, ); check_edit( "logt", r#"fn main() { "{2+2}".$0 }"#, - r#"fn main() { log::trace!("{}", 2+2) }"#, + r#"fn main() { log::trace!("{}", 2+2); }"#, ); check_edit( "logd", r#"fn main() { "{2+2}".$0 }"#, - r#"fn main() { log::debug!("{}", 2+2) }"#, + r#"fn main() { log::debug!("{}", 2+2); }"#, + ); + check_edit( + "logi", + r#"fn main() { "{2+2}".$0 }"#, + r#"fn main() { log::info!("{}", 2+2); }"#, + ); + check_edit( + "logw", + r#"fn main() { "{2+2}".$0 }"#, + r#"fn main() { log::warn!("{}", 2+2); }"#, ); - check_edit("logi", r#"fn main() { "{2+2}".$0 }"#, r#"fn main() { log::info!("{}", 2+2) }"#); - check_edit("logw", r#"fn main() { "{2+2}".$0 }"#, r#"fn main() { log::warn!("{}", 2+2) }"#); check_edit( "loge", r#"fn main() { "{2+2}".$0 }"#, - r#"fn main() { log::error!("{}", 2+2) }"#, + r#"fn main() { log::error!("{}", 2+2); }"#, ); } diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix/format_like.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix/format_like.rs index 7faa1139595f8..db9b6d0bf3514 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix/format_like.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix/format_like.rs @@ -40,6 +40,7 @@ static KINDS: &[(&str, &str)] = &[ ("logw", "log::warn!"), ("loge", "log::error!"), ]; +static HAS_VALUE: &[&str] = &["format"]; pub(crate) fn add_format_like_completions( acc: &mut Completions, @@ -47,6 +48,7 @@ pub(crate) fn add_format_like_completions( dot_receiver: &ast::Expr, cap: SnippetCap, receiver_text: &ast::String, + semi: &str, ) { let postfix_snippet = match build_postfix_snippet_builder(ctx, cap, dot_receiver) { Some(it) => it, @@ -64,10 +66,11 @@ pub(crate) fn add_format_like_completions( let exprs = with_placeholders(exprs); for (label, macro_name) in KINDS { + let semi = if HAS_VALUE.contains(label) { "" } else { semi }; let snippet = if exprs.is_empty() { - format!(r#"{macro_name}({out})"#) + format!(r#"{macro_name}({out}){semi}"#) } else { - format!(r#"{}({}, {})"#, macro_name, out, exprs.join(", ")) + format!(r#"{}({}, {}){semi}"#, macro_name, out, exprs.join(", ")) }; postfix_snippet(label, macro_name, &snippet).add_to(acc, ctx.db); From ddea7263240322cdf6d57d2d7755eddceef8b9f8 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Sun, 5 Apr 2026 11:01:57 +0530 Subject: [PATCH 081/144] update syntaxEditor constructor to make sure caller doesn't need to think of root invariant --- .../crates/syntax/src/syntax_editor.rs | 55 ++++++++++++++----- 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs index e6937e4d0f8a0..84559753a29f7 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs @@ -32,9 +32,25 @@ pub struct SyntaxEditor { } impl SyntaxEditor { - /// Creates a syntax editor to start editing from `root` - pub fn new(root: SyntaxNode) -> Self { - Self { root, changes: vec![], mappings: SyntaxMapping::default(), annotations: vec![] } + /// Creates a syntax editor from `root`. + /// + /// Makes sure the root is detached and not mutable by cloning it if needed, + /// so all changes happen only within the editor. + pub fn new(root: SyntaxNode) -> (Self, SyntaxNode) { + let mut root = root; + + if root.parent().is_some() || root.is_mutable() { + root = root.clone_subtree() + }; + + let editor = Self { + root: root.clone(), + changes: Vec::new(), + mappings: SyntaxMapping::default(), + annotations: Vec::new(), + }; + + (editor, root) } pub fn add_annotation(&mut self, element: impl Element, annotation: SyntaxAnnotation) { @@ -420,10 +436,12 @@ mod tests { .into(), ); + let (mut editor, root) = SyntaxEditor::new(root.syntax().clone()); + let root = ast::MatchArm::cast(root).unwrap(); + let to_wrap = root.syntax().descendants().find_map(ast::TupleExpr::cast).unwrap(); let to_replace = root.syntax().descendants().find_map(ast::BinExpr::cast).unwrap(); - let mut editor = SyntaxEditor::new(root.syntax().clone()); let make = SyntaxFactory::with_mappings(); let name = make::name("var_name"); @@ -478,9 +496,10 @@ mod tests { None, ); - let second_let = root.syntax().descendants().find_map(ast::LetStmt::cast).unwrap(); + let (mut editor, root) = SyntaxEditor::new(root.syntax().clone()); + let root = ast::BlockExpr::cast(root).unwrap(); - let mut editor = SyntaxEditor::new(root.syntax().clone()); + let second_let = root.syntax().descendants().find_map(ast::LetStmt::cast).unwrap(); let make = SyntaxFactory::without_mappings(); editor.insert( @@ -530,11 +549,13 @@ mod tests { ), ); + let (mut editor, root) = SyntaxEditor::new(root.syntax().clone()); + let root = ast::BlockExpr::cast(root).unwrap(); + let inner_block = root.syntax().descendants().flat_map(ast::BlockExpr::cast).nth(1).unwrap(); let second_let = root.syntax().descendants().find_map(ast::LetStmt::cast).unwrap(); - let mut editor = SyntaxEditor::new(root.syntax().clone()); let make = SyntaxFactory::with_mappings(); let new_block_expr = make.block_expr([], Some(ast::Expr::BlockExpr(inner_block.clone()))); @@ -584,9 +605,10 @@ mod tests { None, ); - let inner_block = root.clone(); + let (mut editor, root) = SyntaxEditor::new(root.syntax().clone()); + let root = ast::BlockExpr::cast(root).unwrap(); - let mut editor = SyntaxEditor::new(root.syntax().clone()); + let inner_block = root; let make = SyntaxFactory::with_mappings(); let new_block_expr = make.block_expr([], Some(ast::Expr::BlockExpr(inner_block.clone()))); @@ -632,7 +654,8 @@ mod tests { false, ); - let mut editor = SyntaxEditor::new(parent_fn.syntax().clone()); + let (mut editor, parent_fn) = SyntaxEditor::new(parent_fn.syntax().clone()); + let parent_fn = ast::Fn::cast(parent_fn).unwrap(); if let Some(ret_ty) = parent_fn.ret_type() { editor.delete(ret_ty.syntax().clone()); @@ -659,7 +682,9 @@ mod tests { let arg_list = make::arg_list([make::expr_literal("1").into(), make::expr_literal("2").into()]); - let mut editor = SyntaxEditor::new(arg_list.syntax().clone()); + let (mut editor, arg_list) = SyntaxEditor::new(arg_list.syntax().clone()); + let arg_list = ast::ArgList::cast(arg_list).unwrap(); + let target_expr = make::token(parser::SyntaxKind::UNDERSCORE); for arg in arg_list.args() { @@ -677,7 +702,9 @@ mod tests { let arg_list = make::arg_list([make::expr_literal("1").into(), make::expr_literal("2").into()]); - let mut editor = SyntaxEditor::new(arg_list.syntax().clone()); + let (mut editor, arg_list) = SyntaxEditor::new(arg_list.syntax().clone()); + let arg_list = ast::ArgList::cast(arg_list).unwrap(); + let target_expr = make::expr_literal("3").clone_for_update(); for arg in arg_list.args() { @@ -695,7 +722,9 @@ mod tests { let arg_list = make::arg_list([make::expr_literal("1").into(), make::expr_literal("2").into()]); - let mut editor = SyntaxEditor::new(arg_list.syntax().clone()); + let (mut editor, arg_list) = SyntaxEditor::new(arg_list.syntax().clone()); + let arg_list = ast::ArgList::cast(arg_list).unwrap(); + let target_expr = make::ext::expr_unit().clone_for_update(); for arg in arg_list.args() { From cb130c7f009921af53cdfeaba5d5b5209d72859a Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Sun, 5 Apr 2026 11:05:22 +0530 Subject: [PATCH 082/144] update utils with new syntaxEditor constructor --- .../crates/ide-assists/src/utils.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs index c77321ebd1d7e..145a6af7e8e03 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs @@ -248,27 +248,26 @@ pub fn add_trait_assoc_items_to_impl( }) .filter_map(|item| match item { ast::AssocItem::Fn(fn_) if fn_.body().is_none() => { - let fn_ = fn_.clone_subtree(); + let (mut fn_editor, fn_) = SyntaxEditor::new(fn_.syntax().clone()); + let fn_ = ast::Fn::cast(fn_).unwrap(); let fill_expr: ast::Expr = match config.expr_fill_default { ExprFillDefaultMode::Todo | ExprFillDefaultMode::Default => make.expr_todo(), ExprFillDefaultMode::Underscore => make.expr_underscore().into(), }; let new_body = make.block_expr(None::, Some(fill_expr)); - let mut fn_editor = SyntaxEditor::new(fn_.syntax().clone()); fn_.replace_or_insert_body(&mut fn_editor, new_body); let new_fn_ = fn_editor.finish().new_root().clone(); ast::AssocItem::cast(new_fn_) } ast::AssocItem::TypeAlias(type_alias) => { - let type_alias = type_alias.clone_subtree(); + let (mut type_alias_editor, type_alias) = + SyntaxEditor::new(type_alias.syntax().clone()); + let type_alias = ast::TypeAlias::cast(type_alias).unwrap(); if let Some(type_bound_list) = type_alias.type_bound_list() { - let mut type_alias_editor = SyntaxEditor::new(type_alias.syntax().clone()); type_bound_list.remove(&mut type_alias_editor); - let type_alias = type_alias_editor.finish().new_root().clone(); - ast::AssocItem::cast(type_alias) - } else { - Some(ast::AssocItem::TypeAlias(type_alias)) - } + }; + let type_alias = type_alias_editor.finish().new_root().clone(); + ast::AssocItem::cast(type_alias) } item => Some(item), }) From 43f966ad88afa027edc9f5ae073386fccb3f8457 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Sun, 5 Apr 2026 11:05:49 +0530 Subject: [PATCH 083/144] update all assist with new constructor semantics --- .../src/handlers/add_missing_impl_members.rs | 2 +- .../ide-assists/src/handlers/apply_demorgan.rs | 4 ++-- .../ide-assists/src/handlers/convert_bool_then.rs | 8 ++++---- .../src/handlers/convert_for_to_while_let.rs | 4 ++-- .../src/handlers/convert_let_else_to_match.rs | 2 +- .../src/handlers/convert_match_to_let_else.rs | 12 ++++-------- .../handlers/convert_named_struct_to_tuple_struct.rs | 10 +++++----- .../src/handlers/convert_range_for_to_while.rs | 6 +++--- .../src/handlers/convert_to_guarded_return.rs | 7 ++++--- .../handlers/convert_tuple_struct_to_named_struct.rs | 10 ++++++---- .../crates/ide-assists/src/handlers/flip_binexpr.rs | 4 ++-- .../src/handlers/generate_delegate_trait.rs | 7 ++++--- .../src/handlers/generate_getter_or_setter.rs | 2 +- .../src/handlers/generate_mut_trait_impl.rs | 5 +++-- .../src/handlers/generate_trait_from_impl.rs | 5 +++-- .../ide-assists/src/handlers/inline_type_alias.rs | 3 +-- .../src/handlers/introduce_named_lifetime.rs | 6 ++---- .../ide-assists/src/handlers/pull_assignment_up.rs | 4 ++-- .../crates/ide-assists/src/handlers/remove_dbg.rs | 8 ++++---- .../src/handlers/replace_derive_with_manual_impl.rs | 2 +- .../src/handlers/replace_if_let_with_match.rs | 4 ++-- .../src/handlers/replace_qualified_name_with_use.rs | 4 ++-- .../crates/ide-assists/src/handlers/unwrap_block.rs | 2 +- 23 files changed, 60 insertions(+), 61 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs index e43adefe67209..44b367059eca3 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs @@ -175,7 +175,7 @@ fn add_missing_impl_members_inner( ) && let Some(func_body) = func.body() { - let mut func_editor = SyntaxEditor::new(first_new_item.syntax().clone_subtree()); + let (mut func_editor, _) = SyntaxEditor::new(first_new_item.syntax().clone()); func_editor.replace(func_body.syntax(), body.syntax()); ast::AssocItem::cast(func_editor.finish().new_root().clone()) } else { diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs index 4ee49702489d2..9220d127efe2c 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs @@ -82,8 +82,8 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti let make = SyntaxFactory::with_mappings(); - let demorganed = bin_expr.clone_subtree(); - let mut editor = SyntaxEditor::new(demorganed.syntax().clone()); + let (mut editor, demorganed) = SyntaxEditor::new(bin_expr.syntax().clone()); + let demorganed = ast::BinExpr::cast(demorganed).unwrap(); editor.replace(demorganed.op_token()?, make.token(inv_token)); let mut exprs = VecDeque::from([ diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs index b3bfe5b8c41a7..f13d1c1f8624c 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs @@ -77,8 +77,8 @@ pub(crate) fn convert_if_to_bool_then(acc: &mut Assists, ctx: &AssistContext<'_> "Convert `if` expression to `bool::then` call", target, |builder| { - let closure_body = closure_body.clone_subtree(); - let mut editor = SyntaxEditor::new(closure_body.syntax().clone()); + let (mut editor, closure_body) = SyntaxEditor::new(closure_body.syntax().clone()); + let closure_body = ast::Expr::cast(closure_body).unwrap(); // Rewrite all `Some(e)` in tail position to `e` for_each_tail_expr(&closure_body, &mut |e| { let e = match e { @@ -188,8 +188,8 @@ pub(crate) fn convert_bool_then_to_if(acc: &mut Assists, ctx: &AssistContext<'_> e => mapless_make.block_expr(None, Some(e)), }; - let closure_body = closure_body.clone_subtree(); - let mut editor = SyntaxEditor::new(closure_body.syntax().clone()); + let (mut editor, closure_body) = SyntaxEditor::new(closure_body.syntax().clone()); + let closure_body = ast::BlockExpr::cast(closure_body).unwrap(); // Wrap all tails in `Some(...)` let none_path = mapless_make.expr_path(mapless_make.ident_path("None")); let some_path = mapless_make.expr_path(mapless_make.ident_path("Some")); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_for_to_while_let.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_for_to_while_let.rs index 15f324eff3294..a5c29a45a51f2 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_for_to_while_let.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_for_to_while_let.rs @@ -81,14 +81,14 @@ pub(crate) fn convert_for_loop_to_while_let( let indent = IndentLevel::from_node(for_loop.syntax()); if let Some(label) = for_loop.label() { - let label = label.syntax().clone_for_update(); + let label = label.syntax(); editor.insert(Position::before(for_loop.syntax()), make.whitespace(" ")); editor.insert(Position::before(for_loop.syntax()), label); } crate::utils::insert_attributes( for_loop.syntax(), &mut editor, - for_loop.attrs().map(|it| it.clone_for_update()), + for_loop.attrs(), &make, ); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_let_else_to_match.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_let_else_to_match.rs index 5874f66522fd3..20c01d35bb8f5 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_let_else_to_match.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_let_else_to_match.rs @@ -190,7 +190,7 @@ fn remove_mut_and_collect_idents( let inner = p.pat()?; if let ast::Pat::IdentPat(ident) = inner { acc.push(ident); - p.clone_for_update().into() + p.clone().into() } else { make.ref_pat(remove_mut_and_collect_idents(make, &inner, acc)?).into() } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_match_to_let_else.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_match_to_let_else.rs index 1a6d176c9054c..4b132d68ee3a5 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_match_to_let_else.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_match_to_let_else.rs @@ -121,8 +121,7 @@ fn find_extracted_variable(ctx: &AssistContext<'_>, arm: &ast::MatchArm) -> Opti // Rename `extracted` with `binding` in `pat`. fn rename_variable(pat: &ast::Pat, extracted: &[Name], binding: ast::Pat) -> SyntaxNode { - let syntax = pat.syntax().clone_subtree(); - let mut editor = SyntaxEditor::new(syntax.clone()); + let (mut editor, syntax) = SyntaxEditor::new(pat.syntax().clone()); let make = SyntaxFactory::with_mappings(); let extracted = extracted .iter() @@ -138,15 +137,12 @@ fn rename_variable(pat: &ast::Pat, extracted: &[Name], binding: ast::Pat) -> Syn if let Some(name_ref) = record_pat_field.field_name() { editor.replace( record_pat_field.syntax(), - make.record_pat_field( - make.name_ref(&name_ref.text()), - binding.clone_for_update(), - ) - .syntax(), + make.record_pat_field(make.name_ref(&name_ref.text()), binding.clone()) + .syntax(), ); } } else { - editor.replace(extracted_syntax, binding.syntax().clone_for_update()); + editor.replace(extracted_syntax, binding.syntax()); } } editor.add_mappings(make.finish_with_mappings()); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs index aaf727058cf19..3c4f297bddf84 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs @@ -102,11 +102,12 @@ fn edit_struct_def( // Note that we don't need to consider macro files in this function because this is // currently not triggered for struct definitions inside macro calls. let tuple_fields = record_fields.fields().filter_map(|f| { - let field = ast::make::tuple_field(f.visibility(), f.ty()?); - let mut editor = SyntaxEditor::new(field.syntax().clone()); + let (mut editor, field) = + SyntaxEditor::new(ast::make::tuple_field(f.visibility(), f.ty()?).syntax().clone()); + let field = ast::TupleField::cast(field).unwrap(); editor.insert_all( Position::first_child_of(field.syntax()), - f.attrs().map(|attr| attr.syntax().clone_subtree().clone_for_update().into()).collect(), + f.attrs().map(|attr| attr.syntax().clone().into()).collect(), ); let field_syntax = editor.finish().new_root().clone(); let field = ast::TupleField::cast(field_syntax)?; @@ -328,8 +329,7 @@ fn delete_whitespace(edit: &mut SyntaxEditor, whitespace: Option) } fn remove_trailing_comma(w: ast::WhereClause) -> SyntaxNode { - let w = w.syntax().clone_subtree(); - let mut editor = SyntaxEditor::new(w.clone()); + let (mut editor, w) = SyntaxEditor::new(w.syntax().clone()); if let Some(last) = w.last_child_or_token() && last.kind() == T![,] { diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_range_for_to_while.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_range_for_to_while.rs index 2e649f14be26e..09435eeaecda0 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_range_for_to_while.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_range_for_to_while.rs @@ -155,15 +155,15 @@ fn process_loop_body( let block_content = first.clone()..=children.last().unwrap_or(first); let continue_label = make::lifetime("'cont"); - let break_expr = make::expr_break(Some(continue_label.clone()), None).clone_for_update(); - let mut new_edit = SyntaxEditor::new(new_body.syntax().clone()); + let break_expr = make::expr_break(Some(continue_label.clone()), None); + let (mut new_edit, _) = SyntaxEditor::new(new_body.syntax().clone()); for continue_expr in &continues { new_edit.replace(continue_expr.syntax(), break_expr.syntax()); } let new_body = new_edit.finish().new_root().clone(); let elements = itertools::chain( [ - continue_label.syntax().clone_for_update().syntax_element(), + continue_label.syntax().syntax_element(), make::token(T![:]).syntax_element(), make::tokens::single_space().syntax_element(), new_body.syntax_element(), diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs index e59527b0e0951..f5ec60ac8aa1e 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs @@ -261,7 +261,9 @@ impl<'db> ElseBlock<'db> { return block_expr.reset_indent(); } - let block_expr = block_expr.reset_indent().clone_subtree(); + let (mut edit, block_expr) = SyntaxEditor::new(block_expr.reset_indent().syntax().clone()); + let block_expr = ast::BlockExpr::cast(block_expr).unwrap(); + let last_stmt = block_expr.statements().last().map(|it| it.syntax().clone()); let tail_expr = block_expr.tail_expr().map(|it| it.syntax().clone()); let Some(last_element) = tail_expr.clone().or(last_stmt.clone()) else { @@ -270,7 +272,6 @@ impl<'db> ElseBlock<'db> { let whitespace = last_element.prev_sibling_or_token().filter(|it| it.kind() == WHITESPACE); let make = SyntaxFactory::without_mappings(); - let mut edit = SyntaxEditor::new(block_expr.syntax().clone()); if let Some(tail_expr) = block_expr.tail_expr() && !self.kind.is_unit() @@ -280,7 +281,7 @@ impl<'db> ElseBlock<'db> { } else { let last_stmt = match block_expr.tail_expr() { Some(expr) => make.expr_stmt(expr).syntax().clone(), - None => last_element.clone_for_update(), + None => last_element.clone(), }; let whitespace = make.whitespace(&whitespace.map_or(String::new(), |it| it.to_string())); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs index ae41e6c015cef..51bdca449c0af 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs @@ -103,11 +103,13 @@ fn edit_struct_def( names: Vec, ) { let record_fields = tuple_fields.fields().zip(names).filter_map(|(f, name)| { - let field = ast::make::record_field(f.visibility(), name, f.ty()?); - let mut field_editor = SyntaxEditor::new(field.syntax().clone()); + let (mut field_editor, field) = SyntaxEditor::new( + ast::make::record_field(f.visibility(), name, f.ty()?).syntax().clone(), + ); + let field = ast::RecordField::cast(field).unwrap(); field_editor.insert_all( Position::first_child_of(field.syntax()), - f.attrs().map(|attr| attr.syntax().clone_subtree().clone_for_update().into()).collect(), + f.attrs().map(|attr| attr.syntax().clone().into()).collect(), ); ast::RecordField::cast(field_editor.finish().new_root().clone()) }); @@ -120,7 +122,7 @@ fn edit_struct_def( editor.delete(w.syntax()); let mut insert_element = Vec::new(); insert_element.push(ast::make::tokens::single_newline().syntax_element()); - insert_element.push(w.syntax().clone_for_update().syntax_element()); + insert_element.push(w.syntax().syntax_element()); if w.syntax().last_token().is_none_or(|t| t.kind() != SyntaxKind::COMMA) { insert_element.push(ast::make::token(T![,]).into()); } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/flip_binexpr.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/flip_binexpr.rs index 8f2306e9037e7..922a61bf3a854 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/flip_binexpr.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/flip_binexpr.rs @@ -142,11 +142,11 @@ pub(crate) fn flip_range_expr(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opt } (Some(start), None) => { edit.delete(start.syntax()); - edit.insert(Position::after(&op), start.syntax().clone_for_update()); + edit.insert(Position::after(&op), start.syntax()); } (None, Some(end)) => { edit.delete(end.syntax()); - edit.insert(Position::before(&op), end.syntax().clone_for_update()); + edit.insert(Position::before(&op), end.syntax()); } (None, None) => (), } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs index f703e4dc4ab25..92232ba4da038 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs @@ -563,7 +563,8 @@ fn finalize_delegate( return Some(delegate.clone()); } - let mut editor = SyntaxEditor::new(delegate.syntax().clone_subtree()); + let (mut editor, delegate) = SyntaxEditor::new(delegate.syntax().clone()); + let delegate = ast::Impl::cast(delegate).unwrap(); // 1. Replace assoc_item_list if we have new items if let Some(items) = assoc_items @@ -577,7 +578,7 @@ fn finalize_delegate( // 2. Remove useless where clauses if remove_where_clauses { - remove_useless_where_clauses(&mut editor, delegate); + remove_useless_where_clauses(&mut editor, &delegate); } ast::Impl::cast(editor.finish().new_root().clone()) @@ -703,7 +704,7 @@ fn resolve_name_conflicts( } } p @ ast::GenericParam::LifetimeParam(_) => { - new_params.push(p.clone_for_update()); + new_params.push(p); } ast::GenericParam::TypeParam(t) => { let type_bounds = t.type_bound_list(); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_getter_or_setter.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_getter_or_setter.rs index 62ffd3d9656b5..4cd018d02d029 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_getter_or_setter.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_getter_or_setter.rs @@ -429,7 +429,7 @@ fn build_source_change( generate_getter_from_info(ctx, &assist_info, record_field_info, &syntax_factory) } }; - let new_fn = method.clone_for_update(); + let new_fn = method; let new_fn = new_fn.indent(1.into()); new_fn.into() }) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_mut_trait_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_mut_trait_impl.rs index 3a62a8853e3af..f45b68f79c8f3 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_mut_trait_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_mut_trait_impl.rs @@ -67,8 +67,9 @@ pub(crate) fn generate_mut_trait_impl(acc: &mut Assists, ctx: &AssistContext<'_> format!("Generate `{trait_new}` impl from this `{trait_name}` trait"), target, |edit| { - let impl_clone = impl_def.reset_indent().clone_subtree(); - let mut editor = SyntaxEditor::new(impl_clone.syntax().clone()); + let (mut editor, impl_clone) = + SyntaxEditor::new(impl_def.reset_indent().syntax().clone()); + let impl_clone = ast::Impl::cast(impl_clone).unwrap(); let factory = SyntaxFactory::without_mappings(); apply_generate_mut_impl(&mut editor, &factory, &impl_clone, trait_new); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs index 1286abe3565e2..b7fdcce2f3ce9 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs @@ -98,8 +98,9 @@ pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_ impl_ast.syntax().text_range(), |builder| { let trait_items: ast::AssocItemList = { - let trait_items = impl_assoc_items.clone_subtree(); - let mut trait_items_editor = SyntaxEditor::new(trait_items.syntax().clone()); + let (mut trait_items_editor, trait_items) = + SyntaxEditor::new(impl_assoc_items.syntax().clone()); + let trait_items = ast::AssocItemList::cast(trait_items).unwrap(); trait_items.assoc_items().for_each(|item| { strip_body(&mut trait_items_editor, &item); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_type_alias.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_type_alias.rs index f5b5b228f30c5..4b60f0ac1e3cf 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_type_alias.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_type_alias.rs @@ -312,8 +312,7 @@ fn create_replacement( const_and_type_map: &ConstAndTypeMap, concrete_type: &ast::Type, ) -> SyntaxNode { - let updated_concrete_type = concrete_type.syntax().clone_subtree(); - let mut editor = SyntaxEditor::new(updated_concrete_type.clone()); + let (mut editor, updated_concrete_type) = SyntaxEditor::new(concrete_type.syntax().clone()); let mut replacements: Vec<(SyntaxNode, SyntaxNode)> = Vec::new(); let mut removals: Vec> = Vec::new(); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/introduce_named_lifetime.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/introduce_named_lifetime.rs index 854e9561d29a4..5e8ea7daff90d 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/introduce_named_lifetime.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/introduce_named_lifetime.rs @@ -97,8 +97,7 @@ fn generate_fn_def_assist( }; acc.add(AssistId::refactor(ASSIST_NAME), ASSIST_LABEL, lifetime_loc, |edit| { - let root = fn_def.syntax().ancestors().last().unwrap().clone(); - let mut editor = SyntaxEditor::new(root); + let mut editor = edit.make_editor(fn_def.syntax()); let factory = SyntaxFactory::with_mappings(); if let Some(generic_list) = fn_def.generic_param_list() { @@ -167,8 +166,7 @@ fn generate_impl_def_assist( let new_lifetime_name = generate_unique_lifetime_param_name(impl_def.generic_param_list())?; acc.add(AssistId::refactor(ASSIST_NAME), ASSIST_LABEL, lifetime_loc, |edit| { - let root = impl_def.syntax().ancestors().last().unwrap().clone(); - let mut editor = SyntaxEditor::new(root); + let mut editor = edit.make_editor(impl_def.syntax()); let factory = SyntaxFactory::without_mappings(); if let Some(generic_list) = impl_def.generic_param_list() { diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/pull_assignment_up.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/pull_assignment_up.rs index 812ebf6c6e289..74ed2e14fa239 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/pull_assignment_up.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/pull_assignment_up.rs @@ -75,7 +75,8 @@ pub(crate) fn pull_assignment_up(acc: &mut Assists, ctx: &AssistContext<'_>) -> } let target = tgt.syntax().text_range(); - let edit_tgt = tgt.syntax().clone_subtree(); + let (mut editor, edit_tgt) = SyntaxEditor::new(tgt.syntax().clone()); + let assignments: Vec<_> = collector .assignments .into_iter() @@ -93,7 +94,6 @@ pub(crate) fn pull_assignment_up(acc: &mut Assists, ctx: &AssistContext<'_>) -> }) .collect(); - let mut editor = SyntaxEditor::new(edit_tgt); for (stmt, rhs) in assignments { let mut stmt = stmt.syntax().clone(); if let Some(parent) = stmt.parent() diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs index 08779a3ed1f77..d56d85d12d0d7 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs @@ -50,7 +50,7 @@ pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<( let mut editor = builder.make_editor(ctx.source_file().syntax()); for (range, expr) in replacements { if let Some(expr) = expr { - editor.insert(Position::before(range[0].clone()), expr.syntax().clone_for_update()); + editor.insert(Position::before(range[0].clone()), expr.syntax()); } for node_or_token in range { editor.delete(node_or_token); @@ -209,8 +209,8 @@ fn replace_nested_dbgs(expanded: ast::Expr) -> ast::Expr { return replaced; } - let expanded = expanded.clone_subtree(); - let mut editor = SyntaxEditor::new(expanded.syntax().clone()); + let (mut editor, expanded) = SyntaxEditor::new(expanded.syntax().clone()); + let expanded = ast::Expr::cast(expanded).unwrap(); // We need to collect to avoid mutation during traversal. let macro_exprs: Vec<_> = expanded.syntax().descendants().filter_map(ast::MacroExpr::cast).collect(); @@ -222,7 +222,7 @@ fn replace_nested_dbgs(expanded: ast::Expr) -> ast::Expr { }; if let Some(expr) = expr_opt { - editor.replace(mac.syntax(), expr.syntax().clone_for_update()); + editor.replace(mac.syntax(), expr.syntax()); } else { editor.delete(mac.syntax()); } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs index 01299729bc864..62b4e04950492 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs @@ -227,7 +227,7 @@ fn impl_def_from_trait( && let Some(body) = gen_trait_fn_body(&make, func, trait_path, adt, None) && let Some(func_body) = func.body() { - let mut editor = SyntaxEditor::new(first.syntax().clone()); + let (mut editor, _) = SyntaxEditor::new(first.syntax().clone()); editor.replace(func_body.syntax(), body.syntax()); ast::AssocItem::cast(editor.finish().new_root().clone()) } else { diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_if_let_with_match.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_if_let_with_match.rs index 2bbce2b2c0800..2730f5cb7bef2 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_if_let_with_match.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_if_let_with_match.rs @@ -402,8 +402,8 @@ fn let_and_guard(cond: &ast::Expr) -> (Option, Option) } else if let ast::Expr::BinExpr(bin_expr) = cond && let Some(ast::Expr::LetExpr(let_expr)) = and_bin_expr_left(bin_expr).lhs() { - let new_expr = bin_expr.clone_subtree(); - let mut edit = SyntaxEditor::new(new_expr.syntax().clone()); + let (mut edit, new_expr) = SyntaxEditor::new(bin_expr.syntax().clone()); + let new_expr = ast::BinExpr::cast(new_expr).unwrap(); let left_bin = and_bin_expr_left(&new_expr); if let Some(rhs) = left_bin.rhs() { diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs index cdf20586ef153..693a081e91089 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs @@ -111,8 +111,8 @@ fn target_path(ctx: &AssistContext<'_>, mut original_path: ast::Path) -> Option< } fn drop_generic_args(path: &ast::Path) -> ast::Path { - let path = path.clone_subtree(); - let mut editor = SyntaxEditor::new(path.syntax().clone()); + let (mut editor, path) = SyntaxEditor::new(path.syntax().clone()); + let path = ast::Path::cast(path).unwrap(); if let Some(segment) = path.segment() && let Some(generic_args) = segment.generic_arg_list() { diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs index e029d7884fd52..87e61b35d8c4f 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs @@ -115,7 +115,7 @@ fn wrap_let(assign: &ast::LetStmt, replacement: ast::BlockExpr) -> ast::BlockExp .skip(1) .collect(); - let mut edit = SyntaxEditor::new(replacement.syntax().clone()); + let (mut edit, _) = SyntaxEditor::new(replacement.syntax().clone()); edit.insert_all(Position::before(tail_expr.syntax()), before); edit.insert_all(Position::after(tail_expr.syntax()), after); ast::BlockExpr::cast(edit.finish().new_root().clone()) From 65157a2e04c71adaee0044c98c6693653bb9f8b2 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Sun, 5 Apr 2026 11:06:24 +0530 Subject: [PATCH 084/144] update ide-db with new syntaxEditor --- .../crates/ide-db/src/imports/insert_use.rs | 6 +-- .../crates/ide-db/src/path_transform.rs | 53 +++++++++---------- .../crates/ide-db/src/source_change.rs | 2 +- 3 files changed, 27 insertions(+), 34 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use.rs b/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use.rs index da8525d1fb72b..3a109a48e4892 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use.rs @@ -305,10 +305,8 @@ fn insert_use_with_alias_option_with_editor( if mb == Some(MergeBehavior::One) && use_tree.path().is_some() { use_tree.wrap_in_tree_list(); } - let use_item = make::use_(None, None, use_tree).clone_for_update(); - for attr in - scope.required_cfgs.iter().map(|attr| attr.syntax().clone_subtree().clone_for_update()) - { + let use_item = make::use_(None, None, use_tree); + for attr in scope.required_cfgs.iter().map(|attr| attr.syntax().clone()) { syntax_editor.insert(Position::first_child_of(use_item.syntax()), attr); } diff --git a/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs b/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs index 508f841340b1b..3fd15057723c6 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs @@ -197,7 +197,7 @@ impl<'a> PathTransform<'a> { && let Some(default) = &default.display_source_code(db, source_module.into(), false).ok() { - type_substs.insert(k, make::ty(default).clone_for_update()); + type_substs.insert(k, make::ty(default)); defaulted_params.push(Either::Left(k)); } } @@ -222,7 +222,7 @@ impl<'a> PathTransform<'a> { k.default(db, target_module.krate(db).to_display_target(db)) && let Some(default) = default.expr() { - const_substs.insert(k, default.syntax().clone_for_update()); + const_substs.insert(k, default.syntax().clone()); defaulted_params.push(Either::Right(k)); } } @@ -278,12 +278,10 @@ impl Ctx<'_> { // `transform_path` may update a node's parent and that would break the // tree traversal. Thus all paths in the tree are collected into a vec // so that such operation is safe. - let item = self.transform_path(item).clone_subtree(); - let mut editor = SyntaxEditor::new(item.clone()); + let (mut editor, item) = SyntaxEditor::new(self.transform_path(item)); preorder_rev(&item).filter_map(ast::Lifetime::cast).for_each(|lifetime| { if let Some(subst) = self.lifetime_substs.get(&lifetime.syntax().text().to_string()) { - editor - .replace(lifetime.syntax(), subst.clone_subtree().clone_for_update().syntax()); + editor.replace(lifetime.syntax(), subst.clone().syntax()); } }); @@ -331,18 +329,14 @@ impl Ctx<'_> { result } - let root_path = path.clone_subtree(); - + let (mut editor, root_path) = SyntaxEditor::new(path.clone()); let result = find_child_paths_and_ident_pats(&root_path); - let mut editor = SyntaxEditor::new(root_path.clone()); for sub_path in result { let new = self.transform_path(sub_path.syntax()); editor.replace(sub_path.syntax(), new); } - - let update_sub_item = editor.finish().new_root().clone().clone_subtree(); + let (mut editor, update_sub_item) = SyntaxEditor::new(editor.finish().new_root().clone()); let item = find_child_paths_and_ident_pats(&update_sub_item); - let mut editor = SyntaxEditor::new(update_sub_item); for sub_path in item { self.transform_path_or_ident_pat(&mut editor, &sub_path); } @@ -411,12 +405,12 @@ impl Ctx<'_> { let segment = make::path_segment_ty(subst.clone(), trait_ref); let qualified = make::path_from_segments(std::iter::once(segment), false); - editor.replace(path.syntax(), qualified.clone_for_update().syntax()); + editor.replace(path.syntax(), qualified.clone().syntax()); } else if let Some(path_ty) = ast::PathType::cast(parent) { let old = path_ty.syntax(); if old.parent().is_some() { - editor.replace(old, subst.clone_subtree().clone_for_update().syntax()); + editor.replace(old, subst.clone().syntax()); } else { // Some `path_ty` has no parent, especially ones made for default value // of type parameters. @@ -434,10 +428,7 @@ impl Ctx<'_> { ); } } else { - editor.replace( - path.syntax(), - subst.clone_subtree().clone_for_update().syntax(), - ); + editor.replace(path.syntax(), subst.clone().syntax()); } } } @@ -459,18 +450,18 @@ impl Ctx<'_> { allow_unstable: true, }; let found_path = self.target_module.find_path(self.source_scope.db, def, cfg)?; - let res = mod_path_to_ast(&found_path, self.target_edition).clone_for_update(); - let mut res_editor = SyntaxEditor::new(res.syntax().clone_subtree()); + let res = mod_path_to_ast(&found_path, self.target_edition); + let (mut res_editor, res) = SyntaxEditor::new(res.syntax().clone()); + let res = ast::Path::cast(res).unwrap(); if let Some(args) = path.segment().and_then(|it| it.generic_arg_list()) && let Some(segment) = res.segment() { if let Some(old) = segment.generic_arg_list() { - res_editor - .replace(old.syntax(), args.clone_subtree().syntax().clone_for_update()) + res_editor.replace(old.syntax(), args.syntax().clone()) } else { res_editor.insert( syntax_editor::Position::last_child_of(segment.syntax()), - args.clone_subtree().syntax().clone_for_update(), + args.syntax().clone(), ); } } @@ -479,7 +470,7 @@ impl Ctx<'_> { } hir::PathResolution::ConstParam(cp) => { if let Some(subst) = self.const_substs.get(&cp) { - editor.replace(path.syntax(), subst.clone_subtree().clone_for_update()); + editor.replace(path.syntax(), subst.clone()); } } hir::PathResolution::SelfType(imp) => { @@ -496,7 +487,7 @@ impl Ctx<'_> { true, ) .ok()?; - let ast_ty = make::ty(ty_str).clone_for_update(); + let ast_ty = make::ty(ty_str); if let Some(adt) = ty.as_adt() && let ast::Type::PathType(path_ty) = &ast_ty @@ -516,8 +507,10 @@ impl Ctx<'_> { if let Some(qual) = mod_path_to_ast(&found_path, self.target_edition).qualifier() { - let res = make::path_concat(qual, path_ty.path()?).clone_for_update(); - editor.replace(path.syntax(), res.syntax()); + editor.replace( + path.syntax(), + make::path_concat(qual, path_ty.path()?).syntax(), + ); return Some(()); } } @@ -593,8 +586,10 @@ impl Ctx<'_> { allow_unstable: true, }; let found_path = self.target_module.find_path(self.source_scope.db, def, cfg)?; - let res = mod_path_to_ast(&found_path, self.target_edition).clone_for_update(); - editor.replace(ident_pat.syntax(), res.syntax()); + editor.replace( + ident_pat.syntax(), + mod_path_to_ast(&found_path, self.target_edition).syntax(), + ); Some(()) } _ => None, diff --git a/src/tools/rust-analyzer/crates/ide-db/src/source_change.rs b/src/tools/rust-analyzer/crates/ide-db/src/source_change.rs index 57072bb5ba36c..4a83f707fcacd 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/source_change.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/source_change.rs @@ -282,7 +282,7 @@ impl SourceChangeBuilder { } pub fn make_editor(&self, node: &SyntaxNode) -> SyntaxEditor { - SyntaxEditor::new(node.ancestors().last().unwrap_or_else(|| node.clone())) + SyntaxEditor::new(node.ancestors().last().unwrap_or_else(|| node.clone())).0 } pub fn add_file_edits(&mut self, file_id: impl Into, edit: SyntaxEditor) { From 3dd6020f65d825b1d4ccf48ccd076fa7afb94941 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Sun, 5 Apr 2026 11:06:50 +0530 Subject: [PATCH 085/144] update syntax with new syntaxeditor --- src/tools/rust-analyzer/crates/syntax/src/ast/edit.rs | 6 ++---- .../rust-analyzer/crates/syntax/src/syntax_editor/edits.rs | 5 +++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/edit.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/edit.rs index b706d7f722f4e..23a0411eadbd6 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/edit.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/edit.rs @@ -105,8 +105,7 @@ impl IndentLevel { } pub(super) fn clone_increase_indent(self, node: &SyntaxNode) -> SyntaxNode { - let node = node.clone_subtree(); - let mut editor = SyntaxEditor::new(node.clone()); + let (mut editor, node) = SyntaxEditor::new(node.clone()); let tokens = node .preorder_with_tokens() .filter_map(|event| match event { @@ -140,8 +139,7 @@ impl IndentLevel { } pub(super) fn clone_decrease_indent(self, node: &SyntaxNode) -> SyntaxNode { - let node = node.clone_subtree(); - let mut editor = SyntaxEditor::new(node.clone()); + let (mut editor, node) = SyntaxEditor::new(node.clone()); let tokens = node .preorder_with_tokens() .filter_map(|event| match event { diff --git a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edits.rs b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edits.rs index 44f0a8038eca5..253df826d7d61 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edits.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edits.rs @@ -473,8 +473,9 @@ enum Foo { } fn check_add_variant(before: &str, expected: &str, variant: ast::Variant) { - let enum_ = ast_from_text::(before); - let mut editor = SyntaxEditor::new(enum_.syntax().clone()); + let (mut editor, enum_) = + SyntaxEditor::new(ast_from_text::(before).syntax().clone()); + let enum_ = ast::Enum::cast(enum_).unwrap(); if let Some(it) = enum_.variant_list() { it.add_variant(&mut editor, &variant) } From fc2f17781e0b9ddf122ccaed23c9a2372fb86c25 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Mon, 6 Apr 2026 03:16:08 +0300 Subject: [PATCH 086/144] Fix SyntaxEditor upmapping of nodes with mapped ancestor that aren't mapped themselves There is no point to expect a connection between the mapped ancestor and the unmapped child. --- .../rust-analyzer/crates/syntax/src/syntax_editor/mapping.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/mapping.rs b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/mapping.rs index 6257bf4e572ec..180c2e69fa3e9 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/mapping.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/mapping.rs @@ -161,7 +161,7 @@ impl SyntaxMapping { // Try to follow the mapping tree, if it exists let input_mapping = self.upmap_node_single(input); let input_ancestor = - input.ancestors().find_map(|ancestor| self.upmap_node_single(&ancestor)); + input.ancestors().find(|ancestor| self.upmap_node_single(ancestor).is_some()); match (input_mapping, input_ancestor) { (Some(input_mapping), _) => { From 8158fb0e7aacaac62381b6a853b62865132b4394 Mon Sep 17 00:00:00 2001 From: Asuka Minato Date: Thu, 2 Apr 2026 00:34:53 +0900 Subject: [PATCH 087/144] impl part of the pattern detect {$0} reduce reduction update based on comment clippy --- .../crates/ide/src/typing/on_enter.rs | 193 ++++++++++++------ .../book/src/contributing/lsp-extensions.md | 2 +- 2 files changed, 133 insertions(+), 62 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide/src/typing/on_enter.rs b/src/tools/rust-analyzer/crates/ide/src/typing/on_enter.rs index 82f12783980d2..7d04594a5bf8d 100644 --- a/src/tools/rust-analyzer/crates/ide/src/typing/on_enter.rs +++ b/src/tools/rust-analyzer/crates/ide/src/typing/on_enter.rs @@ -1,12 +1,11 @@ -//! Handles the `Enter` key press. At the momently, this only continues -//! comments, but should handle indent some time in the future as well. +//! Handles the `Enter` key press, including comment continuation and +//! indentation in brace-delimited constructs. use ide_db::{FilePosition, RootDatabase}; use syntax::{ AstNode, SmolStr, SourceFile, SyntaxKind::*, - SyntaxNode, SyntaxToken, TextRange, TextSize, TokenAtOffset, - algo::find_node_at_offset, + SyntaxToken, TextRange, TextSize, TokenAtOffset, ast::{self, AstToken, edit::IndentLevel}, }; @@ -19,7 +18,8 @@ use ide_db::text_edit::TextEdit; // - Enter inside triple-slash comments automatically inserts `///` // - Enter in the middle or after a trailing space in `//` inserts `//` // - Enter inside `//!` doc comments automatically inserts `//!` -// - Enter after `{` indents contents and closing `}` of single-line block +// - Enter after `{` reformats single-line brace-delimited contents by +// moving the text between `{` and the matching `}` onto an indented line // // This action needs to be assigned to shortcut explicitly. // @@ -59,22 +59,11 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option Option { - let contents = block_contents(&block)?; - - if block.syntax().text().contains_char('\n') { - return None; - } - - let indent = IndentLevel::from_node(block.syntax()); - let mut edit = TextEdit::insert(position.offset, format!("\n{}$0", indent + 1)); - edit.union(TextEdit::insert(contents.text_range().end(), format!("\n{indent}"))).ok()?; - Some(edit) -} - -fn on_enter_in_use_tree_list(list: ast::UseTreeList, position: FilePosition) -> Option { - if list.syntax().text().contains_char('\n') { +fn on_enter_in_braces(l_curly: SyntaxToken, position: FilePosition) -> Option { + if l_curly.text_range().end() != position.offset { return None; } - let indent = IndentLevel::from_node(list.syntax()); - let mut edit = TextEdit::insert(position.offset, format!("\n{}$0", indent + 1)); - edit.union(TextEdit::insert(list.r_curly_token()?.text_range().start(), format!("\n{indent}"))) - .ok()?; - Some(edit) + let (r_curly, content) = brace_contents_on_same_line(&l_curly)?; + let indent = IndentLevel::from_token(&l_curly); + Some(TextEdit::replace( + TextRange::new(position.offset, r_curly.text_range().start()), + format!("\n{}$0{}\n{indent}", indent + 1, content), + )) } -fn block_contents(block: &ast::BlockExpr) -> Option { - let mut node = block.tail_expr().map(|e| e.syntax().clone()); +fn brace_contents_on_same_line(l_curly: &SyntaxToken) -> Option<(SyntaxToken, String)> { + let mut depth = 0_u32; + let mut tokens = Vec::new(); + let mut token = l_curly.next_token()?; - for stmt in block.statements() { - if node.is_some() { - // More than 1 node in the block + loop { + if token.kind() == WHITESPACE && token.text().contains('\n') { return None; } - node = Some(stmt.syntax().clone()); - } + match token.kind() { + L_CURLY => { + depth += 1; + tokens.push(token.clone()); + } + R_CURLY if depth == 0 => { + let first = tokens.iter().position(|it| it.kind() != WHITESPACE); + let last = tokens.iter().rposition(|it| it.kind() != WHITESPACE); + let content = match first.zip(last) { + Some((first, last)) => { + tokens[first..=last].iter().map(|it| it.text()).collect() + } + None => String::new(), + }; + return Some((token, content)); + } + R_CURLY => { + depth -= 1; + tokens.push(token.clone()); + } + _ => tokens.push(token.clone()), + } - node + token = token.next_token()?; + } } fn followed_by_comment(comment: &ast::Comment) -> bool { @@ -382,10 +381,58 @@ fn main() { } #[test] - fn indents_fn_body_block() { + fn indents_empty_brace_pairs() { cov_mark::check!(indent_block_contents); do_check( r#" +fn f() {$0} + "#, + r#" +fn f() { + $0 +} + "#, + ); + do_check( + r#" +fn f() { + let x = {$0}; +} + "#, + r#" +fn f() { + let x = { + $0 + }; +} + "#, + ); + do_check( + r#" +use crate::{$0}; + "#, + r#" +use crate::{ + $0 +}; + "#, + ); + do_check( + r#" +mod m {$0} + "#, + r#" +mod m { + $0 +} + "#, + ); + } + + #[test] + fn indents_fn_body_block() { + do_check( + r#" fn f() {$0()} "#, r#" @@ -477,29 +524,39 @@ fn f() { } #[test] - fn does_not_indent_empty_block() { - do_check_noop( + fn indents_block_with_multiple_statements() { + do_check( r#" -fn f() {$0} +fn f() {$0 a = b; ()} + "#, + r#" +fn f() { + $0a = b; () +} "#, ); - do_check_noop( + do_check( r#" -fn f() {{$0}} +fn f() {$0 a = b; a = b; } + "#, + r#" +fn f() { + $0a = b; a = b; +} "#, ); } #[test] - fn does_not_indent_block_with_too_much_content() { - do_check_noop( + fn trims_spaces_around_brace_contents() { + do_check( r#" -fn f() {$0 a = b; ()} +fn f() {$0 () } "#, - ); - do_check_noop( r#" -fn f() {$0 a = b; a = b; } +fn f() { + $0() +} "#, ); } @@ -569,6 +626,20 @@ use { ); } + #[test] + fn indents_item_lists() { + do_check( + r#" +mod m {$0} + "#, + r#" +mod m { + $0 +} + "#, + ); + } + #[test] fn does_not_indent_use_tree_list_when_not_at_curly_brace() { do_check_noop( diff --git a/src/tools/rust-analyzer/docs/book/src/contributing/lsp-extensions.md b/src/tools/rust-analyzer/docs/book/src/contributing/lsp-extensions.md index 5d21c37806ddd..22c1784ac293c 100644 --- a/src/tools/rust-analyzer/docs/book/src/contributing/lsp-extensions.md +++ b/src/tools/rust-analyzer/docs/book/src/contributing/lsp-extensions.md @@ -236,7 +236,7 @@ fn main() { ``` The primary goal of `onEnter` is to handle automatic indentation when opening a new line. -This is not yet implemented. +This is partially implemented for single-line brace-delimited contents, in addition to comment continuation. The secondary goal is to handle fixing up syntax, like continuing doc strings and comments, and escaping `\n` in string literals. As proper cursor positioning is raison d'être for `onEnter`, it uses `SnippetTextEdit`. From 0f328eb738e9883183ccbed606ab8f1d2c44d461 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Mon, 6 Apr 2026 12:10:00 +0800 Subject: [PATCH 088/144] Renames a constant variable name --- .../ide-completion/src/completions/postfix/format_like.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix/format_like.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix/format_like.rs index db9b6d0bf3514..85a8899fd10b0 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix/format_like.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix/format_like.rs @@ -40,7 +40,7 @@ static KINDS: &[(&str, &str)] = &[ ("logw", "log::warn!"), ("loge", "log::error!"), ]; -static HAS_VALUE: &[&str] = &["format"]; +static SNIPPET_RETURNS_NON_UNIT: &[&str] = &["format"]; pub(crate) fn add_format_like_completions( acc: &mut Completions, @@ -66,7 +66,7 @@ pub(crate) fn add_format_like_completions( let exprs = with_placeholders(exprs); for (label, macro_name) in KINDS { - let semi = if HAS_VALUE.contains(label) { "" } else { semi }; + let semi = if SNIPPET_RETURNS_NON_UNIT.contains(label) { "" } else { semi }; let snippet = if exprs.is_empty() { format!(r#"{macro_name}({out}){semi}"#) } else { From f170efdfb673a56ef28ffee1a9e70d23bc0da05b Mon Sep 17 00:00:00 2001 From: "Matt \"Siyuan\" Yan" Date: Mon, 6 Apr 2026 14:08:42 +0900 Subject: [PATCH 089/144] fix: load rust-analyzer.toml for virtual workspaces --- .../crates/load-cargo/src/lib.rs | 27 ++++++++++++ .../rust-analyzer/tests/slow-tests/ratoml.rs | 42 +++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs b/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs index 654ff4f75b0e6..c91acd9cf98b2 100644 --- a/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs +++ b/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs @@ -282,6 +282,19 @@ impl ProjectFolders { } } + // Collect workspace roots not already covered by a local PackageRoot + // (e.g. virtual workspaces where no package lives at the workspace root). + // We need these to load workspace-root rust-analyzer.toml into a local source root. + let uncovered_ws_roots: Vec = workspaces + .iter() + .filter_map(|ws| { + let ws_root = ws.workspace_root().to_path_buf(); + let dominated = + roots.iter().any(|root| root.is_local && root.include.contains(&ws_root)); + (!dominated).then_some(ws_root) + }) + .collect(); + for root in roots.into_iter().filter(|it| !it.include.is_empty()) { let file_set_roots: Vec = root.include.iter().cloned().map(VfsPath::from).collect(); @@ -334,6 +347,20 @@ impl ProjectFolders { } } + // For virtual workspaces, the workspace root has no local PackageRoot, so + // rust-analyzer.toml there would fall into a library source root and be + // ignored. Load it explicitly via Entry::Files and register the workspace + // root as a local file-set root so the file is classified as local. + for ws_root in &uncovered_ws_roots { + let ratoml_path = ws_root.join("rust-analyzer.toml"); + let file_set_roots = vec![VfsPath::from(ws_root.clone())]; + let entry = vfs::loader::Entry::Files(vec![ratoml_path]); + res.watch.push(res.load.len()); + res.load.push(entry); + local_filesets.push(fsc.len() as u64); + fsc.add_file_set(file_set_roots); + } + if let Some(user_config_path) = user_config_dir_path { let ratoml_path = { let mut p = user_config_path.to_path_buf(); diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/ratoml.rs b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/ratoml.rs index cac7efd84aaf5..dd113babffeb5 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/ratoml.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/ratoml.rs @@ -1008,3 +1008,45 @@ fn main() { InternalTestingFetchConfigResponse::CheckWorkspace(true), ); } + +#[test] +fn ratoml_virtual_workspace() { + if skip_slow_tests() { + return; + } + + let server = RatomlTest::new( + vec![ + r#" +//- /p1/Cargo.toml +[workspace] +members = ["member"] +"#, + r#" +//- /p1/rust-analyzer.toml +assist.emitMustUse = true +"#, + r#" +//- /p1/member/Cargo.toml +[package] +name = "member" +version = "0.1.0" +edition = "2021" +"#, + r#" +//- /p1/member/src/lib.rs +pub fn add(left: usize, right: usize) -> usize { + left + right +} +"#, + ], + vec!["p1"], + None, + ); + + server.query( + InternalTestingFetchConfigOption::AssistEmitMustUse, + 3, + InternalTestingFetchConfigResponse::AssistEmitMustUse(true), + ); +} From f5913b4ad15cc3b23dcc2681a9d9c2ca6ff699fb Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 6 Apr 2026 11:07:06 +0530 Subject: [PATCH 090/144] add typed syntax editor constructor API --- .../crates/syntax/src/syntax_editor.rs | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs index 84559753a29f7..a21a5dd3aad8d 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs @@ -14,7 +14,7 @@ use std::{ use rowan::TextRange; use rustc_hash::FxHashMap; -use crate::{SyntaxElement, SyntaxNode, SyntaxToken}; +use crate::{AstNode, SyntaxElement, SyntaxNode, SyntaxToken}; mod edit_algo; mod edits; @@ -53,6 +53,26 @@ impl SyntaxEditor { (editor, root) } + pub fn new_typed(root: &T) -> (Self, T) + where + T: AstNode, + { + let mut root = root.syntax().clone(); + + if root.parent().is_some() || root.is_mutable() { + root = root.clone_subtree() + }; + + let editor = Self { + root: root.clone(), + changes: Vec::new(), + mappings: SyntaxMapping::default(), + annotations: Vec::new(), + }; + + (editor, T::cast(root).unwrap()) + } + pub fn add_annotation(&mut self, element: impl Element, annotation: SyntaxAnnotation) { self.annotations.push((element.syntax_element(), annotation)) } From 3021aec9e53b1136745831d1c5a82bf7a54c9313 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Mon, 6 Apr 2026 08:58:04 +0300 Subject: [PATCH 091/144] Improve TypeScript code - Update tsc and eslint - Handle TypeScript deprecations - Handle new lints --- .../editors/code/package-lock.json | 775 +++++++++--------- .../rust-analyzer/editors/code/package.json | 14 +- .../rust-analyzer/editors/code/src/client.ts | 2 +- .../editors/code/src/commands.ts | 3 +- .../rust-analyzer/editors/code/src/config.ts | 2 +- .../rust-analyzer/editors/code/src/debug.ts | 2 +- .../editors/code/src/dependencies_provider.ts | 6 +- .../editors/code/src/diagnostics.ts | 2 +- .../editors/code/src/toolchain.ts | 2 +- .../rust-analyzer/editors/code/tsconfig.json | 1 - 10 files changed, 400 insertions(+), 409 deletions(-) diff --git a/src/tools/rust-analyzer/editors/code/package-lock.json b/src/tools/rust-analyzer/editors/code/package-lock.json index 2c9ce1d948ef7..24e65e2487f44 100644 --- a/src/tools/rust-analyzer/editors/code/package-lock.json +++ b/src/tools/rust-analyzer/editors/code/package-lock.json @@ -17,8 +17,8 @@ "vscode-languageclient": "^9.0.1" }, "devDependencies": { - "@eslint/js": "^9.21.0", - "@stylistic/eslint-plugin": "^4.1.0", + "@eslint/js": "^10.0.1", + "@stylistic/eslint-plugin": "^5.10.0", "@stylistic/eslint-plugin-js": "^4.1.0", "@tsconfig/strictest": "^2.0.5", "@types/lodash": "^4.17.20", @@ -29,14 +29,14 @@ "@vscode/test-electron": "^2.4.1", "@vscode/vsce": "^3.7.1", "esbuild": "^0.25.0", - "eslint": "^9.21.0", - "eslint-config-prettier": "^10.0.2", + "eslint": "^10.2.0", + "eslint-config-prettier": "^10.1.8", "eslint-define-config": "^2.1.0", "ovsx": "0.10.10", - "prettier": "^3.5.2", + "prettier": "^3.8.1", "tslib": "^2.8.1", - "typescript": "^5.7.3", - "typescript-eslint": "^8.25.0" + "typescript": "^6.0.2", + "typescript-eslint": "^8.58.0" }, "engines": { "vscode": "^1.93.0" @@ -715,9 +715,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", - "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -747,9 +747,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, "license": "MIT", "engines": { @@ -757,137 +757,89 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz", - "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==", + "version": "0.23.4", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.4.tgz", + "integrity": "sha512-lf19F24LSMfF8weXvW5QEtnLqW70u7kgit5e9PSx0MsHAFclGd1T9ynvWEMDT1w5J4Qt54tomGeAhdoAku1Xow==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.6", + "@eslint/object-schema": "^3.0.4", "debug": "^4.3.1", - "minimatch": "^3.1.2" + "minimatch": "^10.2.4" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", - "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "node_modules/@eslint/config-helpers": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.4.tgz", + "integrity": "sha512-jJhqiY3wPMlWWO3370M86CPJ7pt8GmEwSLglMfQhjXal07RCvhmU0as4IuUEW5SJeunfItiEetHmSxCCe9lDBg==", "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", + "license": "Apache-2.0", "dependencies": { - "brace-expansion": "^1.1.7" + "@eslint/core": "^1.2.0" }, "engines": { - "node": "*" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/core": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", - "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.0.tgz", + "integrity": "sha512-8FTGbNzTvmSlc4cZBaShkC6YvFMG0riksYWRFKXztqVdXaQbcZLXlFbSpC05s70sGEsXAw0qwhx69JiW7hQS7A==", "dev": true, "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.0.tgz", - "integrity": "sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==", + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", "dev": true, "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", - "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" + "url": "https://eslint.org/donate" }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/js": { - "version": "9.21.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.21.0.tgz", - "integrity": "sha512-BqStZ3HX8Yz6LvsF5ByXYrtigrV5AXADWLAGc7PH/1SxOb7/FIYYMszZZWiUou/GB9P2lXWk2SV4d+Z8h0nknw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, "node_modules/@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.4.tgz", + "integrity": "sha512-55lO/7+Yp0ISKRP0PsPtNTeNGapXaO085aELZmWCVc5SH3jfrqpuU6YgOdIxMS99ZHkQN1cXKE+cdIqwww9ptw==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz", - "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.0.tgz", + "integrity": "sha512-ejvBr8MQCbVsWNZnCwDXjUKq40MDmHalq7cJ6e9s/qzTUFIIo/afzt1Vui9T97FM/V/pN4YsFVoed5NIa96RDg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.12.0", + "@eslint/core": "^1.2.0", "levn": "^0.4.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@hpcc-js/wasm": { @@ -1517,23 +1469,24 @@ } }, "node_modules/@stylistic/eslint-plugin": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-4.1.0.tgz", - "integrity": "sha512-bytbL7qiici7yPyEiId0fGPK9kjQbzcPMj2aftPfzTCyJ/CRSKdtI+iVjM0LSGzGxfunflI+MDDU9vyIIeIpoQ==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.10.0.tgz", + "integrity": "sha512-nPK52ZHvot8Ju/0A4ucSX1dcPV2/1clx0kLcH5wDmrE4naKso7TUC/voUyU1O9OTKTrR6MYip6LP0ogEMQ9jPQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/utils": "^8.23.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/types": "^8.56.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "estraverse": "^5.3.0", - "picomatch": "^4.0.2" + "picomatch": "^4.0.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "peerDependencies": { - "eslint": ">=9.0.0" + "eslint": "^9.0.0 || ^10.0.0" } }, "node_modules/@stylistic/eslint-plugin-js": { @@ -1553,6 +1506,37 @@ "eslint": ">=9.0.0" } }, + "node_modules/@stylistic/eslint-plugin/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@textlint/ast-node-types": { "version": "15.2.1", "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-15.2.1.tgz", @@ -1701,10 +1685,17 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, "license": "MIT" }, @@ -1754,21 +1745,20 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.25.0.tgz", - "integrity": "sha512-VM7bpzAe7JO/BFf40pIT1lJqS/z1F8OaSsUB3rpFJucQA4cOSuH2RVVVkFULN+En0Djgr29/jb4EQnedUo95KA==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.0.tgz", + "integrity": "sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.25.0", - "@typescript-eslint/type-utils": "8.25.0", - "@typescript-eslint/utils": "8.25.0", - "@typescript-eslint/visitor-keys": "8.25.0", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.58.0", + "@typescript-eslint/type-utils": "8.58.0", + "@typescript-eslint/utils": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0", + "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.0.1" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1778,23 +1768,56 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "@typescript-eslint/parser": "^8.58.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.25.0.tgz", - "integrity": "sha512-4gbs64bnbSzu4FpgMiQ1A+D+urxkoJk/kqlDJ2W//5SygaEiAP2B4GoS7TEdxgwol2el03gckFV9lJ4QOMiiHg==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.0.tgz", + "integrity": "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.25.0", - "@typescript-eslint/types": "8.25.0", - "@typescript-eslint/typescript-estree": "8.25.0", - "@typescript-eslint/visitor-keys": "8.25.0", - "debug": "^4.3.4" + "@typescript-eslint/scope-manager": "8.58.0", + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.0.tgz", + "integrity": "sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.58.0", + "@typescript-eslint/types": "^8.58.0", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1804,19 +1827,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.25.0.tgz", - "integrity": "sha512-6PPeiKIGbgStEyt4NNXa2ru5pMzQ8OYKO1hX1z53HMomrmiSB+R5FmChgQAP1ro8jMtNawz+TRQo/cSXrauTpg==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.0.tgz", + "integrity": "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.25.0", - "@typescript-eslint/visitor-keys": "8.25.0" + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1826,17 +1848,35 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.0.tgz", + "integrity": "sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.25.0.tgz", - "integrity": "sha512-d77dHgHWnxmXOPJuDWO4FDWADmGQkN5+tt6SFRZz/RtCWl4pHgFl3+WdYCn16+3teG09DY6XtEpf3gGD0a186g==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.0.tgz", + "integrity": "sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.25.0", - "@typescript-eslint/utils": "8.25.0", - "debug": "^4.3.4", - "ts-api-utils": "^2.0.1" + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0", + "@typescript-eslint/utils": "8.58.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1846,14 +1886,14 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.25.0.tgz", - "integrity": "sha512-+vUe0Zb4tkNgznQwicsvLUJgZIRs6ITeWSCclX1q85pR1iOiaj+4uZJIUp//Z27QWu5Cseiw3O3AR8hVpax7Aw==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.0.tgz", + "integrity": "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==", "dev": true, "license": "MIT", "engines": { @@ -1865,20 +1905,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.25.0.tgz", - "integrity": "sha512-ZPaiAKEZ6Blt/TPAx5Ot0EIB/yGtLI2EsGoY6F7XKklfMxYQyvtL+gT/UCqkMzO0BVFHLDlzvFqQzurYahxv9Q==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.0.tgz", + "integrity": "sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.25.0", - "@typescript-eslint/visitor-keys": "8.25.0", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.0.1" + "@typescript-eslint/project-service": "8.58.0", + "@typescript-eslint/tsconfig-utils": "8.58.0", + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1888,20 +1929,20 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/utils": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.25.0.tgz", - "integrity": "sha512-syqRbrEv0J1wywiLsK60XzHnQe/kRViI3zwFALrNEgnntn1l24Ra2KvOAWwWbWZ1lBZxZljPDGOq967dsl6fkA==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.0.tgz", + "integrity": "sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.25.0", - "@typescript-eslint/types": "8.25.0", - "@typescript-eslint/typescript-estree": "8.25.0" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.58.0", + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1911,19 +1952,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.25.0.tgz", - "integrity": "sha512-kCYXKAum9CecGVHGij7muybDfTS2sD3t0L4bJsEZLkyrXUImiCTq1M3LG2SRtOhiHFwMR9wAFplpT6XHYjTkwQ==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.0.tgz", + "integrity": "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.25.0", - "eslint-visitor-keys": "^4.2.0" + "@typescript-eslint/types": "8.58.0", + "eslint-visitor-keys": "^5.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1933,6 +1974,19 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@vscode/test-electron": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.4.1.tgz", @@ -2167,9 +2221,9 @@ } }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", "bin": { @@ -2200,9 +2254,9 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", "dependencies": { @@ -2496,16 +2550,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -3226,9 +3270,9 @@ } }, "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { @@ -3653,33 +3697,30 @@ } }, "node_modules/eslint": { - "version": "9.21.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.21.0.tgz", - "integrity": "sha512-KjeihdFqTPhOMXTt7StsDxriV4n66ueuF/jfPNC3j/lduHwr/ijDwJMsF+wyMJethgiKi5wniIE243vi07d3pg==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.2.0.tgz", + "integrity": "sha512-+L0vBFYGIpSNIt/KWTpFonPrqYvgKw1eUI5Vn7mEogrQcWtWYtNQ7dNqC+px/J0idT3BAkiWrhfS7k+Tum8TUA==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.19.2", - "@eslint/core": "^0.12.0", - "@eslint/eslintrc": "^3.3.0", - "@eslint/js": "9.21.0", - "@eslint/plugin-kit": "^0.2.7", + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.4", + "@eslint/config-helpers": "^0.5.4", + "@eslint/core": "^1.2.0", + "@eslint/plugin-kit": "^0.7.0", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", - "ajv": "^6.12.4", - "chalk": "^4.0.0", + "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.2.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", - "esquery": "^1.5.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", @@ -3689,8 +3730,7 @@ "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", + "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -3698,7 +3738,7 @@ "eslint": "bin/eslint.js" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://eslint.org/donate" @@ -3713,13 +3753,16 @@ } }, "node_modules/eslint-config-prettier": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.0.2.tgz", - "integrity": "sha512-1105/17ZIMjmCOJOPNfVdbXafLCLj3hPmkmB7dLgt7XsQ/zkxSuDerE/xgO3RxoHysR1N1whmquY0lSn2O0VLg==", + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", "bin": { - "eslint-config-prettier": "build/bin/cli.js" + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" }, "peerDependencies": { "eslint": ">=7.0.0" @@ -3729,6 +3772,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/eslint-define-config/-/eslint-define-config-2.1.0.tgz", "integrity": "sha512-QUp6pM9pjKEVannNAbSJNeRuYwW3LshejfyBBpjeMGaJjaDUpVps4C6KVR8R7dWZnD3i0synmrE36znjTkJvdQ==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", "dev": true, "funding": [ { @@ -3748,17 +3792,19 @@ } }, "node_modules/eslint-scope": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", - "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" @@ -3777,17 +3823,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", - "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/eslint/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -3801,17 +3836,35 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, - "license": "ISC", + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "brace-expansion": "^1.1.7" + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" }, "engines": { - "node": "*" + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/espree": { @@ -3847,9 +3900,9 @@ } }, "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -4001,6 +4054,24 @@ "pend": "~1.2.0" } }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -4246,58 +4317,6 @@ "node": ">=10.13.0" } }, - "node_modules/glob/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/globalthis": { "version": "1.0.4", "resolved": "https://registry.npmmirror.com/globalthis/-/globalthis-1.0.4.tgz", @@ -4366,13 +4385,6 @@ "dev": true, "license": "ISC" }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -4549,23 +4561,6 @@ "dev": true, "license": "MIT" }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -5103,13 +5098,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", @@ -5300,21 +5288,44 @@ } }, "node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.2" + "brace-expansion": "^5.0.5" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/minimatch/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/minimatch/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -5698,19 +5709,6 @@ "dev": true, "license": "(MIT AND Zlib)" }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/parse-json": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", @@ -5925,9 +5923,9 @@ } }, "node_modules/prettier": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.2.tgz", - "integrity": "sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, "license": "MIT", "bin": { @@ -6145,16 +6143,6 @@ "node": ">=0.10.0" } }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/restore-cursor": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", @@ -6296,9 +6284,9 @@ } }, "node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -6707,19 +6695,6 @@ "node": ">=8" } }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/structured-source": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/structured-source/-/structured-source-4.0.0.tgz", @@ -6973,6 +6948,23 @@ "url": "https://bevry.me/fund" } }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, "node_modules/tmp": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.4.tgz", @@ -6997,9 +6989,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz", - "integrity": "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "license": "MIT", "engines": { @@ -7079,9 +7071,9 @@ } }, "node_modules/typescript": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", - "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", + "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -7093,15 +7085,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.25.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.25.0.tgz", - "integrity": "sha512-TxRdQQLH4g7JkoFlYG3caW5v1S6kEkz8rqt80iQJZUYPq1zD1Ra7HfQBJJ88ABRaMvHAXnwRvRB4V+6sQ9xN5Q==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.0.tgz", + "integrity": "sha512-e2TQzKfaI85fO+F3QywtX+tCTsu/D3WW5LVU6nz8hTFKFZ8yBJ6mSYRpXqdR3mFjPWmO0eWsTa5f+UpAOe/FMA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.25.0", - "@typescript-eslint/parser": "8.25.0", - "@typescript-eslint/utils": "8.25.0" + "@typescript-eslint/eslint-plugin": "8.58.0", + "@typescript-eslint/parser": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0", + "@typescript-eslint/utils": "8.58.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7111,8 +7104,8 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/uc.micro": { diff --git a/src/tools/rust-analyzer/editors/code/package.json b/src/tools/rust-analyzer/editors/code/package.json index a117033f8025d..997ca0ae728fe 100644 --- a/src/tools/rust-analyzer/editors/code/package.json +++ b/src/tools/rust-analyzer/editors/code/package.json @@ -57,8 +57,8 @@ "vscode-languageclient": "^9.0.1" }, "devDependencies": { - "@eslint/js": "^9.21.0", - "@stylistic/eslint-plugin": "^4.1.0", + "@eslint/js": "^10.0.1", + "@stylistic/eslint-plugin": "^5.10.0", "@stylistic/eslint-plugin-js": "^4.1.0", "@tsconfig/strictest": "^2.0.5", "@types/lodash": "^4.17.20", @@ -69,14 +69,14 @@ "@vscode/test-electron": "^2.4.1", "@vscode/vsce": "^3.7.1", "esbuild": "^0.25.0", - "eslint": "^9.21.0", - "eslint-config-prettier": "^10.0.2", + "eslint": "^10.2.0", + "eslint-config-prettier": "^10.1.8", "eslint-define-config": "^2.1.0", "ovsx": "0.10.10", - "prettier": "^3.5.2", + "prettier": "^3.8.1", "tslib": "^2.8.1", - "typescript": "^5.7.3", - "typescript-eslint": "^8.25.0" + "typescript": "^6.0.2", + "typescript-eslint": "^8.58.0" }, "activationEvents": [ "workspaceContains:Cargo.toml", diff --git a/src/tools/rust-analyzer/editors/code/src/client.ts b/src/tools/rust-analyzer/editors/code/src/client.ts index 5b358e3211fb9..e265cff391d07 100644 --- a/src/tools/rust-analyzer/editors/code/src/client.ts +++ b/src/tools/rust-analyzer/editors/code/src/client.ts @@ -1,4 +1,4 @@ -import * as anser from "anser"; +import anser from "anser"; import * as lc from "vscode-languageclient/node"; import * as vscode from "vscode"; import * as ra from "../src/lsp_ext"; diff --git a/src/tools/rust-analyzer/editors/code/src/commands.ts b/src/tools/rust-analyzer/editors/code/src/commands.ts index c1b6f310301ec..302f51dee44d2 100644 --- a/src/tools/rust-analyzer/editors/code/src/commands.ts +++ b/src/tools/rust-analyzer/editors/code/src/commands.ts @@ -1194,9 +1194,8 @@ export function runSingle(ctx: CtxInit): Cmd { } export function copyRunCommandLine(ctx: CtxInit) { - let prevRunnable: RunnableQuickPick | undefined; return async () => { - const item = await selectRunnable(ctx, prevRunnable); + const item = await selectRunnable(ctx, undefined); if (!item || !isCargoRunnableArgs(item.runnable.args)) return; const args = createCargoArgs(item.runnable.args); const commandLine = ["cargo", ...args].join(" "); diff --git a/src/tools/rust-analyzer/editors/code/src/config.ts b/src/tools/rust-analyzer/editors/code/src/config.ts index 5dc2c419efa86..d65f011c754be 100644 --- a/src/tools/rust-analyzer/editors/code/src/config.ts +++ b/src/tools/rust-analyzer/editors/code/src/config.ts @@ -485,7 +485,7 @@ export function substituteVariablesInEnv(env: Env): Env { Object.entries(env).map(([key, value]) => { const deps = new Set(); if (value) { - let match = undefined; + let match; while ((match = depRe.exec(value))) { const depName = unwrapUndefinable(match.groups?.["depName"]); deps.add(depName); diff --git a/src/tools/rust-analyzer/editors/code/src/debug.ts b/src/tools/rust-analyzer/editors/code/src/debug.ts index 24f8d90873002..9bc3adad2f7ef 100644 --- a/src/tools/rust-analyzer/editors/code/src/debug.ts +++ b/src/tools/rust-analyzer/editors/code/src/debug.ts @@ -48,7 +48,7 @@ export async function makeDebugConfig(ctx: Ctx, runnable: ra.Runnable): Promise< } export async function startDebugSession(ctx: Ctx, runnable: ra.Runnable): Promise { - let debugConfig: vscode.DebugConfiguration | undefined = undefined; + let debugConfig: vscode.DebugConfiguration | undefined; let message = ""; const wsLaunchSection = vscode.workspace.getConfiguration("launch"); diff --git a/src/tools/rust-analyzer/editors/code/src/dependencies_provider.ts b/src/tools/rust-analyzer/editors/code/src/dependencies_provider.ts index 203ef5cc85e46..3c04f2ef64e84 100644 --- a/src/tools/rust-analyzer/editors/code/src/dependencies_provider.ts +++ b/src/tools/rust-analyzer/editors/code/src/dependencies_provider.ts @@ -6,9 +6,9 @@ import * as ra from "./lsp_ext"; import type { FetchDependencyListResult } from "./lsp_ext"; import { unwrapUndefinable } from "./util"; -export class RustDependenciesProvider - implements vscode.TreeDataProvider -{ +export class RustDependenciesProvider implements vscode.TreeDataProvider< + Dependency | DependencyFile +> { dependenciesMap: { [id: string]: Dependency | DependencyFile }; ctx: CtxInit; diff --git a/src/tools/rust-analyzer/editors/code/src/diagnostics.ts b/src/tools/rust-analyzer/editors/code/src/diagnostics.ts index cd0e43b212038..32a41745edd81 100644 --- a/src/tools/rust-analyzer/editors/code/src/diagnostics.ts +++ b/src/tools/rust-analyzer/editors/code/src/diagnostics.ts @@ -1,4 +1,4 @@ -import * as anser from "anser"; +import anser from "anser"; import * as vscode from "vscode"; import { type ProviderResult, diff --git a/src/tools/rust-analyzer/editors/code/src/toolchain.ts b/src/tools/rust-analyzer/editors/code/src/toolchain.ts index 06f75a8f8d651..76946d1510162 100644 --- a/src/tools/rust-analyzer/editors/code/src/toolchain.ts +++ b/src/tools/rust-analyzer/editors/code/src/toolchain.ts @@ -100,7 +100,7 @@ export class Cargo { ); } catch (err) { log.error(`Cargo invocation has failed: ${err}`); - throw new Error(`Cargo invocation has failed: ${err}`); + throw new Error(`Cargo invocation has failed: ${err}`, { cause: err }); } return spec.filter?.(artifacts) ?? artifacts; diff --git a/src/tools/rust-analyzer/editors/code/tsconfig.json b/src/tools/rust-analyzer/editors/code/tsconfig.json index a13afab170597..380acec59d854 100644 --- a/src/tools/rust-analyzer/editors/code/tsconfig.json +++ b/src/tools/rust-analyzer/editors/code/tsconfig.json @@ -1,7 +1,6 @@ { "extends": "@tsconfig/strictest/tsconfig.json", "compilerOptions": { - "esModuleInterop": false, "module": "NodeNext", "moduleResolution": "nodenext", "target": "ES2024", From 93478444d5dbcd07d2920244af98e32fc427ce98 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 6 Apr 2026 11:41:40 +0530 Subject: [PATCH 092/144] start using typed syntaxEditor constructor --- .../src/handlers/apply_demorgan.rs | 3 +-- .../src/handlers/convert_bool_then.rs | 6 ++--- .../convert_named_struct_to_tuple_struct.rs | 3 +-- .../src/handlers/convert_to_guarded_return.rs | 3 +-- .../convert_tuple_struct_to_named_struct.rs | 6 ++--- .../src/handlers/generate_delegate_trait.rs | 3 +-- .../src/handlers/generate_mut_trait_impl.rs | 4 +-- .../src/handlers/generate_trait_from_impl.rs | 3 +-- .../ide-assists/src/handlers/remove_dbg.rs | 3 +-- .../src/handlers/replace_if_let_with_match.rs | 3 +-- .../replace_qualified_name_with_use.rs | 3 +-- .../crates/ide-assists/src/utils.rs | 7 ++---- .../crates/ide-db/src/path_transform.rs | 3 +-- .../crates/syntax/src/syntax_editor.rs | 25 ++++++------------- .../crates/syntax/src/syntax_editor/edits.rs | 4 +-- 15 files changed, 25 insertions(+), 54 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs index 9220d127efe2c..5eba0d0ef9ced 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs @@ -82,8 +82,7 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti let make = SyntaxFactory::with_mappings(); - let (mut editor, demorganed) = SyntaxEditor::new(bin_expr.syntax().clone()); - let demorganed = ast::BinExpr::cast(demorganed).unwrap(); + let (mut editor, demorganed) = SyntaxEditor::new_typed(&bin_expr); editor.replace(demorganed.op_token()?, make.token(inv_token)); let mut exprs = VecDeque::from([ diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs index f13d1c1f8624c..4e23f4789b4ef 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs @@ -77,8 +77,7 @@ pub(crate) fn convert_if_to_bool_then(acc: &mut Assists, ctx: &AssistContext<'_> "Convert `if` expression to `bool::then` call", target, |builder| { - let (mut editor, closure_body) = SyntaxEditor::new(closure_body.syntax().clone()); - let closure_body = ast::Expr::cast(closure_body).unwrap(); + let (mut editor, closure_body) = SyntaxEditor::new_typed(&closure_body); // Rewrite all `Some(e)` in tail position to `e` for_each_tail_expr(&closure_body, &mut |e| { let e = match e { @@ -188,8 +187,7 @@ pub(crate) fn convert_bool_then_to_if(acc: &mut Assists, ctx: &AssistContext<'_> e => mapless_make.block_expr(None, Some(e)), }; - let (mut editor, closure_body) = SyntaxEditor::new(closure_body.syntax().clone()); - let closure_body = ast::BlockExpr::cast(closure_body).unwrap(); + let (mut editor, closure_body) = SyntaxEditor::new_typed(&closure_body); // Wrap all tails in `Some(...)` let none_path = mapless_make.expr_path(mapless_make.ident_path("None")); let some_path = mapless_make.expr_path(mapless_make.ident_path("Some")); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs index 3c4f297bddf84..35f38bb203b4f 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs @@ -103,8 +103,7 @@ fn edit_struct_def( // currently not triggered for struct definitions inside macro calls. let tuple_fields = record_fields.fields().filter_map(|f| { let (mut editor, field) = - SyntaxEditor::new(ast::make::tuple_field(f.visibility(), f.ty()?).syntax().clone()); - let field = ast::TupleField::cast(field).unwrap(); + SyntaxEditor::new_typed(&ast::make::tuple_field(f.visibility(), f.ty()?)); editor.insert_all( Position::first_child_of(field.syntax()), f.attrs().map(|attr| attr.syntax().clone().into()).collect(), diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs index f5ec60ac8aa1e..f2269675c9cc4 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs @@ -261,8 +261,7 @@ impl<'db> ElseBlock<'db> { return block_expr.reset_indent(); } - let (mut edit, block_expr) = SyntaxEditor::new(block_expr.reset_indent().syntax().clone()); - let block_expr = ast::BlockExpr::cast(block_expr).unwrap(); + let (mut edit, block_expr) = SyntaxEditor::new_typed(&block_expr.reset_indent()); let last_stmt = block_expr.statements().last().map(|it| it.syntax().clone()); let tail_expr = block_expr.tail_expr().map(|it| it.syntax().clone()); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs index 51bdca449c0af..20af3886d5b59 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs @@ -103,10 +103,8 @@ fn edit_struct_def( names: Vec, ) { let record_fields = tuple_fields.fields().zip(names).filter_map(|(f, name)| { - let (mut field_editor, field) = SyntaxEditor::new( - ast::make::record_field(f.visibility(), name, f.ty()?).syntax().clone(), - ); - let field = ast::RecordField::cast(field).unwrap(); + let (mut field_editor, field) = + SyntaxEditor::new_typed(&ast::make::record_field(f.visibility(), name, f.ty()?)); field_editor.insert_all( Position::first_child_of(field.syntax()), f.attrs().map(|attr| attr.syntax().clone().into()).collect(), diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs index 92232ba4da038..26b14608dfb7a 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs @@ -563,8 +563,7 @@ fn finalize_delegate( return Some(delegate.clone()); } - let (mut editor, delegate) = SyntaxEditor::new(delegate.syntax().clone()); - let delegate = ast::Impl::cast(delegate).unwrap(); + let (mut editor, delegate) = SyntaxEditor::new_typed(delegate); // 1. Replace assoc_item_list if we have new items if let Some(items) = assoc_items diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_mut_trait_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_mut_trait_impl.rs index f45b68f79c8f3..bd25f6252aad9 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_mut_trait_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_mut_trait_impl.rs @@ -67,9 +67,7 @@ pub(crate) fn generate_mut_trait_impl(acc: &mut Assists, ctx: &AssistContext<'_> format!("Generate `{trait_new}` impl from this `{trait_name}` trait"), target, |edit| { - let (mut editor, impl_clone) = - SyntaxEditor::new(impl_def.reset_indent().syntax().clone()); - let impl_clone = ast::Impl::cast(impl_clone).unwrap(); + let (mut editor, impl_clone) = SyntaxEditor::new_typed(&impl_def.reset_indent()); let factory = SyntaxFactory::without_mappings(); apply_generate_mut_impl(&mut editor, &factory, &impl_clone, trait_new); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs index b7fdcce2f3ce9..c25bd1ab39847 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs @@ -99,8 +99,7 @@ pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_ |builder| { let trait_items: ast::AssocItemList = { let (mut trait_items_editor, trait_items) = - SyntaxEditor::new(impl_assoc_items.syntax().clone()); - let trait_items = ast::AssocItemList::cast(trait_items).unwrap(); + SyntaxEditor::new_typed(&impl_assoc_items); trait_items.assoc_items().for_each(|item| { strip_body(&mut trait_items_editor, &item); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs index d56d85d12d0d7..007dd91d1b885 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs @@ -209,8 +209,7 @@ fn replace_nested_dbgs(expanded: ast::Expr) -> ast::Expr { return replaced; } - let (mut editor, expanded) = SyntaxEditor::new(expanded.syntax().clone()); - let expanded = ast::Expr::cast(expanded).unwrap(); + let (mut editor, expanded) = SyntaxEditor::new_typed(&expanded); // We need to collect to avoid mutation during traversal. let macro_exprs: Vec<_> = expanded.syntax().descendants().filter_map(ast::MacroExpr::cast).collect(); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_if_let_with_match.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_if_let_with_match.rs index 2730f5cb7bef2..548872c06e317 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_if_let_with_match.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_if_let_with_match.rs @@ -402,8 +402,7 @@ fn let_and_guard(cond: &ast::Expr) -> (Option, Option) } else if let ast::Expr::BinExpr(bin_expr) = cond && let Some(ast::Expr::LetExpr(let_expr)) = and_bin_expr_left(bin_expr).lhs() { - let (mut edit, new_expr) = SyntaxEditor::new(bin_expr.syntax().clone()); - let new_expr = ast::BinExpr::cast(new_expr).unwrap(); + let (mut edit, new_expr) = SyntaxEditor::new_typed(bin_expr); let left_bin = and_bin_expr_left(&new_expr); if let Some(rhs) = left_bin.rhs() { diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs index 693a081e91089..7f0375f3cbc7a 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs @@ -111,8 +111,7 @@ fn target_path(ctx: &AssistContext<'_>, mut original_path: ast::Path) -> Option< } fn drop_generic_args(path: &ast::Path) -> ast::Path { - let (mut editor, path) = SyntaxEditor::new(path.syntax().clone()); - let path = ast::Path::cast(path).unwrap(); + let (mut editor, path) = SyntaxEditor::new_typed(path); if let Some(segment) = path.segment() && let Some(generic_args) = segment.generic_arg_list() { diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs index 145a6af7e8e03..08c5fdbe1c2c4 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs @@ -248,8 +248,7 @@ pub fn add_trait_assoc_items_to_impl( }) .filter_map(|item| match item { ast::AssocItem::Fn(fn_) if fn_.body().is_none() => { - let (mut fn_editor, fn_) = SyntaxEditor::new(fn_.syntax().clone()); - let fn_ = ast::Fn::cast(fn_).unwrap(); + let (mut fn_editor, fn_) = SyntaxEditor::new_typed(&fn_); let fill_expr: ast::Expr = match config.expr_fill_default { ExprFillDefaultMode::Todo | ExprFillDefaultMode::Default => make.expr_todo(), ExprFillDefaultMode::Underscore => make.expr_underscore().into(), @@ -260,9 +259,7 @@ pub fn add_trait_assoc_items_to_impl( ast::AssocItem::cast(new_fn_) } ast::AssocItem::TypeAlias(type_alias) => { - let (mut type_alias_editor, type_alias) = - SyntaxEditor::new(type_alias.syntax().clone()); - let type_alias = ast::TypeAlias::cast(type_alias).unwrap(); + let (mut type_alias_editor, type_alias) = SyntaxEditor::new_typed(&type_alias); if let Some(type_bound_list) = type_alias.type_bound_list() { type_bound_list.remove(&mut type_alias_editor); }; diff --git a/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs b/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs index 3fd15057723c6..2a0a28a9ac6b5 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs @@ -451,8 +451,7 @@ impl Ctx<'_> { }; let found_path = self.target_module.find_path(self.source_scope.db, def, cfg)?; let res = mod_path_to_ast(&found_path, self.target_edition); - let (mut res_editor, res) = SyntaxEditor::new(res.syntax().clone()); - let res = ast::Path::cast(res).unwrap(); + let (mut res_editor, res) = SyntaxEditor::new_typed(&res); if let Some(args) = path.segment().and_then(|it| it.generic_arg_list()) && let Some(segment) = res.segment() { diff --git a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs index a21a5dd3aad8d..64a21af6d730e 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs @@ -456,8 +456,7 @@ mod tests { .into(), ); - let (mut editor, root) = SyntaxEditor::new(root.syntax().clone()); - let root = ast::MatchArm::cast(root).unwrap(); + let (mut editor, root) = SyntaxEditor::new_typed(&root); let to_wrap = root.syntax().descendants().find_map(ast::TupleExpr::cast).unwrap(); let to_replace = root.syntax().descendants().find_map(ast::BinExpr::cast).unwrap(); @@ -516,9 +515,7 @@ mod tests { None, ); - let (mut editor, root) = SyntaxEditor::new(root.syntax().clone()); - let root = ast::BlockExpr::cast(root).unwrap(); - + let (mut editor, root) = SyntaxEditor::new_typed(&root); let second_let = root.syntax().descendants().find_map(ast::LetStmt::cast).unwrap(); let make = SyntaxFactory::without_mappings(); @@ -569,8 +566,7 @@ mod tests { ), ); - let (mut editor, root) = SyntaxEditor::new(root.syntax().clone()); - let root = ast::BlockExpr::cast(root).unwrap(); + let (mut editor, root) = SyntaxEditor::new_typed(&root); let inner_block = root.syntax().descendants().flat_map(ast::BlockExpr::cast).nth(1).unwrap(); @@ -625,8 +621,7 @@ mod tests { None, ); - let (mut editor, root) = SyntaxEditor::new(root.syntax().clone()); - let root = ast::BlockExpr::cast(root).unwrap(); + let (mut editor, root) = SyntaxEditor::new_typed(&root); let inner_block = root; let make = SyntaxFactory::with_mappings(); @@ -674,8 +669,7 @@ mod tests { false, ); - let (mut editor, parent_fn) = SyntaxEditor::new(parent_fn.syntax().clone()); - let parent_fn = ast::Fn::cast(parent_fn).unwrap(); + let (mut editor, parent_fn) = SyntaxEditor::new_typed(&parent_fn); if let Some(ret_ty) = parent_fn.ret_type() { editor.delete(ret_ty.syntax().clone()); @@ -702,8 +696,7 @@ mod tests { let arg_list = make::arg_list([make::expr_literal("1").into(), make::expr_literal("2").into()]); - let (mut editor, arg_list) = SyntaxEditor::new(arg_list.syntax().clone()); - let arg_list = ast::ArgList::cast(arg_list).unwrap(); + let (mut editor, arg_list) = SyntaxEditor::new_typed(&arg_list); let target_expr = make::token(parser::SyntaxKind::UNDERSCORE); @@ -722,8 +715,7 @@ mod tests { let arg_list = make::arg_list([make::expr_literal("1").into(), make::expr_literal("2").into()]); - let (mut editor, arg_list) = SyntaxEditor::new(arg_list.syntax().clone()); - let arg_list = ast::ArgList::cast(arg_list).unwrap(); + let (mut editor, arg_list) = SyntaxEditor::new_typed(&arg_list); let target_expr = make::expr_literal("3").clone_for_update(); @@ -742,8 +734,7 @@ mod tests { let arg_list = make::arg_list([make::expr_literal("1").into(), make::expr_literal("2").into()]); - let (mut editor, arg_list) = SyntaxEditor::new(arg_list.syntax().clone()); - let arg_list = ast::ArgList::cast(arg_list).unwrap(); + let (mut editor, arg_list) = SyntaxEditor::new_typed(&arg_list); let target_expr = make::ext::expr_unit().clone_for_update(); diff --git a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edits.rs b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edits.rs index 253df826d7d61..ce435128a792f 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edits.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edits.rs @@ -473,9 +473,7 @@ enum Foo { } fn check_add_variant(before: &str, expected: &str, variant: ast::Variant) { - let (mut editor, enum_) = - SyntaxEditor::new(ast_from_text::(before).syntax().clone()); - let enum_ = ast::Enum::cast(enum_).unwrap(); + let (mut editor, enum_) = SyntaxEditor::new_typed(&ast_from_text::(before)); if let Some(it) = enum_.variant_list() { it.add_variant(&mut editor, &variant) } From efee8815a97a04c4ba6cf8e392dc813e2b7bb01f Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 6 Apr 2026 11:43:05 +0530 Subject: [PATCH 093/144] rename from new_typed to with_ast_node --- .../ide-assists/src/handlers/apply_demorgan.rs | 2 +- .../src/handlers/convert_bool_then.rs | 4 ++-- .../convert_named_struct_to_tuple_struct.rs | 2 +- .../src/handlers/convert_to_guarded_return.rs | 2 +- .../convert_tuple_struct_to_named_struct.rs | 2 +- .../src/handlers/generate_delegate_trait.rs | 2 +- .../src/handlers/generate_mut_trait_impl.rs | 2 +- .../src/handlers/generate_trait_from_impl.rs | 2 +- .../ide-assists/src/handlers/remove_dbg.rs | 2 +- .../src/handlers/replace_if_let_with_match.rs | 2 +- .../replace_qualified_name_with_use.rs | 2 +- .../crates/ide-assists/src/utils.rs | 4 ++-- .../crates/ide-db/src/path_transform.rs | 2 +- .../crates/syntax/src/syntax_editor.rs | 18 +++++++++--------- .../crates/syntax/src/syntax_editor/edits.rs | 2 +- 15 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs index 5eba0d0ef9ced..2ea0d76b01617 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/apply_demorgan.rs @@ -82,7 +82,7 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti let make = SyntaxFactory::with_mappings(); - let (mut editor, demorganed) = SyntaxEditor::new_typed(&bin_expr); + let (mut editor, demorganed) = SyntaxEditor::with_ast_node(&bin_expr); editor.replace(demorganed.op_token()?, make.token(inv_token)); let mut exprs = VecDeque::from([ diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs index 4e23f4789b4ef..c36c79ee998b4 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_bool_then.rs @@ -77,7 +77,7 @@ pub(crate) fn convert_if_to_bool_then(acc: &mut Assists, ctx: &AssistContext<'_> "Convert `if` expression to `bool::then` call", target, |builder| { - let (mut editor, closure_body) = SyntaxEditor::new_typed(&closure_body); + let (mut editor, closure_body) = SyntaxEditor::with_ast_node(&closure_body); // Rewrite all `Some(e)` in tail position to `e` for_each_tail_expr(&closure_body, &mut |e| { let e = match e { @@ -187,7 +187,7 @@ pub(crate) fn convert_bool_then_to_if(acc: &mut Assists, ctx: &AssistContext<'_> e => mapless_make.block_expr(None, Some(e)), }; - let (mut editor, closure_body) = SyntaxEditor::new_typed(&closure_body); + let (mut editor, closure_body) = SyntaxEditor::with_ast_node(&closure_body); // Wrap all tails in `Some(...)` let none_path = mapless_make.expr_path(mapless_make.ident_path("None")); let some_path = mapless_make.expr_path(mapless_make.ident_path("Some")); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs index 35f38bb203b4f..4ea56e3e613fb 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_named_struct_to_tuple_struct.rs @@ -103,7 +103,7 @@ fn edit_struct_def( // currently not triggered for struct definitions inside macro calls. let tuple_fields = record_fields.fields().filter_map(|f| { let (mut editor, field) = - SyntaxEditor::new_typed(&ast::make::tuple_field(f.visibility(), f.ty()?)); + SyntaxEditor::with_ast_node(&ast::make::tuple_field(f.visibility(), f.ty()?)); editor.insert_all( Position::first_child_of(field.syntax()), f.attrs().map(|attr| attr.syntax().clone().into()).collect(), diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs index f2269675c9cc4..71317440dfd37 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_to_guarded_return.rs @@ -261,7 +261,7 @@ impl<'db> ElseBlock<'db> { return block_expr.reset_indent(); } - let (mut edit, block_expr) = SyntaxEditor::new_typed(&block_expr.reset_indent()); + let (mut edit, block_expr) = SyntaxEditor::with_ast_node(&block_expr.reset_indent()); let last_stmt = block_expr.statements().last().map(|it| it.syntax().clone()); let tail_expr = block_expr.tail_expr().map(|it| it.syntax().clone()); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs index 20af3886d5b59..4ce7a9d866a9f 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs @@ -104,7 +104,7 @@ fn edit_struct_def( ) { let record_fields = tuple_fields.fields().zip(names).filter_map(|(f, name)| { let (mut field_editor, field) = - SyntaxEditor::new_typed(&ast::make::record_field(f.visibility(), name, f.ty()?)); + SyntaxEditor::with_ast_node(&ast::make::record_field(f.visibility(), name, f.ty()?)); field_editor.insert_all( Position::first_child_of(field.syntax()), f.attrs().map(|attr| attr.syntax().clone().into()).collect(), diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs index 26b14608dfb7a..a9730994a5424 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs @@ -563,7 +563,7 @@ fn finalize_delegate( return Some(delegate.clone()); } - let (mut editor, delegate) = SyntaxEditor::new_typed(delegate); + let (mut editor, delegate) = SyntaxEditor::with_ast_node(delegate); // 1. Replace assoc_item_list if we have new items if let Some(items) = assoc_items diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_mut_trait_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_mut_trait_impl.rs index bd25f6252aad9..31e49c8ce48e7 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_mut_trait_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_mut_trait_impl.rs @@ -67,7 +67,7 @@ pub(crate) fn generate_mut_trait_impl(acc: &mut Assists, ctx: &AssistContext<'_> format!("Generate `{trait_new}` impl from this `{trait_name}` trait"), target, |edit| { - let (mut editor, impl_clone) = SyntaxEditor::new_typed(&impl_def.reset_indent()); + let (mut editor, impl_clone) = SyntaxEditor::with_ast_node(&impl_def.reset_indent()); let factory = SyntaxFactory::without_mappings(); apply_generate_mut_impl(&mut editor, &factory, &impl_clone, trait_new); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs index c25bd1ab39847..225556090097b 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs @@ -99,7 +99,7 @@ pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_ |builder| { let trait_items: ast::AssocItemList = { let (mut trait_items_editor, trait_items) = - SyntaxEditor::new_typed(&impl_assoc_items); + SyntaxEditor::with_ast_node(&impl_assoc_items); trait_items.assoc_items().for_each(|item| { strip_body(&mut trait_items_editor, &item); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs index 007dd91d1b885..180c12f2ecabb 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs @@ -209,7 +209,7 @@ fn replace_nested_dbgs(expanded: ast::Expr) -> ast::Expr { return replaced; } - let (mut editor, expanded) = SyntaxEditor::new_typed(&expanded); + let (mut editor, expanded) = SyntaxEditor::with_ast_node(&expanded); // We need to collect to avoid mutation during traversal. let macro_exprs: Vec<_> = expanded.syntax().descendants().filter_map(ast::MacroExpr::cast).collect(); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_if_let_with_match.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_if_let_with_match.rs index 548872c06e317..ada2fd9b217ad 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_if_let_with_match.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_if_let_with_match.rs @@ -402,7 +402,7 @@ fn let_and_guard(cond: &ast::Expr) -> (Option, Option) } else if let ast::Expr::BinExpr(bin_expr) = cond && let Some(ast::Expr::LetExpr(let_expr)) = and_bin_expr_left(bin_expr).lhs() { - let (mut edit, new_expr) = SyntaxEditor::new_typed(bin_expr); + let (mut edit, new_expr) = SyntaxEditor::with_ast_node(bin_expr); let left_bin = and_bin_expr_left(&new_expr); if let Some(rhs) = left_bin.rhs() { diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs index 7f0375f3cbc7a..fd090cc081fa5 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs @@ -111,7 +111,7 @@ fn target_path(ctx: &AssistContext<'_>, mut original_path: ast::Path) -> Option< } fn drop_generic_args(path: &ast::Path) -> ast::Path { - let (mut editor, path) = SyntaxEditor::new_typed(path); + let (mut editor, path) = SyntaxEditor::with_ast_node(path); if let Some(segment) = path.segment() && let Some(generic_args) = segment.generic_arg_list() { diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs index 08c5fdbe1c2c4..01bd46406e1fc 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs @@ -248,7 +248,7 @@ pub fn add_trait_assoc_items_to_impl( }) .filter_map(|item| match item { ast::AssocItem::Fn(fn_) if fn_.body().is_none() => { - let (mut fn_editor, fn_) = SyntaxEditor::new_typed(&fn_); + let (mut fn_editor, fn_) = SyntaxEditor::with_ast_node(&fn_); let fill_expr: ast::Expr = match config.expr_fill_default { ExprFillDefaultMode::Todo | ExprFillDefaultMode::Default => make.expr_todo(), ExprFillDefaultMode::Underscore => make.expr_underscore().into(), @@ -259,7 +259,7 @@ pub fn add_trait_assoc_items_to_impl( ast::AssocItem::cast(new_fn_) } ast::AssocItem::TypeAlias(type_alias) => { - let (mut type_alias_editor, type_alias) = SyntaxEditor::new_typed(&type_alias); + let (mut type_alias_editor, type_alias) = SyntaxEditor::with_ast_node(&type_alias); if let Some(type_bound_list) = type_alias.type_bound_list() { type_bound_list.remove(&mut type_alias_editor); }; diff --git a/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs b/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs index 2a0a28a9ac6b5..ab960a18391c2 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs @@ -451,7 +451,7 @@ impl Ctx<'_> { }; let found_path = self.target_module.find_path(self.source_scope.db, def, cfg)?; let res = mod_path_to_ast(&found_path, self.target_edition); - let (mut res_editor, res) = SyntaxEditor::new_typed(&res); + let (mut res_editor, res) = SyntaxEditor::with_ast_node(&res); if let Some(args) = path.segment().and_then(|it| it.generic_arg_list()) && let Some(segment) = res.segment() { diff --git a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs index 64a21af6d730e..84f42ed52f011 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs @@ -53,7 +53,7 @@ impl SyntaxEditor { (editor, root) } - pub fn new_typed(root: &T) -> (Self, T) + pub fn with_ast_node(root: &T) -> (Self, T) where T: AstNode, { @@ -456,7 +456,7 @@ mod tests { .into(), ); - let (mut editor, root) = SyntaxEditor::new_typed(&root); + let (mut editor, root) = SyntaxEditor::with_ast_node(&root); let to_wrap = root.syntax().descendants().find_map(ast::TupleExpr::cast).unwrap(); let to_replace = root.syntax().descendants().find_map(ast::BinExpr::cast).unwrap(); @@ -515,7 +515,7 @@ mod tests { None, ); - let (mut editor, root) = SyntaxEditor::new_typed(&root); + let (mut editor, root) = SyntaxEditor::with_ast_node(&root); let second_let = root.syntax().descendants().find_map(ast::LetStmt::cast).unwrap(); let make = SyntaxFactory::without_mappings(); @@ -566,7 +566,7 @@ mod tests { ), ); - let (mut editor, root) = SyntaxEditor::new_typed(&root); + let (mut editor, root) = SyntaxEditor::with_ast_node(&root); let inner_block = root.syntax().descendants().flat_map(ast::BlockExpr::cast).nth(1).unwrap(); @@ -621,7 +621,7 @@ mod tests { None, ); - let (mut editor, root) = SyntaxEditor::new_typed(&root); + let (mut editor, root) = SyntaxEditor::with_ast_node(&root); let inner_block = root; let make = SyntaxFactory::with_mappings(); @@ -669,7 +669,7 @@ mod tests { false, ); - let (mut editor, parent_fn) = SyntaxEditor::new_typed(&parent_fn); + let (mut editor, parent_fn) = SyntaxEditor::with_ast_node(&parent_fn); if let Some(ret_ty) = parent_fn.ret_type() { editor.delete(ret_ty.syntax().clone()); @@ -696,7 +696,7 @@ mod tests { let arg_list = make::arg_list([make::expr_literal("1").into(), make::expr_literal("2").into()]); - let (mut editor, arg_list) = SyntaxEditor::new_typed(&arg_list); + let (mut editor, arg_list) = SyntaxEditor::with_ast_node(&arg_list); let target_expr = make::token(parser::SyntaxKind::UNDERSCORE); @@ -715,7 +715,7 @@ mod tests { let arg_list = make::arg_list([make::expr_literal("1").into(), make::expr_literal("2").into()]); - let (mut editor, arg_list) = SyntaxEditor::new_typed(&arg_list); + let (mut editor, arg_list) = SyntaxEditor::with_ast_node(&arg_list); let target_expr = make::expr_literal("3").clone_for_update(); @@ -734,7 +734,7 @@ mod tests { let arg_list = make::arg_list([make::expr_literal("1").into(), make::expr_literal("2").into()]); - let (mut editor, arg_list) = SyntaxEditor::new_typed(&arg_list); + let (mut editor, arg_list) = SyntaxEditor::with_ast_node(&arg_list); let target_expr = make::ext::expr_unit().clone_for_update(); diff --git a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edits.rs b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edits.rs index ce435128a792f..8c842be49dc98 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edits.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edits.rs @@ -473,7 +473,7 @@ enum Foo { } fn check_add_variant(before: &str, expected: &str, variant: ast::Variant) { - let (mut editor, enum_) = SyntaxEditor::new_typed(&ast_from_text::(before)); + let (mut editor, enum_) = SyntaxEditor::with_ast_node(&ast_from_text::(before)); if let Some(it) = enum_.variant_list() { it.add_variant(&mut editor, &variant) } From 1b7332d870c27ce98ea8517c9c285521ded76736 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 6 Apr 2026 12:48:02 +0530 Subject: [PATCH 094/144] call new method directly from with_ast_node and improve comments --- .../crates/syntax/src/syntax_editor.rs | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs index 84f42ed52f011..dbb9f15e173e2 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs @@ -34,8 +34,10 @@ pub struct SyntaxEditor { impl SyntaxEditor { /// Creates a syntax editor from `root`. /// - /// Makes sure the root is detached and not mutable by cloning it if needed, - /// so all changes happen only within the editor. + /// The returned `root` is guaranteed to be a detached, immutable node. + /// If the provided node is not a root (i.e., has a parent) or is already + /// mutable, it is cloned into a fresh subtree to satisfy syntax editor + /// invariants. pub fn new(root: SyntaxNode) -> (Self, SyntaxNode) { let mut root = root; @@ -53,22 +55,12 @@ impl SyntaxEditor { (editor, root) } + /// Typed-node variant of [`SyntaxEditor::new`]. pub fn with_ast_node(root: &T) -> (Self, T) where T: AstNode, { - let mut root = root.syntax().clone(); - - if root.parent().is_some() || root.is_mutable() { - root = root.clone_subtree() - }; - - let editor = Self { - root: root.clone(), - changes: Vec::new(), - mappings: SyntaxMapping::default(), - annotations: Vec::new(), - }; + let (editor, root) = Self::new(root.syntax().clone()); (editor, T::cast(root).unwrap()) } From 2e54741df992163eb0def3a83b76cc6714fb8552 Mon Sep 17 00:00:00 2001 From: BenjaminBrienen Date: Mon, 6 Apr 2026 11:57:32 +0200 Subject: [PATCH 095/144] vfs::ChangeKind is Clone, Copy, Hash --- src/tools/rust-analyzer/crates/vfs/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/vfs/src/lib.rs b/src/tools/rust-analyzer/crates/vfs/src/lib.rs index 50e388d780022..d48b984407e65 100644 --- a/src/tools/rust-analyzer/crates/vfs/src/lib.rs +++ b/src/tools/rust-analyzer/crates/vfs/src/lib.rs @@ -157,7 +157,7 @@ pub enum Change { } /// Kind of [file change](ChangedFile). -#[derive(Eq, PartialEq, Debug)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum ChangeKind { /// The file was (re-)created Create, From bd51cd1edcd9f363d06ecfbd8ac7d86289edf060 Mon Sep 17 00:00:00 2001 From: Asuka Minato Date: Mon, 6 Apr 2026 19:04:44 +0900 Subject: [PATCH 096/144] add md into vfs --- src/tools/rust-analyzer/crates/load-cargo/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs b/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs index 08b6f9ca2b05d..68bf78e037c0c 100644 --- a/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs +++ b/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs @@ -304,6 +304,7 @@ impl ProjectFolders { let mut dirs = vfs::loader::Directories::default(); dirs.extensions.push("rs".into()); dirs.extensions.push("toml".into()); + dirs.extensions.push("md".into()); dirs.include.extend(root.include); dirs.exclude.extend(root.exclude); for excl in global_excludes { From 4c79065b3d2fc1b7686a4d658249d7643323da18 Mon Sep 17 00:00:00 2001 From: PRO <54608551+PRO-2684@users.noreply.github.com> Date: Mon, 6 Apr 2026 18:46:27 +0800 Subject: [PATCH 097/144] fix: Reload when documentation changes See https://github.com/rust-lang/rust-analyzer/pull/21968#discussion_r3038969774 --- src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs index 71accbed4ef16..aee054edff381 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs @@ -582,7 +582,8 @@ impl GlobalState { [ (base.clone(), "**/*.rs"), (base.clone(), "**/Cargo.{lock,toml}"), - (base, "**/rust-analyzer.toml"), + (base.clone(), "**/rust-analyzer.toml"), + (base, "**/*.md"), ] }) }) From 7918ea09ce6d0bf733a84057a61f2b13ee3e6097 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Mon, 10 Nov 2025 10:52:52 +0800 Subject: [PATCH 098/144] Add optional thin-arrow ret-type for fn-item --- .../crates/parser/src/grammar.rs | 12 +++++ .../crates/parser/src/grammar/items.rs | 7 ++- .../parser/test_data/generated/runner.rs | 4 ++ .../err/function_ret_type_missing_arrow.rast | 50 +++++++++++++++++++ .../err/function_ret_type_missing_arrow.rs | 2 + 5 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/function_ret_type_missing_arrow.rast create mode 100644 src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/function_ret_type_missing_arrow.rs diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar.rs b/src/tools/rust-analyzer/crates/parser/src/grammar.rs index e481bbe9bc4a0..1ff8a56b580f0 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar.rs @@ -303,6 +303,18 @@ fn opt_ret_type(p: &mut Parser<'_>) -> bool { } } +fn opt_no_arrow_ret_type(p: &mut Parser<'_>) -> bool { + if p.at_ts(PATH_NAME_REF_KINDS) { + let m = p.start(); + p.error("missing thin-arrow `->`"); + types::type_no_bounds(p); + m.complete(p, RET_TYPE); + true + } else { + false + } +} + fn name_r(p: &mut Parser<'_>, recovery: TokenSet) { if p.at(IDENT) { let m = p.start(); diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/items.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/items.rs index c609f9383ee0f..c0acdde2a7240 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/items.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/items.rs @@ -422,7 +422,12 @@ fn fn_(p: &mut Parser<'_>, m: Marker) { // test function_ret_type // fn foo() {} // fn bar() -> () {} - opt_ret_type(p); + if !opt_ret_type(p) { + // test_err function_ret_type_missing_arrow + // fn foo() usize {} + // fn bar() super::Foo {} + opt_no_arrow_ret_type(p); + } // test_err fn_ret_recovery // fn foo() -> A>]) { let x = 1; } diff --git a/src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs b/src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs index 4c001104fe525..01fc172ed9537 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs +++ b/src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs @@ -793,6 +793,10 @@ mod err { run_and_expect_errors("test_data/parser/inline/err/fn_ret_recovery.rs"); } #[test] + fn function_ret_type_missing_arrow() { + run_and_expect_errors("test_data/parser/inline/err/function_ret_type_missing_arrow.rs"); + } + #[test] fn gen_fn() { run_and_expect_errors_with_edition( "test_data/parser/inline/err/gen_fn.rs", diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/function_ret_type_missing_arrow.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/function_ret_type_missing_arrow.rast new file mode 100644 index 0000000000000..c0bca6ed1c31b --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/function_ret_type_missing_arrow.rast @@ -0,0 +1,50 @@ +SOURCE_FILE + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "foo" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + RET_TYPE + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "usize" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + R_CURLY "}" + WHITESPACE "\n" + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "bar" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + RET_TYPE + PATH_TYPE + PATH + PATH + PATH_SEGMENT + NAME_REF + SUPER_KW "super" + COLON2 "::" + PATH_SEGMENT + NAME_REF + IDENT "Foo" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + R_CURLY "}" + WHITESPACE "\n" +error 9: missing thin-arrow `->` +error 27: missing thin-arrow `->` diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/function_ret_type_missing_arrow.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/function_ret_type_missing_arrow.rs new file mode 100644 index 0000000000000..f48e539df50de --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/function_ret_type_missing_arrow.rs @@ -0,0 +1,2 @@ +fn foo() usize {} +fn bar() super::Foo {} From c2818e88b2c62ec8f794f8d0b42905635aa860f8 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Mon, 6 Apr 2026 16:15:58 +0800 Subject: [PATCH 099/144] Add `adds_text` to completion item builder --- .../crates/ide-completion/src/item.rs | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/item.rs b/src/tools/rust-analyzer/crates/ide-completion/src/item.rs index 1a9139d8553be..d3fecede3511a 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/item.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/item.rs @@ -450,6 +450,7 @@ impl CompletionItem { ref_match: None, imports_to_add: Default::default(), doc_aliases: vec![], + adds_text: None, edition, } } @@ -486,6 +487,7 @@ pub(crate) struct Builder { imports_to_add: SmallVec<[LocatedImport; 1]>, trait_name: Option, doc_aliases: Vec, + adds_text: Option, label: SmolStr, insert_text: Option, is_snippet: bool, @@ -526,9 +528,16 @@ impl Builder { let insert_text = self.insert_text.unwrap_or_else(|| label.to_string()); let mut detail_left = None; + let mut to_detail_left = |args: fmt::Arguments<'_>| { + let detail_left = detail_left.get_or_insert_with(String::new); + if !detail_left.is_empty() { + detail_left.push(' '); + } + format_to!(detail_left, "{args}") + }; if !self.doc_aliases.is_empty() { let doc_aliases = self.doc_aliases.iter().join(", "); - detail_left = Some(format!("(alias {doc_aliases})")); + to_detail_left(format_args!("(alias {doc_aliases})")); let lookup_doc_aliases = self .doc_aliases .iter() @@ -548,22 +557,17 @@ impl Builder { lookup = format_smolstr!("{lookup}{lookup_doc_aliases}"); } } + if let Some(adds_text) = self.adds_text { + to_detail_left(format_args!("(adds {})", adds_text.trim())); + } if let [import_edit] = &*self.imports_to_add { // snippets can have multiple imports, but normal completions only have up to one - let detail_left = detail_left.get_or_insert_with(String::new); - format_to!( - detail_left, - "{}(use {})", - if detail_left.is_empty() { "" } else { " " }, + to_detail_left(format_args!( + "(use {})", import_edit.import_path.display(db, self.edition) - ); + )); } else if let Some(trait_name) = self.trait_name { - let detail_left = detail_left.get_or_insert_with(String::new); - format_to!( - detail_left, - "{}(as {trait_name})", - if detail_left.is_empty() { "" } else { " " }, - ); + to_detail_left(format_args!("(as {trait_name})")); } let text_edit = match self.text_edit { @@ -613,6 +617,10 @@ impl Builder { self.doc_aliases = doc_aliases; self } + pub(crate) fn adds_text(&mut self, adds_text: SmolStr) -> &mut Builder { + self.adds_text = Some(adds_text); + self + } pub(crate) fn insert_text(&mut self, insert_text: impl Into) -> &mut Builder { self.insert_text = Some(insert_text.into()); self From bfc90308c6fbefd15a2dbd53f5b5388d1c1d69f4 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Mon, 10 Nov 2025 16:05:58 +0800 Subject: [PATCH 100/144] Implement thin-arrow completion in fn return position Very cool feature that can quickly complete simple return types Example --- ```rust fn foo() u$0 ``` **Before this PR** ```text kw where ``` **After this PR** ```text bt u32 (adds ->) u32 kw where ... ``` ```rust fn foo() -> u32 ``` --- .../crates/ide-completion/src/completions.rs | 43 +++- .../ide-completion/src/completions/type.rs | 8 +- .../crates/ide-completion/src/context.rs | 24 +- .../ide-completion/src/context/analysis.rs | 9 +- .../crates/ide-completion/src/item.rs | 2 +- .../crates/ide-completion/src/render.rs | 54 ++++- .../crates/ide-completion/src/tests/item.rs | 32 ++- .../ide-completion/src/tests/type_pos.rs | 226 +++++++++++++++++- 8 files changed, 372 insertions(+), 26 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions.rs index 1fb1fd4e579dd..4a94383ff4cbd 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions.rs @@ -34,7 +34,7 @@ use crate::{ CompletionContext, CompletionItem, CompletionItemKind, context::{ DotAccess, ItemListKind, NameContext, NameKind, NameRefContext, NameRefKind, - PathCompletionCtx, PathKind, PatternContext, TypeLocation, Visible, + PathCompletionCtx, PathKind, PatternContext, TypeAscriptionTarget, TypeLocation, Visible, }, item::Builder, render::{ @@ -45,7 +45,7 @@ use crate::{ macro_::render_macro, pattern::{render_struct_pat, render_variant_pat}, render_expr, render_field, render_path_resolution, render_pattern_resolution, - render_tuple_field, + render_tuple_field, render_type_keyword_snippet, type_alias::{render_type_alias, render_type_alias_with_eq}, union_literal::render_union_literal, }, @@ -104,6 +104,21 @@ impl Completions { } } + pub(crate) fn add_nameref_keywords_with_type_like( + &mut self, + ctx: &CompletionContext<'_>, + path_ctx: &PathCompletionCtx<'_>, + ) { + let mut add_keyword = |kw| { + render_type_keyword_snippet(ctx, path_ctx, kw, kw).add_to(self, ctx.db); + }; + ["self::", "crate::"].into_iter().for_each(&mut add_keyword); + + if ctx.depth_from_crate_root > 0 { + add_keyword("super::"); + } + } + pub(crate) fn add_nameref_keywords(&mut self, ctx: &CompletionContext<'_>) { ["self", "crate"].into_iter().for_each(|kw| self.add_keyword(ctx, kw)); @@ -112,11 +127,19 @@ impl Completions { } } - pub(crate) fn add_type_keywords(&mut self, ctx: &CompletionContext<'_>) { - self.add_keyword_snippet(ctx, "fn", "fn($1)"); - self.add_keyword_snippet(ctx, "dyn", "dyn $0"); - self.add_keyword_snippet(ctx, "impl", "impl $0"); - self.add_keyword_snippet(ctx, "for", "for<$1>"); + pub(crate) fn add_type_keywords( + &mut self, + ctx: &CompletionContext<'_>, + path_ctx: &PathCompletionCtx<'_>, + ) { + let mut add_keyword = |kw, snippet| { + render_type_keyword_snippet(ctx, path_ctx, kw, snippet).add_to(self, ctx.db); + }; + + add_keyword("fn", "fn($1)"); + add_keyword("dyn", "dyn $0"); + add_keyword("impl", "impl $0"); + add_keyword("for", "for<$1>"); } pub(crate) fn add_super_keyword( @@ -747,6 +770,12 @@ pub(super) fn complete_name_ref( field::complete_field_list_tuple_variant(acc, ctx, path_ctx); } TypeLocation::TypeAscription(ascription) => { + if let TypeAscriptionTarget::RetType { item: Some(item), .. } = + ascription + && path_ctx.required_thin_arrow().is_some() + { + keyword::complete_for_and_where(acc, ctx, &item.clone().into()); + } r#type::complete_ascribed_type(acc, ctx, path_ctx, ascription); } TypeLocation::GenericArg { .. } diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/type.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/type.rs index 8ff9c3258e8e3..e2125a9678234 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/type.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/type.rs @@ -206,8 +206,8 @@ pub(crate) fn complete_type_path( _ => {} }; - acc.add_nameref_keywords_with_colon(ctx); - acc.add_type_keywords(ctx); + acc.add_nameref_keywords_with_type_like(ctx, path_ctx); + acc.add_type_keywords(ctx, path_ctx); ctx.process_all_names(&mut |name, def, doc_aliases| { if scope_def_applicable(def) { acc.add_path_resolution(ctx, path_ctx, name, def, doc_aliases); @@ -230,14 +230,14 @@ pub(crate) fn complete_ascribed_type( TypeAscriptionTarget::Let(pat) | TypeAscriptionTarget::FnParam(pat) => { ctx.sema.type_of_pat(pat.as_ref()?) } - TypeAscriptionTarget::Const(exp) | TypeAscriptionTarget::RetType(exp) => { + TypeAscriptionTarget::Const(exp) | TypeAscriptionTarget::RetType { body: exp, .. } => { ctx.sema.type_of_expr(exp.as_ref()?) } }? .adjusted(); if !ty.is_unknown() { let ty_string = ty.display_source_code(ctx.db, ctx.module.into(), true).ok()?; - acc.add(render_type_inference(ty_string, ctx)); + acc.add(render_type_inference(ty_string, ctx, path_ctx)); } None } 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 a91f123176e0c..ae3f71760744a 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/context.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/context.rs @@ -102,6 +102,28 @@ impl PathCompletionCtx<'_> { } ) } + + pub(crate) fn required_thin_arrow(&self) -> Option<(&'static str, TextSize)> { + let PathKind::Type { + location: + TypeLocation::TypeAscription(TypeAscriptionTarget::RetType { + item: Some(ref fn_item), + .. + }), + } = self.kind + else { + return None; + }; + if fn_item.ret_type().is_some_and(|it| it.thin_arrow_token().is_some()) { + return None; + } + let ret_type = fn_item.ret_type().and_then(|it| it.ty()); + match (ret_type, fn_item.param_list()) { + (Some(ty), _) => Some(("-> ", ty.syntax().text_range().start())), + (None, Some(param)) => Some((" ->", param.syntax().text_range().end())), + (None, None) => None, + } + } } /// The kind of path we are completing right now. @@ -231,7 +253,7 @@ impl TypeLocation { pub(crate) enum TypeAscriptionTarget { Let(Option), FnParam(Option), - RetType(Option), + RetType { body: Option, item: Option }, Const(Option), } diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs index 294e70dd56a29..d8f160c1005e6 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs @@ -1278,15 +1278,14 @@ fn classify_name_ref<'db>( let original = ast::Static::cast(name.syntax().parent()?)?; TypeLocation::TypeAscription(TypeAscriptionTarget::Const(original.body())) }, - ast::RetType(it) => { - it.thin_arrow_token()?; + ast::RetType(_) => { let parent = match ast::Fn::cast(parent.parent()?) { Some(it) => it.param_list(), None => ast::ClosureExpr::cast(parent.parent()?)?.param_list(), }; let parent = find_opt_node_in_file(original_file, parent)?.syntax().parent()?; - TypeLocation::TypeAscription(TypeAscriptionTarget::RetType(match_ast! { + let body = match_ast! { match parent { ast::ClosureExpr(it) => { it.body() @@ -1296,7 +1295,9 @@ fn classify_name_ref<'db>( }, _ => return None, } - })) + }; + let item = ast::Fn::cast(parent); + TypeLocation::TypeAscription(TypeAscriptionTarget::RetType { body, item }) }, ast::Param(it) => { it.colon_token()?; diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/item.rs b/src/tools/rust-analyzer/crates/ide-completion/src/item.rs index d3fecede3511a..e6dd1d37d9335 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/item.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/item.rs @@ -481,7 +481,7 @@ impl CompletionItem { /// A helper to make `CompletionItem`s. #[must_use] -#[derive(Clone)] +#[derive(Debug, Clone)] pub(crate) struct Builder { source_range: TextRange, imports_to_add: SmallVec<[LocatedImport; 1]>, diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render.rs index 4751ee36eceb0..f37b73a28ab60 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/render.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/render.rs @@ -220,13 +220,15 @@ pub(crate) fn render_tuple_field( pub(crate) fn render_type_inference( ty_string: String, ctx: &CompletionContext<'_>, + path_ctx: &PathCompletionCtx<'_>, ) -> CompletionItem { let mut builder = CompletionItem::new( CompletionItemKind::InferredType, ctx.source_range(), - ty_string, + &ty_string, ctx.edition, ); + adds_ret_type_arrow(ctx, path_ctx, &mut builder, ty_string); builder.set_relevance(CompletionRelevance { type_match: Some(CompletionRelevanceTypeMatch::Exact), exact_name_match: true, @@ -425,11 +427,10 @@ fn render_resolution_path( let config = completion.config; let requires_import = import_to_add.is_some(); - let name = local_name.display_no_db(ctx.completion.edition).to_smolstr(); + let name = local_name.display(db, completion.edition).to_smolstr(); let mut item = render_resolution_simple_(ctx, &local_name, import_to_add, resolution); - if local_name.needs_escape(completion.edition) { - item.insert_text(local_name.display_no_db(completion.edition).to_smolstr()); - } + let mut insert_text = name.clone(); + // Add `<>` for generic types let type_path_no_ty_args = matches!( path_ctx, @@ -446,12 +447,14 @@ fn render_resolution_path( if has_non_default_type_params { cov_mark::hit!(inserts_angle_brackets_for_generics); + insert_text = format_smolstr!("{insert_text}<$0>"); item.lookup_by(name.clone()) .label(SmolStr::from_iter([&name, "<…>"])) .trigger_call_info() - .insert_snippet(cap, format!("{}<$0>", local_name.display(db, completion.edition))); + .insert_snippet(cap, ""); // set is snippet } } + adds_ret_type_arrow(completion, path_ctx, &mut item, insert_text.into()); let mut set_item_relevance = |ty: Type<'_>| { if !ty.is_unknown() { @@ -577,6 +580,45 @@ fn scope_def_is_deprecated(ctx: &RenderContext<'_>, resolution: ScopeDef) -> boo } } +pub(crate) fn render_type_keyword_snippet( + ctx: &CompletionContext<'_>, + path_ctx: &PathCompletionCtx<'_>, + label: &str, + snippet: &str, +) -> Builder { + let source_range = ctx.source_range(); + let mut item = + CompletionItem::new(CompletionItemKind::Keyword, source_range, label, ctx.edition); + + let cap = ctx.config.snippet_cap; + if let Some(cap) = cap { + item.insert_snippet(cap, snippet); + } + + let insert_text = if cap.is_some() { snippet } else { label }.to_owned(); + adds_ret_type_arrow(ctx, path_ctx, &mut item, insert_text); + + item +} + +fn adds_ret_type_arrow( + ctx: &CompletionContext<'_>, + path_ctx: &PathCompletionCtx<'_>, + item: &mut Builder, + insert_text: String, +) { + if let Some((arrow, at)) = path_ctx.required_thin_arrow() { + let mut edit = TextEdit::builder(); + + edit.insert(at, arrow.to_owned()); + edit.replace(ctx.source_range(), insert_text); + + item.text_edit(edit.finish()).adds_text(SmolStr::new_static(arrow)); + } else { + item.insert_text(insert_text); + } +} + // FIXME: This checks types without possible coercions which some completions might want to do fn match_types( ctx: &CompletionContext<'_>, diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/item.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/item.rs index 61a9da8c278a5..2f032c3f4ca56 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/item.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/item.rs @@ -116,8 +116,23 @@ fn completes_where() { check_with_base_items( r"fn func() $0", expect![[r#" - kw where - "#]], + en Enum (adds ->) Enum + ma makro!(…) macro_rules! makro + md module (adds ->) + st Record (adds ->) Record + st Tuple (adds ->) Tuple + st Unit (adds ->) Unit + tt Trait (adds ->) + un Union (adds ->) Union + bt u32 (adds ->) u32 + kw crate:: (adds ->) + kw dyn (adds ->) + kw fn (adds ->) + kw for (adds ->) + kw impl (adds ->) + kw self:: (adds ->) + kw where + "#]], ); check_with_base_items( r"enum Enum $0", @@ -243,6 +258,19 @@ impl Copy for S where $0 ); } +#[test] +fn fn_item_where_kw() { + check_edit( + "where", + r#" +fn foo() $0 +"#, + r#" +fn foo() where $0 +"#, + ); +} + #[test] fn test_is_not_considered_macro() { check_with_base_items( diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/type_pos.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/type_pos.rs index 7c6b7370aafdd..7d4a7fe6b8872 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/type_pos.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/type_pos.rs @@ -1,7 +1,7 @@ //! Completion tests for type position. use expect_test::expect; -use crate::tests::{check, check_with_base_items}; +use crate::tests::{check, check_edit, check_with_base_items}; #[test] fn record_field_ty() { @@ -93,6 +93,230 @@ fn x<'lt, T, const C: usize>() -> $0 ); } +#[test] +fn fn_return_type_missing_thin_arrow() { + check_with_base_items( + r#" +fn x() u$0 +"#, + expect![[r#" + en Enum (adds ->) Enum + ma makro!(…) macro_rules! makro + md module (adds ->) + st Record (adds ->) Record + st Tuple (adds ->) Tuple + st Unit (adds ->) Unit + tt Trait (adds ->) + un Union (adds ->) Union + bt u32 (adds ->) u32 + kw crate:: (adds ->) + kw dyn (adds ->) + kw fn (adds ->) + kw for (adds ->) + kw impl (adds ->) + kw self:: (adds ->) + kw where + "#]], + ); + + check_with_base_items( + r#" +fn x() $0 +"#, + expect![[r#" + en Enum (adds ->) Enum + ma makro!(…) macro_rules! makro + md module (adds ->) + st Record (adds ->) Record + st Tuple (adds ->) Tuple + st Unit (adds ->) Unit + tt Trait (adds ->) + un Union (adds ->) Union + bt u32 (adds ->) u32 + kw crate:: (adds ->) + kw dyn (adds ->) + kw fn (adds ->) + kw for (adds ->) + kw impl (adds ->) + kw self:: (adds ->) + kw where + "#]], + ); +} + +#[test] +fn fn_return_type_missing_thin_arrow_path_completion() { + check_edit( + "u32", + r#" +fn foo() u$0 +"#, + r#" +fn foo() -> u32 +"#, + ); + + check_edit( + "u32", + r#" +fn foo() $0 +"#, + r#" +fn foo() -> u32 +"#, + ); + + check_edit( + "Num", + r#" +type Num = u32; +fn foo() $0 +"#, + r#" +type Num = u32; +fn foo() -> Num +"#, + ); + + check_edit( + "impl", + r#" +fn foo() $0 +"#, + r#" +fn foo() -> impl $0 +"#, + ); + + check_edit( + "foo", + r#" +mod foo { pub type Num = u32; } +fn foo() $0 +"#, + r#" +mod foo { pub type Num = u32; } +fn foo() -> foo +"#, + ); + + check_edit( + "crate::", + r#" +mod foo { pub type Num = u32; } +fn foo() $0 +"#, + r#" +mod foo { pub type Num = u32; } +fn foo() -> crate:: +"#, + ); + + check_edit( + "Num", + r#" +mod foo { pub type Num = u32; } +fn foo() foo::$0 +"#, + r#" +mod foo { pub type Num = u32; } +fn foo() -> foo::Num +"#, + ); + + // no spaces, test edit order + check_edit( + "foo", + r#" +mod foo { pub type Num = u32; } +fn foo()$0 +"#, + r#" +mod foo { pub type Num = u32; } +fn foo() ->foo +"#, + ); +} + +#[test] +fn fn_return_type_missing_thin_arrow_path_completion_with_generic_args() { + check_edit( + "Foo", + r#" +struct Foo(T); +fn foo() F$0 +"#, + r#" +struct Foo(T); +fn foo() -> Foo<$0> +"#, + ); + + check_edit( + "Foo", + r#" +struct Foo(T); +fn foo() $0 +"#, + r#" +struct Foo(T); +fn foo() -> Foo<$0> +"#, + ); + + check_edit( + "Foo", + r#" +type Foo = T; +fn foo() $0 +"#, + r#" +type Foo = T; +fn foo() -> Foo<$0> +"#, + ); +} + +#[test] +fn fn_return_type_missing_thin_arrow_infer_ref_type() { + check_with_base_items( + r#" +fn x() u$0 {&2u32} +"#, + expect![[r#" + en Enum (adds ->) Enum + ma makro!(…) macro_rules! makro + md module (adds ->) + st Record (adds ->) Record + st Tuple (adds ->) Tuple + st Unit (adds ->) Unit + tt Trait (adds ->) + un Union (adds ->) Union + bt u32 (adds ->) u32 + it &u32 (adds ->) + kw crate:: (adds ->) + kw dyn (adds ->) + kw fn (adds ->) + kw for (adds ->) + kw impl (adds ->) + kw self:: (adds ->) + kw where + "#]], + ); + + check_edit( + "&u32", + r#" +struct Foo(T); +fn x() u$0 {&2u32} +"#, + r#" +struct Foo(T); +fn x() -> &u32 {&2u32} +"#, + ); +} + #[test] fn fn_return_type_after_reference() { check_with_base_items( From 551cf637c961bb935e4fd722676ee7ab12522d53 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Mon, 10 Nov 2025 21:09:36 +0800 Subject: [PATCH 101/144] Remove a ide-typing test --- src/tools/rust-analyzer/crates/ide/src/typing.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide/src/typing.rs b/src/tools/rust-analyzer/crates/ide/src/typing.rs index ec620982ff08f..a49a85fe7804a 100644 --- a/src/tools/rust-analyzer/crates/ide/src/typing.rs +++ b/src/tools/rust-analyzer/crates/ide/src/typing.rs @@ -1239,12 +1239,6 @@ sdasdasdasdasd #[test] fn parenthesis_noop_in_item_position_with_macro() { type_char_noop('(', r#"$0println!();"#); - type_char_noop( - '(', - r#" -fn main() $0println!("hello"); -}"#, - ); } #[test] From ad0d0669903bfa2df1ba448c47c50a9313e1ece5 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Mon, 6 Apr 2026 15:23:24 +0300 Subject: [PATCH 102/144] Check coercion, not unification, in "Fill struct fields", as the criteria to use an existing local as the field's value Since struct literals allow coercions. --- .../src/handlers/missing_fields.rs | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_fields.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_fields.rs index 050d5477f62ce..efbd266714398 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_fields.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_fields.rs @@ -120,7 +120,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option A { + let v = loop {}; + A {$0} +} + "#, + r#" +struct A { + v: f64, +} + +fn f() -> A { + let v = loop {}; + A { v } +} + "#, + ); + } } From 5e903eae2d5eee1a0c33e5cb2085f7e80b3794ef Mon Sep 17 00:00:00 2001 From: PRO-2684 <54608551+PRO-2684@users.noreply.github.com> Date: Mon, 6 Apr 2026 21:10:37 +0800 Subject: [PATCH 103/144] fix: Resolve https://github.com/rust-lang/rust-analyzer/pull/21970#pullrequestreview-4061885516 --- src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs index aee054edff381..b85fe9c15c133 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs @@ -608,6 +608,7 @@ impl GlobalState { format!("{base}/**/*.rs"), format!("{base}/**/Cargo.{{toml,lock}}"), format!("{base}/**/rust-analyzer.toml"), + format!("{base}/**/*.md"), ] }) }) From 87c3b447a67b53dc4b7c97a993e14fa834d16dfb Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 6 Apr 2026 22:09:32 +0530 Subject: [PATCH 104/144] remove redundant clone_subtree --- .../src/handlers/convert_range_for_to_while.rs | 2 +- .../src/handlers/generate_delegate_trait.rs | 4 ++-- .../crates/ide-assists/src/handlers/remove_dbg.rs | 2 +- .../handlers/replace_derive_with_manual_impl.rs | 6 ++---- .../ide-assists/src/handlers/unwrap_block.rs | 3 +-- .../rust-analyzer/crates/ide-assists/src/utils.rs | 6 ++---- .../crates/ide-db/src/path_transform.rs | 15 ++++++--------- 7 files changed, 15 insertions(+), 23 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_range_for_to_while.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_range_for_to_while.rs index 09435eeaecda0..61393950767f8 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_range_for_to_while.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_range_for_to_while.rs @@ -133,7 +133,7 @@ fn process_loop_body( ) -> Option<()> { let last = previous_non_trivia_token(body.r_curly_token()?)?.syntax_element(); - let new_body = body.indent(1.into()).clone_subtree(); + let new_body = body.indent(1.into()); let mut continues = vec![]; collect_continue_to( &mut continues, diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs index a9730994a5424..abe447d9d9b7d 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_delegate_trait.rs @@ -363,9 +363,9 @@ fn generate_impl( ast_strukt, &old_impl, &transform_args, - trait_args.clone_subtree(), + trait_args.clone(), ) { - *trait_args = new_args.clone_subtree(); + *trait_args = new_args.clone(); Some(new_args) } else { None diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs index 180c12f2ecabb..f4c354b8a21d8 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs @@ -163,7 +163,7 @@ fn compute_dbg_replacement( None => false, }; let expr = replace_nested_dbgs(expr.clone()); - let expr = if wrap { make::expr_paren(expr).into() } else { expr.clone_subtree() }; + let expr = if wrap { make::expr_paren(expr).into() } else { expr }; (vec![macro_call.syntax().clone().into()], Some(expr)) } // dbg!(expr0, expr1, ...) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs index 62b4e04950492..04c9d8e54de5c 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs @@ -220,10 +220,8 @@ fn impl_def_from_trait( &impl_def, &target_scope, ); - let assoc_item_list = if let Some((first, other)) = - assoc_items.split_first().map(|(first, other)| (first.clone_subtree(), other)) - { - let first_item = if let ast::AssocItem::Fn(ref func) = first + let assoc_item_list = if let Some((first, other)) = assoc_items.split_first() { + let first_item = if let ast::AssocItem::Fn(func) = first && let Some(body) = gen_trait_fn_body(&make, func, trait_path, adt, None) && let Some(func_body) = func.body() { diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs index 87e61b35d8c4f..c7e0394ce152b 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs @@ -103,7 +103,7 @@ fn delete_else_before(container: SyntaxNode, edit: &mut SyntaxEditor) { fn wrap_let(assign: &ast::LetStmt, replacement: ast::BlockExpr) -> ast::BlockExpr { let try_wrap_assign = || { let initializer = assign.initializer()?.syntax().syntax_element(); - let replacement = replacement.clone_subtree(); + let (mut edit, replacement) = SyntaxEditor::with_ast_node(&replacement); let assign = assign.clone_for_update(); let tail_expr = replacement.tail_expr()?; let before = @@ -115,7 +115,6 @@ fn wrap_let(assign: &ast::LetStmt, replacement: ast::BlockExpr) -> ast::BlockExp .skip(1) .collect(); - let (mut edit, _) = SyntaxEditor::new(replacement.syntax().clone()); edit.insert_all(Position::before(tail_expr.syntax()), before); edit.insert_all(Position::after(tail_expr.syntax()), after); ast::BlockExpr::cast(edit.finish().new_root().clone()) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs index 01bd46406e1fc..3de8ec7f536c3 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs @@ -330,10 +330,8 @@ fn invert_special_case(make: &SyntaxFactory, expr: &ast::Expr) -> Option match pe.expr()? { - ast::Expr::ParenExpr(parexpr) => { - parexpr.expr().map(|e| e.clone_subtree().clone_for_update()) - } - _ => pe.expr().map(|e| e.clone_subtree().clone_for_update()), + ast::Expr::ParenExpr(parexpr) => parexpr.expr(), + _ => pe.expr(), }, ast::Expr::Literal(lit) => match lit.kind() { ast::LiteralKind::Bool(b) => match b { diff --git a/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs b/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs index ab960a18391c2..407276a2defc9 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/path_transform.rs @@ -412,19 +412,16 @@ impl Ctx<'_> { if old.parent().is_some() { editor.replace(old, subst.clone().syntax()); } else { - // Some `path_ty` has no parent, especially ones made for default value - // of type parameters. - // In this case, `ted` cannot replace `path_ty` with `subst` directly. - // So, just replace its children as long as the `subst` is the same type. - let new = subst.clone_subtree().clone_for_update(); - if !matches!(new, ast::Type::PathType(..)) { - return None; - } let start = path_ty.syntax().first_child().map(NodeOrToken::Node)?; let end = path_ty.syntax().last_child().map(NodeOrToken::Node)?; editor.replace_all( start..=end, - new.syntax().children().map(NodeOrToken::Node).collect::>(), + subst + .clone() + .syntax() + .children() + .map(NodeOrToken::Node) + .collect::>(), ); } } else { From f6772e15d3f840ae01f56ac82825a181c4081c93 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Mon, 6 Apr 2026 20:15:04 +0300 Subject: [PATCH 105/144] Consider the context of the path for `ImportAssets` For example, only suggest macros if the path is followed by `!`. --- .../ide-assists/src/handlers/auto_import.rs | 29 +++ .../src/completions/flyimport.rs | 11 +- .../ide-db/src/imports/import_assets.rs | 170 +++++++++++++++++- 3 files changed, 204 insertions(+), 6 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/auto_import.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/auto_import.rs index de5dfdf4d9543..adeb191719fb7 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/auto_import.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/auto_import.rs @@ -1928,4 +1928,33 @@ fn f() { "#; check_auto_import_order(before, &["Import `foo::wanted`", "Import `quux::wanted`"]); } + + #[test] + fn consider_definition_kind() { + check_assist( + auto_import, + r#" +//- /eyre.rs crate:eyre +#[macro_export] +macro_rules! eyre { + () => {}; +} + +//- /color-eyre.rs crate:color-eyre deps:eyre +pub use eyre; + +//- /main.rs crate:main deps:color-eyre +fn main() { + ey$0re!(); +} + "#, + r#" +use color_eyre::eyre::eyre; + +fn main() { + eyre!(); +} + "#, + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/flyimport.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/flyimport.rs index 20d01485a45a2..413830904aa8c 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/flyimport.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/flyimport.rs @@ -135,7 +135,12 @@ pub(crate) fn import_on_the_fly_path( Qualified::With { path, .. } => Some(path.clone()), _ => None, }; - let import_assets = import_assets_for_path(ctx, &potential_import_name, qualifier.clone())?; + let import_assets = import_assets_for_path( + ctx, + Some(&path_ctx.path), + &potential_import_name, + qualifier.clone(), + )?; import_on_the_fly( acc, @@ -160,7 +165,7 @@ pub(crate) fn import_on_the_fly_pat( } let potential_import_name = import_name(ctx); - let import_assets = import_assets_for_path(ctx, &potential_import_name, None)?; + let import_assets = import_assets_for_path(ctx, None, &potential_import_name, None)?; import_on_the_fly_pat_( acc, @@ -402,6 +407,7 @@ fn import_name(ctx: &CompletionContext<'_>) -> String { fn import_assets_for_path<'db>( ctx: &CompletionContext<'db>, + path: Option<&ast::Path>, potential_import_name: &str, qualifier: Option, ) -> Option> { @@ -411,6 +417,7 @@ fn import_assets_for_path<'db>( let fuzzy_name_length = potential_import_name.len(); let mut assets_for_path = ImportAssets::for_fuzzy_path( ctx.module, + path, qualifier, potential_import_name.to_owned(), &ctx.sema, diff --git a/src/tools/rust-analyzer/crates/ide-db/src/imports/import_assets.rs b/src/tools/rust-analyzer/crates/ide-db/src/imports/import_assets.rs index 1c485270272e3..2f696d07e21be 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/imports/import_assets.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/imports/import_assets.rs @@ -8,8 +8,10 @@ use hir::{ SemanticsScope, Trait, Type, }; use itertools::Itertools; +use parser::SyntaxKind; use rustc_hash::{FxHashMap, FxHashSet}; use smallvec::{SmallVec, smallvec}; +use stdx::never; use syntax::{ AstNode, SyntaxNode, ast::{self, HasName, make}, @@ -61,6 +63,103 @@ pub struct TraitImportCandidate<'db> { pub assoc_item_name: NameToImport, } +#[derive(Debug)] +struct PathDefinitionKinds { + modules: bool, + bang_macros: bool, + // FIXME: Distinguish between attr and derive macros. + attr_macros: bool, + value_namespace: bool, + type_namespace: bool, + /// Unions, record structs and record enum variants. Note that unions and structs + /// can also be enabled by `type_namespace` (either works). + records: bool, + /// Tuple structs and tuple enum variants. Both are also controlled by `value_namespace` + /// (either works). Structs are also covered by `type_namespace`. + tuple_structs: bool, + /// Structs, enum variants and consts. + structs_and_consts: bool, +} + +impl PathDefinitionKinds { + const ALL_DISABLED: Self = Self { + modules: false, + bang_macros: false, + attr_macros: false, + value_namespace: false, + type_namespace: false, + records: false, + tuple_structs: false, + structs_and_consts: false, + }; + const ALL_ENABLED: Self = Self { + modules: true, + bang_macros: true, + attr_macros: true, + value_namespace: true, + type_namespace: true, + records: true, + tuple_structs: true, + structs_and_consts: true, + }; + // While a path pattern only allows unit structs/enum variants, parentheses/braces may be written later. + const PATH_PAT_KINDS: PathDefinitionKinds = + Self { structs_and_consts: true, bang_macros: true, ..Self::ALL_DISABLED }; + + fn deduce_from_path(path: &ast::Path, exact: bool) -> Self { + let Some(parent) = path.syntax().parent() else { + return Self::ALL_ENABLED; + }; + let mut result = match parent.kind() { + // When there are following segments, it can be a type (with a method) or a module. + // Technically, a type can only have up to 2 segments following (an associated type + // then a method), but most paths are shorter than 3 segments anyway, and we'll also + // validate that the following segment resolve. + SyntaxKind::PATH => Self { modules: true, type_namespace: true, ..Self::ALL_DISABLED }, + SyntaxKind::MACRO_CALL => Self { bang_macros: true, ..Self::ALL_DISABLED }, + SyntaxKind::META => Self { attr_macros: true, ..Self::ALL_DISABLED }, + SyntaxKind::USE_TREE => { + if ast::UseTree::cast(parent).unwrap().use_tree_list().is_some() { + Self { modules: true, ..Self::ALL_DISABLED } + } else { + Self::ALL_ENABLED + } + } + SyntaxKind::VISIBILITY => Self { modules: true, ..Self::ALL_DISABLED }, + SyntaxKind::ASM_SYM => Self { value_namespace: true, ..Self::ALL_DISABLED }, + // `bang_macros = true` because you can still type the `!`. + // `type_namespace = true` because you can type `::method()`. + SyntaxKind::PATH_EXPR => Self { + value_namespace: true, + bang_macros: true, + type_namespace: true, + ..Self::ALL_DISABLED + }, + SyntaxKind::PATH_PAT => Self::PATH_PAT_KINDS, + SyntaxKind::TUPLE_STRUCT_PAT => { + Self { tuple_structs: true, bang_macros: true, ..Self::ALL_DISABLED } + } + SyntaxKind::RECORD_EXPR | SyntaxKind::RECORD_PAT => { + Self { records: true, bang_macros: true, ..Self::ALL_DISABLED } + } + SyntaxKind::PATH_TYPE => { + Self { type_namespace: true, bang_macros: true, ..Self::ALL_DISABLED } + } + SyntaxKind::ERROR => Self::ALL_ENABLED, + _ => { + never!("this match should cover all possible parents of paths\nparent={parent:#?}"); + Self::ALL_ENABLED + } + }; + if !exact { + // When the path is not required to be exact, there could be additional segments to be filled. + result.modules = true; + result.type_namespace = true; + } + result + } +} + /// Path import for a given name, qualified or not. #[derive(Debug)] pub struct PathImportCandidate { @@ -70,6 +169,8 @@ pub struct PathImportCandidate { pub name: NameToImport, /// Potentially more segments that should resolve in the candidate. pub after: Vec, + /// The kind of definitions that we can include. + definition_kinds: PathDefinitionKinds, } /// A name that will be used during item lookups. @@ -168,13 +269,14 @@ impl<'db> ImportAssets<'db> { pub fn for_fuzzy_path( module_with_candidate: Module, + path: Option<&ast::Path>, qualifier: Option, fuzzy_name: String, sema: &Semantics<'db, RootDatabase>, candidate_node: SyntaxNode, ) -> Option { Some(Self { - import_candidate: ImportCandidate::for_fuzzy_path(qualifier, fuzzy_name, sema)?, + import_candidate: ImportCandidate::for_fuzzy_path(path, qualifier, fuzzy_name, sema)?, module_with_candidate, candidate_node, }) @@ -394,6 +496,9 @@ fn path_applicable_imports( // see also an ignored test under FIXME comment in the qualify_path.rs module AssocSearchMode::Exclude, ) + .filter(|(item, _)| { + filter_by_definition_kind(db, *item, &path_candidate.definition_kinds) + }) .filter_map(|(item, do_not_complete)| { if !scope_filter(item) { return None; @@ -442,6 +547,46 @@ fn path_applicable_imports( result } +fn filter_by_definition_kind( + db: &RootDatabase, + item: ItemInNs, + allowed: &PathDefinitionKinds, +) -> bool { + let item = item.into_module_def(); + let struct_per_kind = |struct_kind| { + allowed.structs_and_consts + || match struct_kind { + hir::StructKind::Record => allowed.records, + hir::StructKind::Tuple => allowed.value_namespace || allowed.tuple_structs, + hir::StructKind::Unit => allowed.value_namespace, + } + }; + match item { + ModuleDef::Module(_) => allowed.modules, + ModuleDef::Function(_) => allowed.value_namespace, + ModuleDef::Adt(hir::Adt::Struct(item)) => { + allowed.type_namespace || struct_per_kind(item.kind(db)) + } + ModuleDef::Adt(hir::Adt::Enum(_)) => allowed.type_namespace, + ModuleDef::Adt(hir::Adt::Union(_)) => { + allowed.type_namespace || allowed.records || allowed.structs_and_consts + } + ModuleDef::EnumVariant(item) => struct_per_kind(item.kind(db)), + ModuleDef::Const(_) => allowed.value_namespace || allowed.structs_and_consts, + ModuleDef::Static(_) => allowed.value_namespace, + ModuleDef::Trait(_) => allowed.type_namespace, + ModuleDef::TypeAlias(_) => allowed.type_namespace, + ModuleDef::BuiltinType(_) => allowed.type_namespace, + ModuleDef::Macro(item) => { + if item.is_fn_like(db) { + allowed.bang_macros + } else { + allowed.attr_macros + } + } + } +} + fn filter_candidates_by_after_path( db: &RootDatabase, scope: &SemanticsScope<'_>, @@ -835,6 +980,7 @@ impl<'db> ImportCandidate<'db> { .collect::>()?; path_import_candidate( sema, + Some(path), path.qualifier(), NameToImport::exact_case_sensitive(path.segment()?.name_ref()?.to_string()), after, @@ -853,25 +999,31 @@ impl<'db> ImportCandidate<'db> { qualifier: vec![], name: NameToImport::exact_case_sensitive(name.to_string()), after: vec![], + definition_kinds: PathDefinitionKinds::PATH_PAT_KINDS, })) } fn for_fuzzy_path( + path: Option<&ast::Path>, qualifier: Option, fuzzy_name: String, sema: &Semantics<'db, RootDatabase>, ) -> Option { // Assume a fuzzy match does not want the segments after. Because... I guess why not? - path_import_candidate(sema, qualifier, NameToImport::fuzzy(fuzzy_name), Vec::new()) + path_import_candidate(sema, path, qualifier, NameToImport::fuzzy(fuzzy_name), Vec::new()) } } fn path_import_candidate<'db>( sema: &Semantics<'db, RootDatabase>, + path: Option<&ast::Path>, qualifier: Option, name: NameToImport, after: Vec, ) -> Option> { + let definition_kinds = path.map_or(PathDefinitionKinds::ALL_ENABLED, |path| { + PathDefinitionKinds::deduce_from_path(path, matches!(name, NameToImport::Exact(..))) + }); Some(match qualifier { Some(qualifier) => match sema.resolve_path(&qualifier) { Some(PathResolution::Def(ModuleDef::BuiltinType(_))) | None => { @@ -880,7 +1032,12 @@ fn path_import_candidate<'db>( .segments() .map(|seg| seg.name_ref().map(|name| Name::new_root(&name.text()))) .collect::>>()?; - ImportCandidate::Path(PathImportCandidate { qualifier, name, after }) + ImportCandidate::Path(PathImportCandidate { + qualifier, + name, + after, + definition_kinds, + }) } else { return None; } @@ -904,7 +1061,12 @@ fn path_import_candidate<'db>( } Some(_) => return None, }, - None => ImportCandidate::Path(PathImportCandidate { qualifier: vec![], name, after }), + None => ImportCandidate::Path(PathImportCandidate { + qualifier: vec![], + name, + after, + definition_kinds, + }), }) } From dfc2fdf0eccbcd3859d8df9f20d5bc1c894903d6 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 6 Apr 2026 23:19:49 +0530 Subject: [PATCH 106/144] edit_algo only handled the node mutability scenario earlier, this commits supports it for token as well, making its parent mutable --- .../syntax/src/syntax_editor/edit_algo.rs | 36 ++++++++++++++++--- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edit_algo.rs b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edit_algo.rs index f6bd992f23e34..1baba5e299873 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edit_algo.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edit_algo.rs @@ -215,15 +215,41 @@ pub(super) fn apply_edits(editor: SyntaxEditor) -> SyntaxEdit { *node = node.clone_for_update(); } } + Change::Insert(_, SyntaxElement::Token(token)) + | Change::Replace(_, Some(SyntaxElement::Token(token))) => { + if token.parent().is_some() { + let idx = token.index(); + let new_parent = token.parent().unwrap().clone_subtree().clone_for_update(); + *token = new_parent + .children_with_tokens() + .nth(idx) + .and_then(SyntaxElement::into_token) + .unwrap(); + } + } Change::InsertAll(_, elements) | Change::ReplaceWithMany(_, elements) | Change::ReplaceAll(_, elements) => { for element in elements { - if let SyntaxElement::Node(node) = element { - if node.parent().is_some() { - *node = node.clone_subtree().clone_for_update(); - } else if !node.is_mutable() { - *node = node.clone_for_update(); + match element { + SyntaxElement::Node(node) => { + if node.parent().is_some() { + *node = node.clone_subtree().clone_for_update(); + } else if !node.is_mutable() { + *node = node.clone_for_update(); + } + } + SyntaxElement::Token(token) => { + if token.parent().is_some() { + let idx = token.index(); + let new_parent = + token.parent().unwrap().clone_subtree().clone_for_update(); + *token = new_parent + .children_with_tokens() + .nth(idx) + .and_then(SyntaxElement::into_token) + .unwrap(); + } } } } From 3457b5ef820e6be4a47f8fbd9d9103f15b7a05d5 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 6 Apr 2026 23:20:11 +0530 Subject: [PATCH 107/144] remove redundant clone_for_update in assign as well --- .../crates/ide-assists/src/handlers/unwrap_block.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs index c7e0394ce152b..5593ca3eb88fd 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs @@ -104,7 +104,6 @@ fn wrap_let(assign: &ast::LetStmt, replacement: ast::BlockExpr) -> ast::BlockExp let try_wrap_assign = || { let initializer = assign.initializer()?.syntax().syntax_element(); let (mut edit, replacement) = SyntaxEditor::with_ast_node(&replacement); - let assign = assign.clone_for_update(); let tail_expr = replacement.tail_expr()?; let before = assign.syntax().children_with_tokens().take_while(|it| *it != initializer).collect(); From 619a328e63a0667e959153745f4379b9475a5796 Mon Sep 17 00:00:00 2001 From: Krish Date: Mon, 6 Apr 2026 11:50:32 +0530 Subject: [PATCH 108/144] feat: cargo metadata takes extra args --- .../crates/project-model/src/cargo_workspace.rs | 5 +++++ .../crates/project-model/src/workspace.rs | 5 +++++ .../crates/rust-analyzer/src/config.rs | 4 ++++ .../docs/book/src/configuration_generated.md | 8 ++++++++ src/tools/rust-analyzer/editors/code/package.json | 13 +++++++++++++ 5 files changed, 35 insertions(+) diff --git a/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs b/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs index 792206b74f849..5d8273832bb23 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs @@ -131,6 +131,8 @@ pub struct CargoConfig { pub run_build_script_command: Option>, /// Extra args to pass to the cargo command. pub extra_args: Vec, + /// Extra args passed only to `cargo metadata`, not other cargo commands. + pub metadata_extra_args: Vec, /// Extra env vars to set when invoking the cargo command pub extra_env: FxHashMap>, pub invocation_strategy: InvocationStrategy, @@ -320,6 +322,8 @@ pub struct CargoMetadataConfig { pub targets: Vec, /// Extra args to pass to the cargo command. pub extra_args: Vec, + /// Extra args passed directly to `cargo metadata` without filtering. + pub metadata_extra_args: Vec, /// Extra env vars to set when invoking the cargo command pub extra_env: FxHashMap>, /// What kind of metadata are we fetching: workspace, rustc, or sysroot. @@ -679,6 +683,7 @@ impl FetchMetadata { other_options.push(arg.to_owned()); } } + other_options.extend(config.metadata_extra_args.iter().cloned()); let mut lockfile_copy = None; if cargo_toml.is_rust_manifest() { diff --git a/src/tools/rust-analyzer/crates/project-model/src/workspace.rs b/src/tools/rust-analyzer/crates/project-model/src/workspace.rs index 581b5fa514461..29a19bc32e453 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/workspace.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/workspace.rs @@ -216,6 +216,7 @@ impl ProjectWorkspace { features, rustc_source, extra_args, + metadata_extra_args, extra_env, set_test, cfg_overrides, @@ -289,6 +290,7 @@ impl ProjectWorkspace { features: features.clone(), targets: targets.clone(), extra_args: extra_args.clone(), + metadata_extra_args: metadata_extra_args.clone(), extra_env: extra_env.clone(), toolchain_version: toolchain.clone(), kind: "workspace", @@ -343,6 +345,7 @@ impl ProjectWorkspace { features: crate::CargoFeatures::default(), targets: targets.clone(), extra_args: extra_args.clone(), + metadata_extra_args: metadata_extra_args.clone(), extra_env: extra_env.clone(), toolchain_version: toolchain.clone(), kind: "rustc-dev" @@ -575,6 +578,7 @@ impl ProjectWorkspace { features: config.features.clone(), targets, extra_args: config.extra_args.clone(), + metadata_extra_args: config.metadata_extra_args.clone(), extra_env: config.extra_env.clone(), toolchain_version: toolchain.clone(), kind: "detached-file", @@ -1942,6 +1946,7 @@ fn sysroot_metadata_config( features: Default::default(), targets, extra_args: Default::default(), + metadata_extra_args: config.metadata_extra_args.clone(), extra_env: config.extra_env.clone(), toolchain_version, kind: "sysroot", diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs index 90857a3073109..74d498368cbba 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs @@ -831,6 +831,9 @@ config_data! { /// /// Set this to `"all"` to pass `--all-features` to cargo. cargo_features: CargoFeaturesDef = CargoFeaturesDef::Selected(vec![]), + /// Extra arguments passed only to `cargo metadata`, not to other cargo invocations. + /// Useful for flags like `--config` that `cargo metadata` supports. + cargo_metadataExtraArgs: Vec = vec![], /// Whether to pass `--no-default-features` to cargo. cargo_noDefaultFeatures: bool = false, /// Whether to skip fetching dependencies. If set to "true", the analysis is performed @@ -2444,6 +2447,7 @@ impl Config { target_dir_config: self.target_dir_from_config(source_root), set_test: *self.cfg_setTest(source_root), no_deps: *self.cargo_noDeps(source_root), + metadata_extra_args: self.cargo_metadataExtraArgs(source_root).clone(), } } diff --git a/src/tools/rust-analyzer/docs/book/src/configuration_generated.md b/src/tools/rust-analyzer/docs/book/src/configuration_generated.md index aff2e32b637f0..548c5fb093fa3 100644 --- a/src/tools/rust-analyzer/docs/book/src/configuration_generated.md +++ b/src/tools/rust-analyzer/docs/book/src/configuration_generated.md @@ -166,6 +166,14 @@ List of features to activate. Set this to `"all"` to pass `--all-features` to cargo. +## rust-analyzer.cargo.metadataExtraArgs {#cargo.metadataExtraArgs} + +Default: `[]` + +Extra arguments passed only to `cargo metadata`, not to other cargo invocations. +Useful for flags like `--config` that `cargo metadata` supports. + + ## rust-analyzer.cargo.noDefaultFeatures {#cargo.noDefaultFeatures} Default: `false` diff --git a/src/tools/rust-analyzer/editors/code/package.json b/src/tools/rust-analyzer/editors/code/package.json index a117033f8025d..82e94f5f9ebdb 100644 --- a/src/tools/rust-analyzer/editors/code/package.json +++ b/src/tools/rust-analyzer/editors/code/package.json @@ -993,6 +993,19 @@ } } }, + { + "title": "Cargo", + "properties": { + "rust-analyzer.cargo.metadataExtraArgs": { + "markdownDescription": "Extra arguments passed only to `cargo metadata`, not to other cargo invocations.\nUseful for flags like `--config` that `cargo metadata` supports.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + } + }, { "title": "Cargo", "properties": { From 8609572f432b9739ad36bfedcde18e0e84250917 Mon Sep 17 00:00:00 2001 From: Stanley Horwood Date: Tue, 7 Apr 2026 01:57:57 +0200 Subject: [PATCH 109/144] feat: add ${exact} and ${include_ignored} placeholders to overrideCommand --- .../crates/rust-analyzer/src/config.rs | 38 ++++++++++++++----- .../crates/rust-analyzer/src/target_spec.rs | 19 +++++++++- .../docs/book/src/configuration_generated.md | 38 ++++++++++++++----- .../rust-analyzer/editors/code/package.json | 6 +-- 4 files changed, 77 insertions(+), 24 deletions(-) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs index 74d498368cbba..3a88a8fe84807 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs @@ -960,18 +960,30 @@ config_data! { /// Override the command used for bench runnables. /// The first element of the array should be the program to execute (for example, `cargo`). /// - /// Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${executable_args}` to dynamically - /// replace the package name, target option (such as `--bin` or `--example`), the target name and - /// the arguments passed to test binary args (includes `rust-analyzer.runnables.extraTestBinaryArgs`). + /// Use the placeholders: + /// - `${package}`: package name. + /// - `${target_arg}`: target option such as `--bin`, `--test`, `--lib`, etc. + /// - `${target}`: target name (empty for `--lib`). + /// - `${test_name}`: the test path filter, e.g. `module::bench_func`. + /// - `${exact}`: `--exact` for single benchmarks, empty for modules. + /// - `${include_ignored}`: always empty for benchmarks. + /// - `${executable_args}`: all of the above binary args bundled together + /// (includes `rust-analyzer.runnables.extraTestBinaryArgs`). runnables_bench_overrideCommand: Option> = None, /// Command to be executed instead of 'cargo' for runnables. runnables_command: Option = None, - /// Override the command used for bench runnables. + /// Override the command used for doc-test runnables. /// The first element of the array should be the program to execute (for example, `cargo`). /// - /// Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${executable_args}` to dynamically - /// replace the package name, target option (such as `--bin` or `--example`), the target name and - /// the arguments passed to test binary args (includes `rust-analyzer.runnables.extraTestBinaryArgs`). + /// Use the placeholders: + /// - `${package}`: package name. + /// - `${target_arg}`: target option such as `--bin`, `--test`, `--lib`, etc. + /// - `${target}`: target name (empty for `--lib`). + /// - `${test_name}`: the test path filter, e.g. `module::func`. + /// - `${exact}`: always empty for doc-tests. + /// - `${include_ignored}`: always empty for doc-tests. + /// - `${executable_args}`: all of the above binary args bundled together + /// (includes `rust-analyzer.runnables.extraTestBinaryArgs`). runnables_doctest_overrideCommand: Option> = None, /// Additional arguments to be passed to cargo for runnables such as /// tests or binaries. For example, it may be `--release`. @@ -989,9 +1001,15 @@ config_data! { /// Override the command used for test runnables. /// The first element of the array should be the program to execute (for example, `cargo`). /// - /// Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${executable_args}` to dynamically - /// replace the package name, target option (such as `--bin` or `--example`), the target name and - /// the arguments passed to test binary args (includes `rust-analyzer.runnables.extraTestBinaryArgs`). + /// Available placeholders: + /// - `${package}`: package name. + /// - `${target_arg}`: target option such as `--bin`, `--test`, `--lib`, etc. + /// - `${target}`: target name (empty for `--lib`). + /// - `${test_name}`: the test path filter, e.g. `module::test_func`. + /// - `${exact}`: `--exact` for single tests, empty for modules. + /// - `${include_ignored}`: `--include-ignored` for single tests, empty otherwise. + /// - `${executable_args}`: all of the above binary args bundled together + /// (includes `rust-analyzer.runnables.extraTestBinaryArgs`). runnables_test_overrideCommand: Option> = None, /// Path to the Cargo.toml of the rust compiler workspace, for usage in rustc_private diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs index c1d52e4c9b40b..beb523e6bdec1 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs @@ -230,6 +230,21 @@ impl CargoTargetSpec { }; let test_name = test_name.unwrap_or_default(); + let exact = match kind { + RunnableKind::Test { test_id } | RunnableKind::Bench { test_id } => match test_id { + TestId::Path(_) => "", + TestId::Name(_) => "--exact", + }, + _ => "", + }; + let include_ignored = match kind { + RunnableKind::Test { test_id } => match test_id { + TestId::Path(_) => "", + TestId::Name(_) => "--include-ignored", + }, + _ => "", + }; + let target_arg = |kind| match kind { TargetKind::Bin => "--bin", TargetKind::Test => "--test", @@ -249,7 +264,9 @@ impl CargoTargetSpec { .replace("${package}", &spec.package) .replace("${target_arg}", target_arg(spec.target_kind)) .replace("${target}", target(spec.target_kind, &spec.target)) - .replace("${test_name}", &test_name), + .replace("${test_name}", &test_name) + .replace("${exact}", exact) + .replace("${include_ignored}", include_ignored), _ => arg, }; diff --git a/src/tools/rust-analyzer/docs/book/src/configuration_generated.md b/src/tools/rust-analyzer/docs/book/src/configuration_generated.md index 548c5fb093fa3..da37fc1582342 100644 --- a/src/tools/rust-analyzer/docs/book/src/configuration_generated.md +++ b/src/tools/rust-analyzer/docs/book/src/configuration_generated.md @@ -1404,9 +1404,15 @@ Default: `null` Override the command used for bench runnables. The first element of the array should be the program to execute (for example, `cargo`). -Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${executable_args}` to dynamically -replace the package name, target option (such as `--bin` or `--example`), the target name and -the arguments passed to test binary args (includes `rust-analyzer.runnables.extraTestBinaryArgs`). +Use the placeholders: +- `${package}`: package name. +- `${target_arg}`: target option such as `--bin`, `--test`, `--lib`, etc. +- `${target}`: target name (empty for `--lib`). +- `${test_name}`: the test path filter, e.g. `module::bench_func`. +- `${exact}`: `--exact` for single benchmarks, empty for modules. +- `${include_ignored}`: always empty for benchmarks. +- `${executable_args}`: all of the above binary args bundled together + (includes `rust-analyzer.runnables.extraTestBinaryArgs`). ## rust-analyzer.runnables.command {#runnables.command} @@ -1420,12 +1426,18 @@ Command to be executed instead of 'cargo' for runnables. Default: `null` -Override the command used for bench runnables. +Override the command used for doc-test runnables. The first element of the array should be the program to execute (for example, `cargo`). -Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${executable_args}` to dynamically -replace the package name, target option (such as `--bin` or `--example`), the target name and -the arguments passed to test binary args (includes `rust-analyzer.runnables.extraTestBinaryArgs`). +Use the placeholders: +- `${package}`: package name. +- `${target_arg}`: target option such as `--bin`, `--test`, `--lib`, etc. +- `${target}`: target name (empty for `--lib`). +- `${test_name}`: the test path filter, e.g. `module::func`. +- `${exact}`: always empty for doc-tests. +- `${include_ignored}`: always empty for doc-tests. +- `${executable_args}`: all of the above binary args bundled together + (includes `rust-analyzer.runnables.extraTestBinaryArgs`). ## rust-analyzer.runnables.extraArgs {#runnables.extraArgs} @@ -1468,9 +1480,15 @@ Default: `null` Override the command used for test runnables. The first element of the array should be the program to execute (for example, `cargo`). -Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${executable_args}` to dynamically -replace the package name, target option (such as `--bin` or `--example`), the target name and -the arguments passed to test binary args (includes `rust-analyzer.runnables.extraTestBinaryArgs`). +Available placeholders: +- `${package}`: package name. +- `${target_arg}`: target option such as `--bin`, `--test`, `--lib`, etc. +- `${target}`: target name (empty for `--lib`). +- `${test_name}`: the test path filter, e.g. `module::test_func`. +- `${exact}`: `--exact` for single tests, empty for modules. +- `${include_ignored}`: `--include-ignored` for single tests, empty otherwise. +- `${executable_args}`: all of the above binary args bundled together + (includes `rust-analyzer.runnables.extraTestBinaryArgs`). ## rust-analyzer.rustc.source {#rustc.source} diff --git a/src/tools/rust-analyzer/editors/code/package.json b/src/tools/rust-analyzer/editors/code/package.json index 8c99b30ce9d6c..29cbc8bd4fdab 100644 --- a/src/tools/rust-analyzer/editors/code/package.json +++ b/src/tools/rust-analyzer/editors/code/package.json @@ -2909,7 +2909,7 @@ "title": "Runnables", "properties": { "rust-analyzer.runnables.bench.overrideCommand": { - "markdownDescription": "Override the command used for bench runnables.\nThe first element of the array should be the program to execute (for example, `cargo`).\n\nUse the placeholders `${package}`, `${target_arg}`, `${target}`, `${executable_args}` to dynamically\nreplace the package name, target option (such as `--bin` or `--example`), the target name and\nthe arguments passed to test binary args (includes `rust-analyzer.runnables.extraTestBinaryArgs`).", + "markdownDescription": "Override the command used for bench runnables.\nThe first element of the array should be the program to execute (for example, `cargo`).\n\nUse the placeholders:\n- `${package}`: package name.\n- `${target_arg}`: target option such as `--bin`, `--test`, `--lib`, etc.\n- `${target}`: target name (empty for `--lib`).\n- `${test_name}`: the test path filter, e.g. `module::bench_func`.\n- `${exact}`: `--exact` for single benchmarks, empty for modules.\n- `${include_ignored}`: always empty for benchmarks.\n- `${executable_args}`: all of the above binary args bundled together\n (includes `rust-analyzer.runnables.extraTestBinaryArgs`).", "default": null, "type": [ "null", @@ -2938,7 +2938,7 @@ "title": "Runnables", "properties": { "rust-analyzer.runnables.doctest.overrideCommand": { - "markdownDescription": "Override the command used for bench runnables.\nThe first element of the array should be the program to execute (for example, `cargo`).\n\nUse the placeholders `${package}`, `${target_arg}`, `${target}`, `${executable_args}` to dynamically\nreplace the package name, target option (such as `--bin` or `--example`), the target name and\nthe arguments passed to test binary args (includes `rust-analyzer.runnables.extraTestBinaryArgs`).", + "markdownDescription": "Override the command used for doc-test runnables.\nThe first element of the array should be the program to execute (for example, `cargo`).\n\nUse the placeholders:\n- `${package}`: package name.\n- `${target_arg}`: target option such as `--bin`, `--test`, `--lib`, etc.\n- `${target}`: target name (empty for `--lib`).\n- `${test_name}`: the test path filter, e.g. `module::func`.\n- `${exact}`: always empty for doc-tests.\n- `${include_ignored}`: always empty for doc-tests.\n- `${executable_args}`: all of the above binary args bundled together\n (includes `rust-analyzer.runnables.extraTestBinaryArgs`).", "default": null, "type": [ "null", @@ -2992,7 +2992,7 @@ "title": "Runnables", "properties": { "rust-analyzer.runnables.test.overrideCommand": { - "markdownDescription": "Override the command used for test runnables.\nThe first element of the array should be the program to execute (for example, `cargo`).\n\nUse the placeholders `${package}`, `${target_arg}`, `${target}`, `${executable_args}` to dynamically\nreplace the package name, target option (such as `--bin` or `--example`), the target name and\nthe arguments passed to test binary args (includes `rust-analyzer.runnables.extraTestBinaryArgs`).", + "markdownDescription": "Override the command used for test runnables.\nThe first element of the array should be the program to execute (for example, `cargo`).\n\nAvailable placeholders:\n- `${package}`: package name.\n- `${target_arg}`: target option such as `--bin`, `--test`, `--lib`, etc.\n- `${target}`: target name (empty for `--lib`).\n- `${test_name}`: the test path filter, e.g. `module::test_func`.\n- `${exact}`: `--exact` for single tests, empty for modules.\n- `${include_ignored}`: `--include-ignored` for single tests, empty otherwise.\n- `${executable_args}`: all of the above binary args bundled together\n (includes `rust-analyzer.runnables.extraTestBinaryArgs`).", "default": null, "type": [ "null", From c0a3fd5b56cf4c049079590ced94332d73b41f94 Mon Sep 17 00:00:00 2001 From: BenjaminBrienen Date: Tue, 7 Apr 2026 04:42:31 +0200 Subject: [PATCH 110/144] changes to Cargo config should always refresh --- .../crates/rust-analyzer/src/reload.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs index 71accbed4ef16..816c0c2544d75 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs @@ -975,6 +975,7 @@ pub(crate) fn should_refresh_for_change( change_kind: ChangeKind, additional_paths: &[&str], ) -> bool { + // Note: build scripts are retriggered on file save, no refresh is necessary const IMPLICIT_TARGET_FILES: &[&str] = &["build.rs", "src/main.rs", "src/lib.rs"]; const IMPLICIT_TARGET_DIRS: &[&str] = &["src/bin", "examples", "tests", "benches"]; @@ -991,15 +992,20 @@ pub(crate) fn should_refresh_for_change( return true; } + // .cargo/config{.toml} + if matches!(file_name, "config.toml" | "config") + && path.parent().map(|parent| parent.as_str().ends_with(".cargo")).unwrap_or(false) + { + return true; + } + + // Everything below only matters when files are created or deleted if change_kind == ChangeKind::Modify { return false; } - // .cargo/config{.toml} if path.extension().unwrap_or_default() != "rs" { - let is_cargo_config = matches!(file_name, "config.toml" | "config") - && path.parent().map(|parent| parent.as_str().ends_with(".cargo")).unwrap_or(false); - return is_cargo_config; + return false; } if IMPLICIT_TARGET_FILES.iter().any(|it| path.as_str().ends_with(it)) { From 4fc8dedec062f4d061fc4f80b1f02c7d548fd011 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Tue, 7 Apr 2026 08:52:34 +0300 Subject: [PATCH 111/144] Diagnose cfged-out crate --- .../crates/hir-def/src/nameres/collector.rs | 11 ++++- .../src/handlers/inactive_code.rs | 42 ++++++++++++++++++- 2 files changed, 50 insertions(+), 3 deletions(-) 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 9c101c127b417..703f070dbaeac 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 @@ -22,7 +22,7 @@ use itertools::izip; use la_arena::Idx; use rustc_hash::{FxHashMap, FxHashSet}; use smallvec::SmallVec; -use span::{Edition, FileAstId, SyntaxContext}; +use span::{Edition, FileAstId, ROOT_ERASED_FILE_AST_ID, SyntaxContext}; use stdx::always; use syntax::ast; use triomphe::Arc; @@ -369,7 +369,14 @@ impl<'db> DefCollector<'db> { self.inject_prelude(); - if matches!(item_tree.top_level_attrs(), AttrsOrCfg::CfgDisabled(_)) { + if let AttrsOrCfg::CfgDisabled(attrs) = item_tree.top_level_attrs() { + let (cfg_expr, _) = &**attrs; + self.def_map.diagnostics.push(DefDiagnostic::unconfigured_code( + self.def_map.root, + InFile::new(file_id.into(), ROOT_ERASED_FILE_AST_ID), + cfg_expr.clone(), + self.cfg_options.clone(), + )); return; } diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/inactive_code.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/inactive_code.rs index dfa9639f6eb90..9bfbeeebf7800 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/inactive_code.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/inactive_code.rs @@ -40,7 +40,10 @@ pub(crate) fn inactive_code( #[cfg(test)] mod tests { - use crate::{DiagnosticsConfig, tests::check_diagnostics_with_config}; + use ide_db::RootDatabase; + use test_fixture::WithFixture; + + use crate::{DiagnosticCode, DiagnosticsConfig, tests::check_diagnostics_with_config}; #[track_caller] pub(crate) fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str) { @@ -212,4 +215,41 @@ union FooBar { "#, ); } + + #[test] + fn inactive_crate() { + let db = RootDatabase::with_files( + r#" +#![cfg(false)] + +fn foo() {} + "#, + ); + let file_id = db.test_crate().root_file_id(&db); + let diagnostics = hir::attach_db(&db, || { + crate::full_diagnostics( + &db, + &DiagnosticsConfig::test_sample(), + &ide_db::assists::AssistResolveStrategy::All, + file_id.file_id(&db), + ) + }); + let [inactive_code] = &*diagnostics else { + panic!("expected one inactive_code diagnostic, found {diagnostics:#?}"); + }; + assert_eq!( + inactive_code.code, + DiagnosticCode::Ra("inactive-code", ide_db::Severity::WeakWarning) + ); + assert_eq!( + inactive_code.message, + "code is inactive due to #[cfg] directives: false is disabled", + ); + assert!(inactive_code.fixes.is_none()); + let full_file_range = file_id.parse(&db).syntax_node().text_range(); + assert_eq!( + inactive_code.range, + ide_db::FileRange { file_id: file_id.file_id(&db), range: full_file_range }, + ); + } } From 3350dae0ea7570d78ae888dc101c2fee1bd028fc Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sat, 4 Apr 2026 20:05:57 +0800 Subject: [PATCH 112/144] fix: Improve add some on block like expression Example --- ```rust fn foo() -> Result<(), ()> { for _ in 0..5 {}$0 } ``` **Before this PR** ```rust fn foo() -> Result<(), ()> { Ok(for _ in 0..5 {}) } ``` **After this PR** ```rust fn foo() -> Result<(), ()> { for _ in 0..5 {} Ok(()) } ``` --- .../src/handlers/type_mismatch.rs | 47 ++++++++++++++----- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs index 2f79a603bbb1a..ff0e6a254b6ae 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs @@ -110,7 +110,8 @@ fn add_missing_ok_or_some( ) -> Option<()> { let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id); let expr = expr_ptr.value.to_node(&root); - let expr_range = ctx.sema.original_range_opt(expr.syntax())?.range; + let hir::FileRange { file_id, range: expr_range } = + ctx.sema.original_range_opt(expr.syntax())?; let scope = ctx.sema.scope(expr.syntax())?; let expected_adt = d.expected.as_adt()?; @@ -133,6 +134,8 @@ fn add_missing_ok_or_some( return None; } + let file_id = file_id.file_id(ctx.sema.db); + if d.actual.is_unit() { if let Expr::BlockExpr(block) = &expr { if block.tail_expr().is_none() { @@ -155,10 +158,7 @@ fn add_missing_ok_or_some( ); } - let source_change = SourceChange::from_text_edit( - expr_ptr.file_id.original_file(ctx.sema.db).file_id(ctx.sema.db), - builder.finish(), - ); + let source_change = SourceChange::from_text_edit(file_id, builder.finish()); let name = format!("Insert {variant_name}(()) as the tail of this block"); acc.push(fix("insert_wrapped_unit", &name, source_change, expr_range)); } @@ -168,24 +168,30 @@ fn add_missing_ok_or_some( if ret_expr.expr().is_none() { let mut builder = TextEdit::builder(); builder.insert(expr_range.end(), format!(" {variant_name}(())")); - let source_change = SourceChange::from_text_edit( - expr_ptr.file_id.original_file(ctx.sema.db).file_id(ctx.sema.db), - builder.finish(), - ); + let source_change = SourceChange::from_text_edit(file_id, builder.finish()); let name = format!("Insert {variant_name}(()) as the return value"); acc.push(fix("insert_wrapped_unit", &name, source_change, expr_range)); } return Some(()); + } else if expr.is_block_like() + && expr.syntax().parent().and_then(ast::StmtList::cast).is_some() + { + // Fix for forms like `fn foo() -> Result<(), String> { for _ in 0..8 {} }` + let mut builder = TextEdit::builder(); + let indent = expr.indent_level(); + builder.insert(expr_range.end(), format!("\n{indent}{variant_name}(())")); + + let source_change = SourceChange::from_text_edit(file_id, builder.finish()); + let name = format!("Insert {variant_name}(()) as the tail of this block"); + acc.push(fix("insert_wrapped_unit", &name, source_change, expr_range)); + return Some(()); } } let mut builder = TextEdit::builder(); builder.insert(expr_range.start(), format!("{variant_name}(")); builder.insert(expr_range.end(), ")".to_owned()); - let source_change = SourceChange::from_text_edit( - expr_ptr.file_id.original_file(ctx.sema.db).file_id(ctx.sema.db), - builder.finish(), - ); + let source_change = SourceChange::from_text_edit(file_id, builder.finish()); let name = format!("Wrap in {variant_name}"); acc.push(fix("wrap_in_constructor", &name, source_change, expr_range)); Some(()) @@ -730,6 +736,21 @@ fn foo() -> Result<(), ()> {}$0 r#" fn foo() -> Result<(), ()> { Ok(()) +} + "#, + ); + + check_fix( + r#" +//- minicore: result +fn foo() -> Result<(), ()> { + for _ in 0..5 {}$0 +} + "#, + r#" +fn foo() -> Result<(), ()> { + for _ in 0..5 {} + Ok(()) } "#, ); From fe134ed0c4db55068e3b28ac9addd49a8c4801f9 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Tue, 7 Apr 2026 18:26:14 +0800 Subject: [PATCH 113/144] minor: Fix self kw is snippet in type location Example --- ```rust const _: $0 ``` **Before this PR** ```text self::~ crate::~ ``` **After this PR** ```text self:: crate:: ``` --- .../crates/ide-completion/src/render.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render.rs index f37b73a28ab60..b946441991383 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/render.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/render.rs @@ -590,14 +590,17 @@ pub(crate) fn render_type_keyword_snippet( let mut item = CompletionItem::new(CompletionItemKind::Keyword, source_range, label, ctx.edition); - let cap = ctx.config.snippet_cap; - if let Some(cap) = cap { + let insert_text = if !snippet.contains('$') { + item.insert_text(snippet); + snippet + } else if let Some(cap) = ctx.config.snippet_cap { item.insert_snippet(cap, snippet); - } - - let insert_text = if cap.is_some() { snippet } else { label }.to_owned(); - adds_ret_type_arrow(ctx, path_ctx, &mut item, insert_text); + snippet + } else { + label + }; + adds_ret_type_arrow(ctx, path_ctx, &mut item, insert_text.to_owned()); item } From 70e0429257446ceb85e512a0d948f0f33347edbb Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Tue, 7 Apr 2026 16:00:01 +0530 Subject: [PATCH 114/144] acquire parent directly via Some() --- .../crates/syntax/src/syntax_editor/edit_algo.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edit_algo.rs b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edit_algo.rs index 1baba5e299873..78e7083f97e4c 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edit_algo.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edit_algo.rs @@ -217,9 +217,9 @@ pub(super) fn apply_edits(editor: SyntaxEditor) -> SyntaxEdit { } Change::Insert(_, SyntaxElement::Token(token)) | Change::Replace(_, Some(SyntaxElement::Token(token))) => { - if token.parent().is_some() { + if let Some(parent) = token.parent() { let idx = token.index(); - let new_parent = token.parent().unwrap().clone_subtree().clone_for_update(); + let new_parent = parent.clone_subtree().clone_for_update(); *token = new_parent .children_with_tokens() .nth(idx) @@ -240,10 +240,9 @@ pub(super) fn apply_edits(editor: SyntaxEditor) -> SyntaxEdit { } } SyntaxElement::Token(token) => { - if token.parent().is_some() { + if let Some(parent) = token.parent() { let idx = token.index(); - let new_parent = - token.parent().unwrap().clone_subtree().clone_for_update(); + let new_parent = parent.clone_subtree().clone_for_update(); *token = new_parent .children_with_tokens() .nth(idx) From efe858abb86329347abf2dc03ff4277e4ce6faff Mon Sep 17 00:00:00 2001 From: Wilfred Hughes Date: Tue, 7 Apr 2026 11:42:33 +0100 Subject: [PATCH 115/144] fix: Use the official npm mirror for all VS Code dependencies c420e48547e444e1f978be9dbf045d459936b0d5 changed a few packages to be installed from npmmirror.com rather than npmjs.org. This is harmless (there's a SHA512 hash to confirm integrity) but it prevents building the VS Code extension in locked down environments that only permit official package repositories. It looks like npmmirror.com is an unofficial mirror run by Taobao (it replaced npm.taobao.org) and it can lag behind the official npm repository. --- .../editors/code/package-lock.json | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/tools/rust-analyzer/editors/code/package-lock.json b/src/tools/rust-analyzer/editors/code/package-lock.json index 24e65e2487f44..5755f0708f0fa 100644 --- a/src/tools/rust-analyzer/editors/code/package-lock.json +++ b/src/tools/rust-analyzer/editors/code/package-lock.json @@ -257,7 +257,7 @@ }, "node_modules/@emnapi/core": { "version": "1.9.1", - "resolved": "https://registry.npmmirror.com/@emnapi/core/-/core-1.9.1.tgz", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz", "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", "dev": true, "license": "MIT", @@ -269,7 +269,7 @@ }, "node_modules/@emnapi/runtime": { "version": "1.9.1", - "resolved": "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.9.1.tgz", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", "dev": true, "license": "MIT", @@ -280,7 +280,7 @@ }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", "dev": true, "license": "MIT", @@ -940,7 +940,7 @@ }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.12", - "resolved": "https://registry.npmmirror.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", "dev": true, "license": "MIT", @@ -953,7 +953,7 @@ }, "node_modules/@node-rs/crc32": { "version": "1.10.6", - "resolved": "https://registry.npmmirror.com/@node-rs/crc32/-/crc32-1.10.6.tgz", + "resolved": "https://registry.npmjs.org/@node-rs/crc32/-/crc32-1.10.6.tgz", "integrity": "sha512-+llXfqt+UzgoDzT9of5vPQPGqTAVCohU74I9zIBkNo5TH6s2P31DFJOGsJQKN207f0GHnYv5pV3wh3BCY/un/A==", "dev": true, "license": "MIT", @@ -983,7 +983,7 @@ }, "node_modules/@node-rs/crc32-android-arm-eabi": { "version": "1.10.6", - "resolved": "https://registry.npmmirror.com/@node-rs/crc32-android-arm-eabi/-/crc32-android-arm-eabi-1.10.6.tgz", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-android-arm-eabi/-/crc32-android-arm-eabi-1.10.6.tgz", "integrity": "sha512-vZAMuJXm3TpWPOkkhxdrofWDv+Q+I2oO7ucLRbXyAPmXFNDhHtBxbO1rk9Qzz+M3eep8ieS4/+jCL1Q0zacNMQ==", "cpu": [ "arm" @@ -1000,7 +1000,7 @@ }, "node_modules/@node-rs/crc32-android-arm64": { "version": "1.10.6", - "resolved": "https://registry.npmmirror.com/@node-rs/crc32-android-arm64/-/crc32-android-arm64-1.10.6.tgz", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-android-arm64/-/crc32-android-arm64-1.10.6.tgz", "integrity": "sha512-Vl/JbjCinCw/H9gEpZveWCMjxjcEChDcDBM8S4hKay5yyoRCUHJPuKr4sjVDBeOm+1nwU3oOm6Ca8dyblwp4/w==", "cpu": [ "arm64" @@ -1017,7 +1017,7 @@ }, "node_modules/@node-rs/crc32-darwin-arm64": { "version": "1.10.6", - "resolved": "https://registry.npmmirror.com/@node-rs/crc32-darwin-arm64/-/crc32-darwin-arm64-1.10.6.tgz", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-darwin-arm64/-/crc32-darwin-arm64-1.10.6.tgz", "integrity": "sha512-kARYANp5GnmsQiViA5Qu74weYQ3phOHSYQf0G+U5wB3NB5JmBHnZcOc46Ig21tTypWtdv7u63TaltJQE41noyg==", "cpu": [ "arm64" @@ -1034,7 +1034,7 @@ }, "node_modules/@node-rs/crc32-darwin-x64": { "version": "1.10.6", - "resolved": "https://registry.npmmirror.com/@node-rs/crc32-darwin-x64/-/crc32-darwin-x64-1.10.6.tgz", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-darwin-x64/-/crc32-darwin-x64-1.10.6.tgz", "integrity": "sha512-Q99bevJVMfLTISpkpKBlXgtPUItrvTWKFyiqoKH5IvscZmLV++NH4V13Pa17GTBmv9n18OwzgQY4/SRq6PQNVA==", "cpu": [ "x64" @@ -1051,7 +1051,7 @@ }, "node_modules/@node-rs/crc32-freebsd-x64": { "version": "1.10.6", - "resolved": "https://registry.npmmirror.com/@node-rs/crc32-freebsd-x64/-/crc32-freebsd-x64-1.10.6.tgz", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-freebsd-x64/-/crc32-freebsd-x64-1.10.6.tgz", "integrity": "sha512-66hpawbNjrgnS9EDMErta/lpaqOMrL6a6ee+nlI2viduVOmRZWm9Rg9XdGTK/+c4bQLdtC6jOd+Kp4EyGRYkAg==", "cpu": [ "x64" @@ -1068,7 +1068,7 @@ }, "node_modules/@node-rs/crc32-linux-arm-gnueabihf": { "version": "1.10.6", - "resolved": "https://registry.npmmirror.com/@node-rs/crc32-linux-arm-gnueabihf/-/crc32-linux-arm-gnueabihf-1.10.6.tgz", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-linux-arm-gnueabihf/-/crc32-linux-arm-gnueabihf-1.10.6.tgz", "integrity": "sha512-E8Z0WChH7X6ankbVm8J/Yym19Cq3otx6l4NFPS6JW/cWdjv7iw+Sps2huSug+TBprjbcEA+s4TvEwfDI1KScjg==", "cpu": [ "arm" @@ -1085,7 +1085,7 @@ }, "node_modules/@node-rs/crc32-linux-arm64-gnu": { "version": "1.10.6", - "resolved": "https://registry.npmmirror.com/@node-rs/crc32-linux-arm64-gnu/-/crc32-linux-arm64-gnu-1.10.6.tgz", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-linux-arm64-gnu/-/crc32-linux-arm64-gnu-1.10.6.tgz", "integrity": "sha512-LmWcfDbqAvypX0bQjQVPmQGazh4dLiVklkgHxpV4P0TcQ1DT86H/SWpMBMs/ncF8DGuCQ05cNyMv1iddUDugoQ==", "cpu": [ "arm64" @@ -1102,7 +1102,7 @@ }, "node_modules/@node-rs/crc32-linux-arm64-musl": { "version": "1.10.6", - "resolved": "https://registry.npmmirror.com/@node-rs/crc32-linux-arm64-musl/-/crc32-linux-arm64-musl-1.10.6.tgz", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-linux-arm64-musl/-/crc32-linux-arm64-musl-1.10.6.tgz", "integrity": "sha512-k8ra/bmg0hwRrIEE8JL1p32WfaN9gDlUUpQRWsbxd1WhjqvXea7kKO6K4DwVxyxlPhBS9Gkb5Urq7Y4mXANzaw==", "cpu": [ "arm64" @@ -1119,7 +1119,7 @@ }, "node_modules/@node-rs/crc32-linux-x64-gnu": { "version": "1.10.6", - "resolved": "https://registry.npmmirror.com/@node-rs/crc32-linux-x64-gnu/-/crc32-linux-x64-gnu-1.10.6.tgz", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-linux-x64-gnu/-/crc32-linux-x64-gnu-1.10.6.tgz", "integrity": "sha512-IfjtqcuFK7JrSZ9mlAFhb83xgium30PguvRjIMI45C3FJwu18bnLk1oR619IYb/zetQT82MObgmqfKOtgemEKw==", "cpu": [ "x64" @@ -1136,7 +1136,7 @@ }, "node_modules/@node-rs/crc32-linux-x64-musl": { "version": "1.10.6", - "resolved": "https://registry.npmmirror.com/@node-rs/crc32-linux-x64-musl/-/crc32-linux-x64-musl-1.10.6.tgz", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-linux-x64-musl/-/crc32-linux-x64-musl-1.10.6.tgz", "integrity": "sha512-LbFYsA5M9pNunOweSt6uhxenYQF94v3bHDAQRPTQ3rnjn+mK6IC7YTAYoBjvoJP8lVzcvk9hRj8wp4Jyh6Y80g==", "cpu": [ "x64" @@ -1153,7 +1153,7 @@ }, "node_modules/@node-rs/crc32-wasm32-wasi": { "version": "1.10.6", - "resolved": "https://registry.npmmirror.com/@node-rs/crc32-wasm32-wasi/-/crc32-wasm32-wasi-1.10.6.tgz", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-wasm32-wasi/-/crc32-wasm32-wasi-1.10.6.tgz", "integrity": "sha512-KaejdLgHMPsRaxnM+OG9L9XdWL2TabNx80HLdsCOoX9BVhEkfh39OeahBo8lBmidylKbLGMQoGfIKDjq0YMStw==", "cpu": [ "wasm32" @@ -1170,7 +1170,7 @@ }, "node_modules/@node-rs/crc32-win32-arm64-msvc": { "version": "1.10.6", - "resolved": "https://registry.npmmirror.com/@node-rs/crc32-win32-arm64-msvc/-/crc32-win32-arm64-msvc-1.10.6.tgz", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-win32-arm64-msvc/-/crc32-win32-arm64-msvc-1.10.6.tgz", "integrity": "sha512-x50AXiSxn5Ccn+dCjLf1T7ZpdBiV1Sp5aC+H2ijhJO4alwznvXgWbopPRVhbp2nj0i+Gb6kkDUEyU+508KAdGQ==", "cpu": [ "arm64" @@ -1187,7 +1187,7 @@ }, "node_modules/@node-rs/crc32-win32-ia32-msvc": { "version": "1.10.6", - "resolved": "https://registry.npmmirror.com/@node-rs/crc32-win32-ia32-msvc/-/crc32-win32-ia32-msvc-1.10.6.tgz", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-win32-ia32-msvc/-/crc32-win32-ia32-msvc-1.10.6.tgz", "integrity": "sha512-DpDxQLaErJF9l36aghe1Mx+cOnYLKYo6qVPqPL9ukJ5rAGLtCdU0C+Zoi3gs9ySm8zmbFgazq/LvmsZYU42aBw==", "cpu": [ "ia32" @@ -1204,7 +1204,7 @@ }, "node_modules/@node-rs/crc32-win32-x64-msvc": { "version": "1.10.6", - "resolved": "https://registry.npmmirror.com/@node-rs/crc32-win32-x64-msvc/-/crc32-win32-x64-msvc-1.10.6.tgz", + "resolved": "https://registry.npmjs.org/@node-rs/crc32-win32-x64-msvc/-/crc32-win32-x64-msvc-1.10.6.tgz", "integrity": "sha512-5B1vXosIIBw1m2Rcnw62IIfH7W9s9f7H7Ma0rRuhT8HR4Xh8QCgw6NJSI2S2MCngsGktYnAhyUvs81b7efTyQw==", "cpu": [ "x64" @@ -1676,7 +1676,7 @@ }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", - "resolved": "https://registry.npmmirror.com/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", "dev": true, "license": "MIT", @@ -2006,7 +2006,7 @@ }, "node_modules/@vscode/vsce": { "version": "3.7.1", - "resolved": "https://registry.npmmirror.com/@vscode/vsce/-/vsce-3.7.1.tgz", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.7.1.tgz", "integrity": "sha512-OTm2XdMt2YkpSn2Nx7z2EJtSuhRHsTPYsSK59hr3v8jRArK+2UEoju4Jumn1CmpgoBLGI6ReHLJ/czYltNUW3g==", "dev": true, "license": "MIT", @@ -3354,7 +3354,7 @@ }, "node_modules/define-data-property": { "version": "1.1.4", - "resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, "license": "MIT", @@ -3385,7 +3385,7 @@ }, "node_modules/define-properties": { "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/define-properties/-/define-properties-1.2.1.tgz", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dev": true, "license": "MIT", @@ -4319,7 +4319,7 @@ }, "node_modules/globalthis": { "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/globalthis/-/globalthis-1.0.4.tgz", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", "dev": true, "license": "MIT", @@ -4397,7 +4397,7 @@ }, "node_modules/has-property-descriptors": { "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, "license": "MIT", @@ -4703,7 +4703,7 @@ }, "node_modules/is-it-type": { "version": "5.1.3", - "resolved": "https://registry.npmmirror.com/is-it-type/-/is-it-type-5.1.3.tgz", + "resolved": "https://registry.npmjs.org/is-it-type/-/is-it-type-5.1.3.tgz", "integrity": "sha512-AX2uU0HW+TxagTgQXOJY7+2fbFHemC7YFBwN1XqD8qQMKdtfbOC8OC3fUb4s5NU59a3662Dzwto8tWDdZYRXxg==", "dev": true, "license": "MIT", @@ -5483,7 +5483,7 @@ }, "node_modules/object-keys": { "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true, "license": "MIT", @@ -5619,7 +5619,7 @@ }, "node_modules/ovsx": { "version": "0.10.10", - "resolved": "https://registry.npmmirror.com/ovsx/-/ovsx-0.10.10.tgz", + "resolved": "https://registry.npmjs.org/ovsx/-/ovsx-0.10.10.tgz", "integrity": "sha512-/X5J4VLKPUGGaMynW9hgvsGg9jmwsK/3RhODeA2yzdeDbb8PUSNcg5GQ9aPDJW/znlqNvAwQcXAyE+Cq0RRvAQ==", "dev": true, "license": "EPL-2.0", @@ -6465,7 +6465,7 @@ }, "node_modules/simple-invariant": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/simple-invariant/-/simple-invariant-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/simple-invariant/-/simple-invariant-2.0.1.tgz", "integrity": "sha512-1sbhsxqI+I2tqlmjbz99GXNmZtr6tKIyEgGGnJw/MKGblalqk/XoOYYFJlBzTKZCxx8kLaD3FD5s9BEEjx5Pyg==", "dev": true, "license": "MIT", @@ -7544,7 +7544,7 @@ }, "node_modules/yauzl-promise": { "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/yauzl-promise/-/yauzl-promise-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/yauzl-promise/-/yauzl-promise-4.0.0.tgz", "integrity": "sha512-/HCXpyHXJQQHvFq9noqrjfa/WpQC2XYs3vI7tBiAi4QiIU1knvYhZGaO1QPjwIVMdqflxbmwgMXtYeaRiAE0CA==", "dev": true, "license": "MIT", From eb708d6dde415686869a55066ecfda394e78077c Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Tue, 7 Apr 2026 20:02:48 +0800 Subject: [PATCH 116/144] internal: Fix lsp_ext field name for RecursiveMemoryLayoutNode Due `editors/code/src/commands.ts` uses inline HTML, so this error pass to type check --- src/tools/rust-analyzer/editors/code/src/lsp_ext.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tools/rust-analyzer/editors/code/src/lsp_ext.ts b/src/tools/rust-analyzer/editors/code/src/lsp_ext.ts index 9712bd4b7b99e..cf190ea3ce0d2 100644 --- a/src/tools/rust-analyzer/editors/code/src/lsp_ext.ts +++ b/src/tools/rust-analyzer/editors/code/src/lsp_ext.ts @@ -300,14 +300,14 @@ export type SsrParams = { }; export type RecursiveMemoryLayoutNode = { - item_name: string; + itemName: string; typename: string; size: number; alignment: number; offset: number; - parent_idx: number; - children_start: number; - children_len: number; + parentIdx: number; + childrenStart: number; + childrenLen: number; }; export type RecursiveMemoryLayout = { nodes: RecursiveMemoryLayoutNode[]; From bcfd03276d7ede21e7e9f8676f60af02368ccfdf Mon Sep 17 00:00:00 2001 From: Wilfred Hughes Date: Tue, 31 Mar 2026 15:08:20 +0100 Subject: [PATCH 117/144] fix: Improve label on add_missing_match_arms assist "Fill match arms" is an extremely helpful assist, but the name is pretty opaque to new users (at least it was to me). We actually renamed the file in rust-lang/rust-analyzer#10299 from `fill_match_arms.rs` to `add_missing_match_arms.rs` but the label hasn't changed from its original text in rust-lang/rust-analyzer#733. Instead, show "Add N missing match arms" if there are multiple missing match arms, and show "Add missing match arm `Foo::Bar`" when there's only a single missing match arm. Partially generated by Claude Opus. --- .../src/handlers/add_missing_match_arms.rs | 103 ++++++++++++++---- .../crates/ide-assists/src/tests.rs | 20 +++- 2 files changed, 97 insertions(+), 26 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_match_arms.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_match_arms.rs index b063e5ffce87d..b7510bb82676a 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_match_arms.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_match_arms.rs @@ -1,4 +1,4 @@ -use std::iter::{self, Peekable}; +use std::iter; use either::Either; use hir::{Adt, AsAssocItem, Crate, FindPathConfig, HasAttrs, ModuleDef, Semantics}; @@ -93,8 +93,8 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) } else { None }; - let (mut missing_pats, is_non_exhaustive, has_hidden_variants): ( - Peekable>>, + let (missing_pats, is_non_exhaustive, has_hidden_variants): ( + Vec<(ast::Pat, bool)>, bool, bool, ) = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr, self_ty.as_ref()) { @@ -117,15 +117,15 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) .filter(|(variant_pat, _)| is_variant_missing(&top_lvl_pats, variant_pat)); let option_enum = FamousDefs(&ctx.sema, module.krate(ctx.db())).core_option_Option(); - let missing_pats: Box> = if matches!(enum_def, ExtendedEnum::Enum { enum_: e, .. } if Some(e) == option_enum) + let missing_pats: Vec<_> = if matches!(enum_def, ExtendedEnum::Enum { enum_: e, .. } if Some(e) == option_enum) { // Match `Some` variant first. cov_mark::hit!(option_order); - Box::new(missing_pats.rev()) + missing_pats.rev().collect() } else { - Box::new(missing_pats) + missing_pats.collect() }; - (missing_pats.peekable(), is_non_exhaustive, has_hidden_variants) + (missing_pats, is_non_exhaustive, has_hidden_variants) } else if let Some(enum_defs) = resolve_tuple_of_enum_def(&ctx.sema, &expr, self_ty.as_ref()) { let is_non_exhaustive = enum_defs .iter() @@ -169,12 +169,9 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) (ast::Pat::from(make.tuple_pat(patterns)), is_hidden) }) - .filter(|(variant_pat, _)| is_variant_missing(&top_lvl_pats, variant_pat)); - ( - (Box::new(missing_pats) as Box>).peekable(), - is_non_exhaustive, - has_hidden_variants, - ) + .filter(|(variant_pat, _)| is_variant_missing(&top_lvl_pats, variant_pat)) + .collect(); + (missing_pats, is_non_exhaustive, has_hidden_variants) } else if let Some((enum_def, len)) = resolve_array_of_enum_def(&ctx.sema, &expr, self_ty.as_ref()) { @@ -205,12 +202,9 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) (ast::Pat::from(make.slice_pat(patterns)), is_hidden) }) - .filter(|(variant_pat, _)| is_variant_missing(&top_lvl_pats, variant_pat)); - ( - (Box::new(missing_pats) as Box>).peekable(), - is_non_exhaustive, - has_hidden_variants, - ) + .filter(|(variant_pat, _)| is_variant_missing(&top_lvl_pats, variant_pat)) + .collect(); + (missing_pats, is_non_exhaustive, has_hidden_variants) } else { return None; }; @@ -218,20 +212,31 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) let mut needs_catch_all_arm = is_non_exhaustive && !has_catch_all_arm; if !needs_catch_all_arm - && ((has_hidden_variants && has_catch_all_arm) || missing_pats.peek().is_none()) + && ((has_hidden_variants && has_catch_all_arm) || missing_pats.is_empty()) { return None; } + let visible_count = missing_pats.iter().filter(|(_, hidden)| !hidden).count(); + let label = if visible_count == 0 { + "Add missing catch-all match arm `_`".to_owned() + } else if visible_count == 1 { + let pat = &missing_pats.iter().find(|(_, hidden)| !hidden).unwrap().0; + format!("Add missing match arm `{pat}`") + } else { + format!("Add {visible_count} missing match arms") + }; + acc.add( AssistId::quick_fix("add_missing_match_arms"), - "Fill match arms", + label, ctx.sema.original_range(match_expr.syntax()).range, |builder| { // having any hidden variants means that we need a catch-all arm needs_catch_all_arm |= has_hidden_variants; let mut missing_arms = missing_pats + .into_iter() .filter(|(_, hidden)| { // filter out hidden patterns because they're handled by the catch-all arm !hidden @@ -635,7 +640,7 @@ mod tests { use crate::AssistConfig; use crate::tests::{ TEST_CONFIG, check_assist, check_assist_not_applicable, check_assist_target, - check_assist_unresolved, check_assist_with_config, + check_assist_unresolved, check_assist_with_config, check_assist_with_label, }; use super::add_missing_match_arms; @@ -1828,8 +1833,10 @@ fn foo(t: Test) { #[test] fn lazy_computation() { - // Computing a single missing arm is enough to determine applicability of the assist. - cov_mark::check_count!(add_missing_match_arms_lazy_computation, 1); + // We now collect all missing arms eagerly, so we can show the count + // of missing arms. + cov_mark::check_count!(add_missing_match_arms_lazy_computation, 4); + check_assist_unresolved( add_missing_match_arms, r#" @@ -1841,6 +1848,54 @@ fn foo(tuple: (A, A)) { ); } + #[test] + fn label_single_missing_arm() { + check_assist_with_label( + add_missing_match_arms, + r#" +enum A { One, Two } +fn foo(a: A) { + match $0a { + A::One => {} + } +} +"#, + "Add missing match arm `A::Two`", + ); + } + + #[test] + fn label_multiple_missing_arms() { + check_assist_with_label( + add_missing_match_arms, + r#" +enum A { One, Two, Three } +fn foo(a: A) { + match $0a {} +} +"#, + "Add 3 missing match arms", + ); + } + + #[test] + fn label_catch_all_only() { + check_assist_with_label( + add_missing_match_arms, + r#" +//- /main.rs crate:main deps:e +fn foo(t: ::e::E) { + match $0t { + e::E::A => {} + } +} +//- /e.rs crate:e +pub enum E { A, #[doc(hidden)] B, } +"#, + "Add missing catch-all match arm `_`", + ); + } + #[test] fn adds_comma_before_new_arms() { check_assist( diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs b/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs index 1c90c95fe155f..135e750ca066c 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs @@ -207,6 +207,15 @@ pub(crate) fn check_assist_target( check(assist, ra_fixture, ExpectedResult::Target(target), None); } +#[track_caller] +pub(crate) fn check_assist_with_label( + assist: Handler, + #[rust_analyzer::rust_fixture] ra_fixture: &str, + label: &str, +) { + check(assist, ra_fixture, ExpectedResult::Label(label), None); +} + #[track_caller] pub(crate) fn check_assist_not_applicable( assist: Handler, @@ -307,6 +316,7 @@ enum ExpectedResult<'a> { Unresolved, After(&'a str), Target(&'a str), + Label(&'a str), } #[track_caller] @@ -335,7 +345,7 @@ fn check_with_config( let ctx = AssistContext::new(sema, &config, frange); let resolve = match expected { - ExpectedResult::Unresolved => AssistResolveStrategy::None, + ExpectedResult::Unresolved | ExpectedResult::Label(_) => AssistResolveStrategy::None, _ => AssistResolveStrategy::All, }; let mut acc = Assists::new(&ctx, resolve); @@ -404,6 +414,9 @@ fn check_with_config( let range = assist.target; assert_eq_text!(&text_without_caret[range], target); } + (Some(assist), ExpectedResult::Label(label)) => { + assert_eq!(assist.label.to_string(), label); + } (Some(assist), ExpectedResult::Unresolved) => assert!( assist.source_change.is_none(), "unresolved assist should not contain source changes" @@ -411,7 +424,10 @@ fn check_with_config( (Some(_), ExpectedResult::NotApplicable) => panic!("assist should not be applicable!"), ( None, - ExpectedResult::After(_) | ExpectedResult::Target(_) | ExpectedResult::Unresolved, + ExpectedResult::After(_) + | ExpectedResult::Target(_) + | ExpectedResult::Label(_) + | ExpectedResult::Unresolved, ) => { panic!("code action is not applicable") } From 772d9620f4f91ebe167e0c7d2d02e502a5f253c0 Mon Sep 17 00:00:00 2001 From: Stanley Horwood Date: Tue, 7 Apr 2026 15:40:55 +0200 Subject: [PATCH 118/144] fix: update exact and include_ignored flags --- .../crates/rust-analyzer/src/target_spec.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs index beb523e6bdec1..01196b80cdb2d 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/target_spec.rs @@ -232,16 +232,13 @@ impl CargoTargetSpec { let exact = match kind { RunnableKind::Test { test_id } | RunnableKind::Bench { test_id } => match test_id { - TestId::Path(_) => "", - TestId::Name(_) => "--exact", + TestId::Path(_) => "--exact", + TestId::Name(_) => "", }, _ => "", }; let include_ignored = match kind { - RunnableKind::Test { test_id } => match test_id { - TestId::Path(_) => "", - TestId::Name(_) => "--include-ignored", - }, + RunnableKind::Test { .. } => "--include-ignored", _ => "", }; From 15251b574dbf8e32a33026fc56117211e1c67428 Mon Sep 17 00:00:00 2001 From: BenjaminBrienen Date: Tue, 7 Apr 2026 21:45:18 +0200 Subject: [PATCH 119/144] unwrap unnecessary result return type --- src/tools/rust-analyzer/crates/ide/src/lib.rs | 2 +- src/tools/rust-analyzer/crates/ide/src/view_crate_graph.rs | 4 ++-- .../crates/rust-analyzer/src/handlers/request.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide/src/lib.rs b/src/tools/rust-analyzer/crates/ide/src/lib.rs index f3e51e191929b..270998cdf751c 100644 --- a/src/tools/rust-analyzer/crates/ide/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide/src/lib.rs @@ -414,7 +414,7 @@ impl Analysis { } /// Renders the crate graph to GraphViz "dot" syntax. - pub fn view_crate_graph(&self, full: bool) -> Cancellable> { + pub fn view_crate_graph(&self, full: bool) -> Cancellable { self.with_db(|db| view_crate_graph::view_crate_graph(db, full)) } diff --git a/src/tools/rust-analyzer/crates/ide/src/view_crate_graph.rs b/src/tools/rust-analyzer/crates/ide/src/view_crate_graph.rs index e1670b7187968..ecfdd09b24485 100644 --- a/src/tools/rust-analyzer/crates/ide/src/view_crate_graph.rs +++ b/src/tools/rust-analyzer/crates/ide/src/view_crate_graph.rs @@ -16,7 +16,7 @@ use ide_db::{ // | Editor | Action Name | // |---------|-------------| // | VS Code | **rust-analyzer: View Crate Graph** | -pub(crate) fn view_crate_graph(db: &RootDatabase, full: bool) -> Result { +pub(crate) fn view_crate_graph(db: &RootDatabase, full: bool) -> String { let all_crates = all_crates(db); let crates_to_render = all_crates .iter() @@ -36,7 +36,7 @@ pub(crate) fn view_crate_graph(db: &RootDatabase, full: bool) -> Result { diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs index 9c2e0a5f321b4..86516b6079c72 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs @@ -332,7 +332,7 @@ pub(crate) fn handle_view_crate_graph( params: ViewCrateGraphParams, ) -> anyhow::Result { let _p = tracing::info_span!("handle_view_crate_graph").entered(); - let dot = snap.analysis.view_crate_graph(params.full)?.map_err(anyhow::Error::msg)?; + let dot = snap.analysis.view_crate_graph(params.full)?; Ok(dot) } From abe5495e28f6797dccf7ea1f8644dffc259185a7 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Wed, 8 Apr 2026 07:37:23 +0530 Subject: [PATCH 120/144] migrate generate_trait_from_impl --- .../src/handlers/generate_trait_from_impl.rs | 13 +++--- .../src/ast/syntax_factory/constructors.rs | 41 +++++++++++++++++++ 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs index 225556090097b..2d3d05849b0ba 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_trait_from_impl.rs @@ -4,6 +4,7 @@ use syntax::{ AstNode, AstToken, SyntaxKind, T, ast::{ self, HasDocComments, HasGenericParams, HasName, HasVisibility, edit::AstNodeEdit, make, + syntax_factory::SyntaxFactory, }, syntax_editor::{Position, SyntaxEditor}, }; @@ -107,17 +108,18 @@ pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_ }); ast::AssocItemList::cast(trait_items_editor.finish().new_root().clone()).unwrap() }; - let trait_ast = make::trait_( + + let factory = SyntaxFactory::with_mappings(); + let trait_ast = factory.trait_( false, &trait_name(&impl_assoc_items).text(), impl_ast.generic_param_list(), impl_ast.where_clause(), trait_items, - ) - .clone_for_update(); + ); let trait_name = trait_ast.name().expect("new trait should have a name"); - let trait_name_ref = make::name_ref(&trait_name.to_string()).clone_for_update(); + let trait_name_ref = factory.name_ref(&trait_name.to_string()); // Change `impl Foo` to `impl NewTrait for Foo` let mut elements = vec![ @@ -128,7 +130,7 @@ pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_ ]; if let Some(params) = impl_ast.generic_param_list() { - let gen_args = ¶ms.to_generic_args().clone_for_update(); + let gen_args = ¶ms.to_generic_args(); elements.insert(1, gen_args.syntax().clone().into()); } @@ -156,6 +158,7 @@ pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_ editor.add_annotation(trait_name_ref.syntax(), placeholder); } + editor.add_mappings(factory.finish_with_mappings()); builder.add_file_edits(ctx.vfs_file_id(), editor); }, ); diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs index 44ab64d4f6f78..e91e444a32336 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/syntax_factory/constructors.rs @@ -1960,6 +1960,47 @@ impl SyntaxFactory { ast } + pub fn trait_( + &self, + is_unsafe: bool, + ident: &str, + generic_param_list: Option, + where_clause: Option, + assoc_items: ast::AssocItemList, + ) -> ast::Trait { + let ast = make::trait_( + is_unsafe, + ident, + generic_param_list.clone(), + where_clause.clone(), + assoc_items.clone(), + ) + .clone_for_update(); + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + if let Some(generic_param_list) = generic_param_list { + builder.map_node( + generic_param_list.syntax().clone(), + ast.generic_param_list().unwrap().syntax().clone(), + ); + } + if let Some(where_clause) = where_clause { + builder.map_node( + where_clause.syntax().clone(), + ast.where_clause().unwrap().syntax().clone(), + ); + } + builder.map_node( + assoc_items.syntax().clone(), + ast.assoc_item_list().unwrap().syntax().clone(), + ); + builder.finish(&mut mapping); + } + + ast + } + pub fn ret_type(&self, ty: ast::Type) -> ast::RetType { let ast = make::ret_type(ty.clone()).clone_for_update(); From da404e417b42d9d1ad834234c20f450134d2441e Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Wed, 8 Apr 2026 13:02:10 +0530 Subject: [PATCH 121/144] add whitespace heuristics same as ted to SyntaxEditor via insert_with_whitespace and insert_all_with_whitespace --- .../crates/syntax/src/syntax_editor.rs | 95 ++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs index dbb9f15e173e2..d68957c501177 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs @@ -14,7 +14,10 @@ use std::{ use rowan::TextRange; use rustc_hash::FxHashMap; -use crate::{AstNode, SyntaxElement, SyntaxNode, SyntaxToken}; +use crate::{ + AstNode, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, T, + ast::{self, edit::IndentLevel, make}, +}; mod edit_algo; mod edits; @@ -101,6 +104,28 @@ impl SyntaxEditor { self.changes.push(Change::InsertAll(position, elements)) } + pub fn insert_with_whitespace(&mut self, position: Position, element: impl Element) { + self.insert_all_with_whitespace(position, vec![element.syntax_element()]) + } + + pub fn insert_all_with_whitespace( + &mut self, + position: Position, + mut elements: Vec, + ) { + if let Some(first) = elements.first() + && let Some(ws) = ws_before(&position, first) + { + elements.insert(0, ws.into()); + } + if let Some(last) = elements.last() + && let Some(ws) = ws_after(&position, last) + { + elements.push(ws.into()); + } + self.insert_all(position, elements) + } + pub fn delete(&mut self, element: impl Element) { let element = element.syntax_element(); debug_assert!(is_ancestor_or_self_of_element(&element, &self.root)); @@ -412,6 +437,74 @@ impl Element for SyntaxToken { } } +fn ws_before(position: &Position, new: &SyntaxElement) -> Option { + let prev = match &position.repr { + PositionRepr::FirstChild(_) => return None, + PositionRepr::After(it) => it, + }; + + if prev.kind() == T!['{'] + && new.kind() == SyntaxKind::USE + && let Some(item_list) = prev.parent().and_then(ast::ItemList::cast) + { + let mut indent = IndentLevel::from_element(&item_list.syntax().clone().into()); + indent.0 += 1; + return Some(make::tokens::whitespace(&format!("\n{indent}"))); + } + + if prev.kind() == T!['{'] + && ast::Stmt::can_cast(new.kind()) + && let Some(stmt_list) = prev.parent().and_then(ast::StmtList::cast) + { + let mut indent = IndentLevel::from_element(&stmt_list.syntax().clone().into()); + indent.0 += 1; + return Some(make::tokens::whitespace(&format!("\n{indent}"))); + } + + ws_between(prev, new) +} + +fn ws_after(position: &Position, new: &SyntaxElement) -> Option { + let next = match &position.repr { + PositionRepr::FirstChild(parent) => parent.first_child_or_token()?, + PositionRepr::After(sibling) => sibling.next_sibling_or_token()?, + }; + ws_between(new, &next) +} + +fn ws_between(left: &SyntaxElement, right: &SyntaxElement) -> Option { + if left.kind() == SyntaxKind::WHITESPACE || right.kind() == SyntaxKind::WHITESPACE { + return None; + } + if right.kind() == T![;] || right.kind() == T![,] { + return None; + } + if left.kind() == T![<] || right.kind() == T![>] { + return None; + } + if left.kind() == T![&] && right.kind() == SyntaxKind::LIFETIME { + return None; + } + if right.kind() == SyntaxKind::GENERIC_ARG_LIST { + return None; + } + if right.kind() == SyntaxKind::USE { + let mut indent = IndentLevel::from_element(left); + if left.kind() == SyntaxKind::USE { + indent.0 = IndentLevel::from_element(right).0.max(indent.0); + } + return Some(make::tokens::whitespace(&format!("\n{indent}"))); + } + if left.kind() == SyntaxKind::ATTR { + let mut indent = IndentLevel::from_element(right); + if right.kind() == SyntaxKind::ATTR { + indent.0 = IndentLevel::from_element(left).0.max(indent.0); + } + return Some(make::tokens::whitespace(&format!("\n{indent}"))); + } + Some(make::tokens::single_space()) +} + fn is_ancestor_or_self(node: &SyntaxNode, ancestor: &SyntaxNode) -> bool { node == ancestor || node.ancestors().any(|it| &it == ancestor) } From 8c79284dc9d6cb70749ae7157d11cf7497a69f0c Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Wed, 8 Apr 2026 13:02:44 +0530 Subject: [PATCH 122/144] update insert_use_with_editor_ to make use of whitespace heuristics --- src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use.rs b/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use.rs index 3a109a48e4892..133d9a9305f96 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use.rs @@ -709,7 +709,7 @@ fn insert_use_with_editor_( Some(b) => { cov_mark::hit!(insert_empty_module); syntax_editor.insert(Position::after(&b), syntax_factory.whitespace("\n")); - syntax_editor.insert(Position::after(&b), use_item.syntax()); + syntax_editor.insert_with_whitespace(Position::after(&b), use_item.syntax()); } None => { cov_mark::hit!(insert_empty_file); From c42deabcb60a8b6ea430bb0365e4b70e026114a7 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Wed, 8 Apr 2026 13:03:26 +0530 Subject: [PATCH 123/144] migrate extract_struct_from_enum_variant to SyntaxEditor and SyntaxFactory --- .../extract_struct_from_enum_variant.rs | 252 +++++++++++------- 1 file changed, 153 insertions(+), 99 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs index 4c46a51bef582..459e80eae2f32 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs @@ -6,7 +6,7 @@ use ide_db::{ FxHashSet, RootDatabase, defs::Definition, helpers::mod_path_to_ast, - imports::insert_use::{ImportScope, InsertUseConfig, insert_use}, + imports::insert_use::{ImportScope, InsertUseConfig, insert_use_with_editor}, path_transform::PathTransform, search::FileReference, }; @@ -16,12 +16,14 @@ use syntax::{ SyntaxKind::*, SyntaxNode, T, ast::{ - self, AstNode, HasAttrs, HasGenericParams, HasName, HasVisibility, edit::AstNodeEdit, make, + self, AstNode, HasAttrs, HasGenericParams, HasName, HasVisibility, edit::AstNodeEdit, + syntax_factory::SyntaxFactory, }, - match_ast, ted, + match_ast, + syntax_editor::{Position, SyntaxEditor}, }; -use crate::{AssistContext, AssistId, Assists, assist_context::SourceChangeBuilder}; +use crate::{AssistContext, AssistId, Assists}; // Assist: extract_struct_from_enum_variant // @@ -58,6 +60,8 @@ pub(crate) fn extract_struct_from_enum_variant( "Extract struct from enum variant", target, |builder| { + let make = SyntaxFactory::with_mappings(); + let mut editor = builder.make_editor(variant.syntax()); let edition = enum_hir.krate(ctx.db()).edition(ctx.db()); let variant_hir_name = variant_hir.name(ctx.db()); let enum_module_def = ModuleDef::from(enum_hir); @@ -73,40 +77,56 @@ pub(crate) fn extract_struct_from_enum_variant( def_file_references = Some(references); continue; } - builder.edit_file(file_id.file_id(ctx.db())); let processed = process_references( ctx, - builder, &mut visited_modules_set, &enum_module_def, &variant_hir_name, references, ); + if processed.is_empty() { + continue; + } + let mut file_editor = builder.make_editor(processed[0].0.syntax()); processed.into_iter().for_each(|(path, node, import)| { - apply_references(ctx.config.insert_use, path, node, import, edition) + apply_references( + ctx.config.insert_use, + path, + node, + import, + edition, + &mut file_editor, + &make, + ) }); + file_editor.add_mappings(make.take()); + builder.add_file_edits(file_id.file_id(ctx.db()), file_editor); } - builder.edit_file(ctx.vfs_file_id()); - let variant = builder.make_mut(variant.clone()); if let Some(references) = def_file_references { let processed = process_references( ctx, - builder, &mut visited_modules_set, &enum_module_def, &variant_hir_name, references, ); processed.into_iter().for_each(|(path, node, import)| { - apply_references(ctx.config.insert_use, path, node, import, edition) + apply_references( + ctx.config.insert_use, + path, + node, + import, + edition, + &mut editor, + &make, + ) }); } - let generic_params = enum_ast - .generic_param_list() - .and_then(|known_generics| extract_generic_params(&known_generics, &field_list)); - let generics = generic_params.as_ref().map(|generics| generics.clone_for_update()); + let generic_params = enum_ast.generic_param_list().and_then(|known_generics| { + extract_generic_params(&make, &known_generics, &field_list) + }); // resolve GenericArg in field_list to actual type let field_list = if let Some((target_scope, source_scope)) = @@ -124,25 +144,37 @@ pub(crate) fn extract_struct_from_enum_variant( } } } else { - field_list.clone_for_update() + field_list.clone() }; - let def = - create_struct_def(variant_name.clone(), &variant, &field_list, generics, &enum_ast); + let (comments_for_struct, comments_to_delete) = + collect_variant_comments(&make, variant.syntax()); + for element in &comments_to_delete { + editor.delete(element.clone()); + } + + let def = create_struct_def( + &make, + variant_name.clone(), + &field_list, + generic_params.clone(), + &enum_ast, + comments_for_struct, + ); let enum_ast = variant.parent_enum(); let indent = enum_ast.indent_level(); let def = def.indent(indent); - ted::insert_all( - ted::Position::before(enum_ast.syntax()), - vec![ - def.syntax().clone().into(), - make::tokens::whitespace(&format!("\n\n{indent}")).into(), - ], + editor.insert_all( + Position::before(enum_ast.syntax()), + vec![def.syntax().clone().into(), make.whitespace(&format!("\n\n{indent}")).into()], ); - update_variant(&variant, generic_params.map(|g| g.clone_for_update())); + update_variant(&make, &mut editor, &variant, generic_params); + + editor.add_mappings(make.finish_with_mappings()); + builder.add_file_edits(ctx.vfs_file_id(), editor); }, ) } @@ -184,6 +216,7 @@ fn existing_definition(db: &RootDatabase, variant_name: &ast::Name, variant: &En } fn extract_generic_params( + make: &SyntaxFactory, known_generics: &ast::GenericParamList, field_list: &Either, ) -> Option { @@ -201,7 +234,7 @@ fn extract_generic_params( }; let generics = generics.into_iter().filter_map(|(param, tag)| tag.then_some(param)); - tagged_one.then(|| make::generic_param_list(generics)) + tagged_one.then(|| make.generic_param_list(generics)) } fn tag_generics_in_variant(ty: &ast::Type, generics: &mut [(ast::GenericParam, bool)]) -> bool { @@ -250,82 +283,94 @@ fn tag_generics_in_variant(ty: &ast::Type, generics: &mut [(ast::GenericParam, b } fn create_struct_def( + make: &SyntaxFactory, name: ast::Name, - variant: &ast::Variant, field_list: &Either, generics: Option, enum_: &ast::Enum, + comments: Vec, ) -> ast::Struct { let enum_vis = enum_.visibility(); - let insert_vis = |node: &'_ SyntaxNode, vis: &'_ SyntaxNode| { - let vis = vis.clone_for_update(); - ted::insert(ted::Position::before(node), vis); - }; - // for fields without any existing visibility, use visibility of enum let field_list: ast::FieldList = match field_list { Either::Left(field_list) => { if let Some(vis) = &enum_vis { - field_list - .fields() - .filter(|field| field.visibility().is_none()) - .filter_map(|field| field.name()) - .for_each(|it| insert_vis(it.syntax(), vis.syntax())); + let (mut fl_editor, new_fl) = SyntaxEditor::with_ast_node(field_list); + for field in new_fl.fields() { + if field.visibility().is_none() + && let Some(field_name) = field.name() + { + fl_editor.insert_all( + Position::before(field_name.syntax()), + vec![vis.syntax().clone().into(), make.whitespace(" ").into()], + ); + } + } + let new_fl = fl_editor.finish().new_root().clone(); + ast::RecordFieldList::cast(new_fl).unwrap().into() + } else { + field_list.clone().into() } - - field_list.clone().into() } Either::Right(field_list) => { if let Some(vis) = &enum_vis { - field_list - .fields() - .filter(|field| field.visibility().is_none()) - .filter_map(|field| field.ty()) - .for_each(|it| insert_vis(it.syntax(), vis.syntax())); + let (mut fl_editor, new_fl) = SyntaxEditor::with_ast_node(field_list); + for field in new_fl.fields() { + if field.visibility().is_none() + && let Some(ty) = field.ty() + { + fl_editor.insert_all( + Position::before(ty.syntax()), + vec![vis.syntax().clone().into(), make.whitespace(" ").into()], + ); + } + } + let new_fl = fl_editor.finish().new_root().clone(); + ast::TupleFieldList::cast(new_fl).unwrap().into() + } else { + field_list.clone().into() } - - field_list.clone().into() } }; - let strukt = make::struct_(enum_vis, name, generics, field_list).clone_for_update(); - - // take comments from variant - ted::insert_all( - ted::Position::first_child_of(strukt.syntax()), - take_all_comments(variant.syntax()), - ); - - // copy attributes from enum - ted::insert_all( - ted::Position::first_child_of(strukt.syntax()), - enum_ - .attrs() - .flat_map(|it| { - vec![it.syntax().clone_for_update().into(), make::tokens::single_newline().into()] - }) - .collect(), - ); - - strukt + let strukt = make.struct_(enum_vis, name, generics, field_list); + let mut items_to_prepend: Vec = Vec::new(); + for attr in enum_.attrs() { + items_to_prepend.push(attr.syntax().clone().into()); + items_to_prepend.push(make.whitespace("\n").into()); + } + items_to_prepend.extend(comments); + + if !items_to_prepend.is_empty() { + let (mut strukt_editor, strukt_root) = SyntaxEditor::with_ast_node(&strukt); + strukt_editor.insert_all(Position::first_child_of(strukt_root.syntax()), items_to_prepend); + ast::Struct::cast(strukt_editor.finish().new_root().clone()).unwrap() + } else { + strukt + } } -fn update_variant(variant: &ast::Variant, generics: Option) -> Option<()> { +fn update_variant( + make: &SyntaxFactory, + editor: &mut SyntaxEditor, + variant: &ast::Variant, + generics: Option, +) -> Option<()> { let name = variant.name()?; let generic_args = generics .filter(|generics| generics.generic_params().count() > 0) .map(|generics| generics.to_generic_args()); // FIXME: replace with a `ast::make` constructor let ty = match generic_args { - Some(generic_args) => make::ty(&format!("{name}{generic_args}")), - None => make::ty(&name.text()), + Some(generic_args) => make.ty(&format!("{name}{generic_args}")), + None => make.ty(&name.text()), }; // change from a record to a tuple field list - let tuple_field = make::tuple_field(None, ty); - let field_list = make::tuple_field_list(iter::once(tuple_field)).clone_for_update(); - ted::replace(variant.field_list()?.syntax(), field_list.syntax()); + let tuple_field = make.tuple_field(None, ty); + let field_list = make.tuple_field_list(iter::once(tuple_field)); + editor.replace(variant.field_list()?.syntax(), field_list.syntax()); // remove any ws after the name if let Some(ws) = name @@ -333,35 +378,39 @@ fn update_variant(variant: &ast::Variant, generics: Option Vec { - let mut remove_next_ws = false; - node.children_with_tokens() - .filter_map(move |child| match child.kind() { +fn collect_variant_comments( + make: &SyntaxFactory, + node: &SyntaxNode, +) -> (Vec, Vec) { + let mut to_insert: Vec = Vec::new(); + let mut to_delete: Vec = Vec::new(); + let mut after_comment = false; + + for child in node.children_with_tokens() { + match child.kind() { COMMENT => { - remove_next_ws = true; - child.detach(); - Some(child) + after_comment = true; + to_insert.push(child.clone()); + to_delete.push(child); } - WHITESPACE if remove_next_ws => { - remove_next_ws = false; - child.detach(); - Some(make::tokens::single_newline().into()) + WHITESPACE if after_comment => { + after_comment = false; + to_insert.push(make.whitespace("\n").into()); + to_delete.push(child); } _ => { - remove_next_ws = false; - None + after_comment = false; } - }) - .collect() + } + } + + (to_insert, to_delete) } fn apply_references( @@ -370,20 +419,27 @@ fn apply_references( node: SyntaxNode, import: Option<(ImportScope, hir::ModPath)>, edition: Edition, + editor: &mut SyntaxEditor, + make: &SyntaxFactory, ) { if let Some((scope, path)) = import { - insert_use(&scope, mod_path_to_ast(&path, edition), &insert_use_cfg); + insert_use_with_editor( + &scope, + mod_path_to_ast(&path, edition), + &insert_use_cfg, + editor, + make, + ); } // deep clone to prevent cycle - let path = make::path_from_segments(iter::once(segment.clone_subtree()), false); - ted::insert_raw(ted::Position::before(segment.syntax()), path.clone_for_update().syntax()); - ted::insert_raw(ted::Position::before(segment.syntax()), make::token(T!['('])); - ted::insert_raw(ted::Position::after(&node), make::token(T![')'])); + let path = make.path_from_segments(iter::once(segment.clone()), false); + editor.insert(Position::before(segment.syntax()), make.token(T!['('])); + editor.insert(Position::before(segment.syntax()), path.syntax()); + editor.insert(Position::after(&node), make.token(T![')'])); } fn process_references( ctx: &AssistContext<'_>, - builder: &mut SourceChangeBuilder, visited_modules: &mut FxHashSet, enum_module_def: &ModuleDef, variant_hir_name: &Name, @@ -394,8 +450,6 @@ fn process_references( refs.into_iter() .flat_map(|reference| { let (segment, scope_node, module) = reference_to_node(&ctx.sema, reference)?; - let segment = builder.make_mut(segment); - let scope_node = builder.make_syntax_mut(scope_node); if !visited_modules.contains(&module) { let cfg = ctx.config.find_path_config(ctx.sema.is_nightly(module.krate(ctx.sema.db))); From dc8535b5c319d179237124de070176f2877f1f76 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Wed, 8 Apr 2026 13:44:12 +0530 Subject: [PATCH 124/144] lets use syntaxFactory in migration as well --- .../crates/ide-db/src/imports/insert_use.rs | 6 ++- .../crates/syntax/src/syntax_editor.rs | 48 +++++++++++++------ 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use.rs b/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use.rs index 133d9a9305f96..41ce1e59603d7 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/imports/insert_use.rs @@ -709,7 +709,11 @@ fn insert_use_with_editor_( Some(b) => { cov_mark::hit!(insert_empty_module); syntax_editor.insert(Position::after(&b), syntax_factory.whitespace("\n")); - syntax_editor.insert_with_whitespace(Position::after(&b), use_item.syntax()); + syntax_editor.insert_with_whitespace( + Position::after(&b), + use_item.syntax(), + syntax_factory, + ); } None => { cov_mark::hit!(insert_empty_file); diff --git a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs index d68957c501177..8e4dc75d22194 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs @@ -16,7 +16,7 @@ use rustc_hash::FxHashMap; use crate::{ AstNode, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, T, - ast::{self, edit::IndentLevel, make}, + ast::{self, edit::IndentLevel, syntax_factory::SyntaxFactory}, }; mod edit_algo; @@ -104,22 +104,28 @@ impl SyntaxEditor { self.changes.push(Change::InsertAll(position, elements)) } - pub fn insert_with_whitespace(&mut self, position: Position, element: impl Element) { - self.insert_all_with_whitespace(position, vec![element.syntax_element()]) + pub fn insert_with_whitespace( + &mut self, + position: Position, + element: impl Element, + factory: &SyntaxFactory, + ) { + self.insert_all_with_whitespace(position, vec![element.syntax_element()], factory) } pub fn insert_all_with_whitespace( &mut self, position: Position, mut elements: Vec, + factory: &SyntaxFactory, ) { if let Some(first) = elements.first() - && let Some(ws) = ws_before(&position, first) + && let Some(ws) = ws_before(&position, first, factory) { elements.insert(0, ws.into()); } if let Some(last) = elements.last() - && let Some(ws) = ws_after(&position, last) + && let Some(ws) = ws_after(&position, last, factory) { elements.push(ws.into()); } @@ -437,7 +443,11 @@ impl Element for SyntaxToken { } } -fn ws_before(position: &Position, new: &SyntaxElement) -> Option { +fn ws_before( + position: &Position, + new: &SyntaxElement, + factory: &SyntaxFactory, +) -> Option { let prev = match &position.repr { PositionRepr::FirstChild(_) => return None, PositionRepr::After(it) => it, @@ -449,7 +459,7 @@ fn ws_before(position: &Position, new: &SyntaxElement) -> Option { { let mut indent = IndentLevel::from_element(&item_list.syntax().clone().into()); indent.0 += 1; - return Some(make::tokens::whitespace(&format!("\n{indent}"))); + return Some(factory.whitespace(&format!("\n{indent}"))); } if prev.kind() == T!['{'] @@ -458,21 +468,29 @@ fn ws_before(position: &Position, new: &SyntaxElement) -> Option { { let mut indent = IndentLevel::from_element(&stmt_list.syntax().clone().into()); indent.0 += 1; - return Some(make::tokens::whitespace(&format!("\n{indent}"))); + return Some(factory.whitespace(&format!("\n{indent}"))); } - ws_between(prev, new) + ws_between(prev, new, factory) } -fn ws_after(position: &Position, new: &SyntaxElement) -> Option { +fn ws_after( + position: &Position, + new: &SyntaxElement, + factory: &SyntaxFactory, +) -> Option { let next = match &position.repr { PositionRepr::FirstChild(parent) => parent.first_child_or_token()?, PositionRepr::After(sibling) => sibling.next_sibling_or_token()?, }; - ws_between(new, &next) + ws_between(new, &next, factory) } -fn ws_between(left: &SyntaxElement, right: &SyntaxElement) -> Option { +fn ws_between( + left: &SyntaxElement, + right: &SyntaxElement, + factory: &SyntaxFactory, +) -> Option { if left.kind() == SyntaxKind::WHITESPACE || right.kind() == SyntaxKind::WHITESPACE { return None; } @@ -493,16 +511,16 @@ fn ws_between(left: &SyntaxElement, right: &SyntaxElement) -> Option bool { From 6cff92d2381966f6748bfa510cf00d9a881fee40 Mon Sep 17 00:00:00 2001 From: smihica Date: Wed, 8 Apr 2026 08:33:48 +0000 Subject: [PATCH 125/144] Use RA_TEST_ prefix in env test to avoid collisions with process env --- .../crates/project-model/src/env.rs | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/tools/rust-analyzer/crates/project-model/src/env.rs b/src/tools/rust-analyzer/crates/project-model/src/env.rs index 51c447945cf4b..83752151362ea 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/env.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/env.rs @@ -124,38 +124,38 @@ fn parse_output_cargo_config_env_works() { .unwrap(); let config_path = cwd.join(".cargo").join("config.toml"); let raw = r#" -env.CARGO_WORKSPACE_DIR.relative = true -env.CARGO_WORKSPACE_DIR.value = "" -env.INVALID.relative = "invalidbool" -env.INVALID.value = "../relative" -env.RELATIVE.relative = true -env.RELATIVE.value = "../relative" -env.TEST.value = "test" -env.FORCED.value = "test" -env.FORCED.force = true -env.UNFORCED.value = "test" -env.UNFORCED.forced = false -env.OVERWRITTEN.value = "test" -env.NOT_AN_OBJECT = "value" +env.RA_TEST_WORKSPACE_DIR.relative = true +env.RA_TEST_WORKSPACE_DIR.value = "" +env.RA_TEST_INVALID.relative = "invalidbool" +env.RA_TEST_INVALID.value = "../relative" +env.RA_TEST_RELATIVE.relative = true +env.RA_TEST_RELATIVE.value = "../relative" +env.RA_TEST_UNSET.value = "test" +env.RA_TEST_FORCED.value = "test" +env.RA_TEST_FORCED.force = true +env.RA_TEST_UNFORCED.value = "test" +env.RA_TEST_UNFORCED.forced = false +env.RA_TEST_OVERWRITTEN.value = "test" +env.RA_TEST_NOT_AN_OBJECT = "value" "#; let raw = raw.lines().map(|l| format!("{l} # {config_path}")).join("\n"); let config = CargoConfigFile::from_string_for_test(raw); let extra_env = [ - ("FORCED", Some("ignored")), - ("UNFORCED", Some("newvalue")), - ("OVERWRITTEN", Some("newvalue")), - ("TEST", None), + ("RA_TEST_FORCED", Some("ignored")), + ("RA_TEST_UNFORCED", Some("newvalue")), + ("RA_TEST_OVERWRITTEN", Some("newvalue")), + ("RA_TEST_UNSET", None), ] .iter() .map(|(k, v)| (k.to_string(), v.map(ToString::to_string))) .collect(); let env = cargo_config_env(&Some(config), &extra_env); - assert_eq!(env.get("CARGO_WORKSPACE_DIR").as_deref(), Some(cwd.join("").as_str())); - assert_eq!(env.get("RELATIVE").as_deref(), Some(cwd.join("../relative").as_str())); - assert_eq!(env.get("INVALID").as_deref(), Some("../relative")); - assert_eq!(env.get("TEST").as_deref(), Some("test")); - assert_eq!(env.get("FORCED").as_deref(), Some("test")); - assert_eq!(env.get("UNFORCED").as_deref(), Some("newvalue")); - assert_eq!(env.get("OVERWRITTEN").as_deref(), Some("newvalue")); - assert_eq!(env.get("NOT_AN_OBJECT").as_deref(), Some("value")); + assert_eq!(env.get("RA_TEST_WORKSPACE_DIR").as_deref(), Some(cwd.join("").as_str())); + assert_eq!(env.get("RA_TEST_RELATIVE").as_deref(), Some(cwd.join("../relative").as_str())); + assert_eq!(env.get("RA_TEST_INVALID").as_deref(), Some("../relative")); + assert_eq!(env.get("RA_TEST_UNSET").as_deref(), Some("test")); + assert_eq!(env.get("RA_TEST_FORCED").as_deref(), Some("test")); + assert_eq!(env.get("RA_TEST_UNFORCED").as_deref(), Some("newvalue")); + assert_eq!(env.get("RA_TEST_OVERWRITTEN").as_deref(), Some("newvalue")); + assert_eq!(env.get("RA_TEST_NOT_AN_OBJECT").as_deref(), Some("value")); } From f573369d78d47bc2ad7d0a2e343377a7e08d3377 Mon Sep 17 00:00:00 2001 From: smihica Date: Wed, 8 Apr 2026 08:34:22 +0000 Subject: [PATCH 126/144] Fix [env] in .cargo/config.toml overriding process environment variables cargo_config_env() only checked extra_env (rust-analyzer.cargo.extraEnv) when deciding whether to skip an existing variable, but never checked the actual process environment via std::env::var. This caused config.toml values to unconditionally override real environment variables even without force = true, diverging from Cargo's behavior. Additionally, plain string entries (e.g. `KEY = "value"`) skipped the force check entirely. When a process env var takes precedence, its value is now inserted into the Env so that env!/option_env! resolution stays correct. Fixes rust-lang/rust-analyzer#21994 --- .../crates/project-model/src/env.rs | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/tools/rust-analyzer/crates/project-model/src/env.rs b/src/tools/rust-analyzer/crates/project-model/src/env.rs index 83752151362ea..1a660fbf5b915 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/env.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/env.rs @@ -79,18 +79,31 @@ pub(crate) fn cargo_config_env( for (key, entry) in env_toml { let key = key.as_ref().as_ref(); let value = match entry.as_ref() { - DeValue::String(s) => String::from(s.clone()), + DeValue::String(s) => { + // Plain string entries have no `force` option, so they should not + // override existing environment variables (matching Cargo behavior). + if extra_env.get(key).is_some_and(Option::is_some) { + continue; + } + if let Ok(val) = std::env::var(key) { val } else { String::from(s.clone()) } + } DeValue::Table(entry) => { // Each entry MUST have a `value` key. let Some(map) = entry.get("value").and_then(|v| v.as_ref().as_str()) else { continue; }; - // If the entry already exists in the environment AND the `force` key is not set to - // true, then don't overwrite the value. - if extra_env.get(key).is_some_and(Option::is_some) - && !entry.get("force").and_then(|v| v.as_ref().as_bool()).unwrap_or(false) - { - continue; + let is_forced = + entry.get("force").and_then(|v| v.as_ref().as_bool()).unwrap_or(false); + // If the entry already exists in the environment AND the `force` key is not set + // to true, use the existing value instead of the config value. + if !is_forced { + if extra_env.get(key).is_some_and(Option::is_some) { + continue; + } + if let Ok(val) = std::env::var(key) { + env.insert(key, val); + continue; + } } if let Some(base) = entry.get("relative").and_then(|v| { From a5ba10686915bf11ac53f981daca00bf21bb2bdd Mon Sep 17 00:00:00 2001 From: smihica Date: Wed, 8 Apr 2026 08:35:03 +0000 Subject: [PATCH 127/144] Add test for process env variable precedence over config.toml [env] --- .../crates/project-model/src/env.rs | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/tools/rust-analyzer/crates/project-model/src/env.rs b/src/tools/rust-analyzer/crates/project-model/src/env.rs index 1a660fbf5b915..ab45917a5663b 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/env.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/env.rs @@ -172,3 +172,45 @@ env.RA_TEST_NOT_AN_OBJECT = "value" assert_eq!(env.get("RA_TEST_OVERWRITTEN").as_deref(), Some("newvalue")); assert_eq!(env.get("RA_TEST_NOT_AN_OBJECT").as_deref(), Some("value")); } + +#[test] +fn cargo_config_env_respects_process_env() { + use itertools::Itertools; + + let cwd = paths::AbsPathBuf::try_from( + paths::Utf8PathBuf::try_from(std::env::current_dir().unwrap()).unwrap(), + ) + .unwrap(); + let config_path = cwd.join(".cargo").join("config.toml"); + + // SAFETY: this test is not run in parallel with other tests that depend on these env vars. + unsafe { + std::env::set_var("RA_TEST_PROCESS_ENV_STRING", "from_process"); + std::env::set_var("RA_TEST_PROCESS_ENV_TABLE", "from_process"); + std::env::set_var("RA_TEST_PROCESS_ENV_FORCED", "from_process"); + } + + let raw = r#" +env.RA_TEST_PROCESS_ENV_STRING = "from_config" +env.RA_TEST_PROCESS_ENV_TABLE.value = "from_config" +env.RA_TEST_PROCESS_ENV_FORCED.value = "from_config" +env.RA_TEST_PROCESS_ENV_FORCED.force = true +"#; + let raw = raw.lines().map(|l| format!("{l} # {config_path}")).join("\n"); + let config = CargoConfigFile::from_string_for_test(raw); + let extra_env = FxHashMap::default(); + let env = cargo_config_env(&Some(config), &extra_env); + + // Plain string form should use process env value, not config value + assert_eq!(env.get("RA_TEST_PROCESS_ENV_STRING").as_deref(), Some("from_process")); + // Table form without force should use process env value, not config value + assert_eq!(env.get("RA_TEST_PROCESS_ENV_TABLE").as_deref(), Some("from_process")); + // Table form with force=true should override process env + assert_eq!(env.get("RA_TEST_PROCESS_ENV_FORCED").as_deref(), Some("from_config")); + + unsafe { + std::env::remove_var("RA_TEST_PROCESS_ENV_STRING"); + std::env::remove_var("RA_TEST_PROCESS_ENV_TABLE"); + std::env::remove_var("RA_TEST_PROCESS_ENV_FORCED"); + } +} From 0e52dc38585cba9df351bdf667d80f43d62709bc Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Wed, 8 Apr 2026 14:53:34 +0530 Subject: [PATCH 128/144] add get_or_create_assoc_item_list editor variant --- .../crates/syntax/src/syntax_editor/edits.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edits.rs b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edits.rs index 8c842be49dc98..d741adb6e3449 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edits.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor/edits.rs @@ -229,6 +229,25 @@ impl ast::AssocItemList { } } +impl ast::Impl { + pub fn get_or_create_assoc_item_list_with_editor( + &self, + editor: &mut SyntaxEditor, + make: &SyntaxFactory, + ) -> ast::AssocItemList { + if let Some(list) = self.assoc_item_list() { + list + } else { + let list = make.assoc_item_list_empty(); + editor.insert_all( + Position::last_child_of(self.syntax()), + vec![make.whitespace(" ").into(), list.syntax().clone().into()], + ); + list + } + } +} + impl ast::VariantList { pub fn add_variant(&self, editor: &mut SyntaxEditor, variant: &ast::Variant) { let make = SyntaxFactory::without_mappings(); From 73037729cbc6e3fe193d7957a903cec124191898 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Wed, 8 Apr 2026 14:53:56 +0530 Subject: [PATCH 129/144] replace make with SyntaxEditor in generate_single_field_struct_from --- .../generate_single_field_struct_from.rs | 110 ++++++++++-------- 1 file changed, 60 insertions(+), 50 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_single_field_struct_from.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_single_field_struct_from.rs index d3022ceda379e..2fc2b9efe81f7 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_single_field_struct_from.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_single_field_struct_from.rs @@ -1,14 +1,16 @@ -use ast::make; use hir::next_solver::{DbInterner, TypingMode}; use hir::{HasCrate, ModuleDef, Semantics}; use ide_db::{ RootDatabase, famous_defs::FamousDefs, helpers::mod_path_to_ast, imports::import_assets::item_for_path_search, use_trivial_constructor::use_trivial_constructor, }; -use syntax::syntax_editor::{Element, Position}; +use syntax::syntax_editor::{Position, SyntaxEditor}; use syntax::{ TokenText, - ast::{self, AstNode, HasAttrs, HasGenericParams, HasName, edit::AstNodeEdit}, + ast::{ + self, AstNode, HasAttrs, HasGenericParams, HasName, edit::AstNodeEdit, + syntax_factory::SyntaxFactory, + }, }; use crate::{ @@ -78,47 +80,52 @@ pub(crate) fn generate_single_field_struct_from( "Generate single field `From`", strukt.syntax().text_range(), |builder| { + let make = SyntaxFactory::with_mappings(); + let mut editor = builder.make_editor(strukt.syntax()); + let indent = strukt.indent_level(); let ty_where_clause = strukt.where_clause(); let type_gen_params = strukt.generic_param_list(); let type_gen_args = type_gen_params.as_ref().map(|params| params.to_generic_args()); - let trait_gen_args = Some(make::generic_arg_list([ast::GenericArg::TypeArg( - make::type_arg(main_field_ty.clone()), - )])); + let trait_gen_args = Some(make.generic_arg_list( + [ast::GenericArg::TypeArg(make.type_arg(main_field_ty.clone()))], + false, + )); - let ty = make::ty(&strukt_name.text()); + let ty = make.ty(&strukt_name.text()); let constructor = - make_adt_constructor(names.as_deref(), constructors, &main_field_name); - let body = make::block_expr([], Some(constructor)); + make_adt_constructor(names.as_deref(), constructors, &main_field_name, &make); + let body = make.block_expr([], Some(constructor)); - let fn_ = make::fn_( - None, - None, - make::name("from"), - None, - None, - make::param_list( + let fn_ = make + .fn_( + [], None, - [make::param( - make::path_pat(make::path_from_text(&main_field_name)), - main_field_ty, - )], - ), - body, - Some(make::ret_type(make::ty("Self"))), - false, - false, - false, - false, - ) - .indent(1.into()); + make.name("from"), + None, + None, + make.param_list( + None, + [make.param( + make.path_pat(make.path_from_text(&main_field_name)), + main_field_ty, + )], + ), + body, + Some(make.ret_type(make.ty("Self"))), + false, + false, + false, + false, + ) + .indent_with_mapping(1.into(), &make); let cfg_attrs = strukt .attrs() .filter(|attr| attr.as_simple_call().is_some_and(|(name, _arg)| name == "cfg")); - let impl_ = make::impl_trait( + let impl_ = make.impl_trait( cfg_attrs, false, None, @@ -126,28 +133,31 @@ pub(crate) fn generate_single_field_struct_from( type_gen_params, type_gen_args, false, - make::ty("From"), + make.ty("From"), ty.clone(), None, ty_where_clause.map(|wc| wc.reset_indent()), None, - ) - .clone_for_update(); - - impl_.get_or_create_assoc_item_list().add_item(fn_.into()); - let impl_ = impl_.indent(indent); + ); - let mut edit = builder.make_editor(strukt.syntax()); + let (mut impl_editor, impl_root) = SyntaxEditor::with_ast_node(&impl_); + let assoc_list = + impl_root.get_or_create_assoc_item_list_with_editor(&mut impl_editor, &make); + assoc_list.add_items(&mut impl_editor, vec![fn_.into()]); + let impl_ = ast::Impl::cast(impl_editor.finish().new_root().clone()) + .unwrap() + .indent_with_mapping(indent, &make); - edit.insert_all( + editor.insert_all( Position::after(strukt.syntax()), vec![ - make::tokens::whitespace(&format!("\n\n{indent}")).syntax_element(), - impl_.syntax().syntax_element(), + make.whitespace(&format!("\n\n{indent}")).into(), + impl_.syntax().clone().into(), ], ); - builder.add_file_edits(ctx.vfs_file_id(), edit); + editor.add_mappings(make.finish_with_mappings()); + builder.add_file_edits(ctx.vfs_file_id(), editor); }, ) } @@ -156,19 +166,18 @@ fn make_adt_constructor( names: Option<&[ast::Name]>, constructors: Vec>, main_field_name: &TokenText<'_>, + make: &SyntaxFactory, ) -> ast::Expr { if let Some(names) = names { - let fields = make::record_expr_field_list(names.iter().zip(constructors).map( - |(name, initializer)| { - make::record_expr_field(make::name_ref(&name.text()), initializer) - }, + let fields = make.record_expr_field_list(names.iter().zip(constructors).map( + |(name, initializer)| make.record_expr_field(make.name_ref(&name.text()), initializer), )); - make::record_expr(make::path_from_text("Self"), fields).into() + make.record_expr(make.path_from_text("Self"), fields).into() } else { - let arg_list = make::arg_list(constructors.into_iter().map(|expr| { - expr.unwrap_or_else(|| make::expr_path(make::path_from_text(main_field_name))) + let arg_list = make.arg_list(constructors.into_iter().map(|expr| { + expr.unwrap_or_else(|| make.expr_path(make.path_from_text(main_field_name))) })); - make::expr_call(make::expr_path(make::path_from_text("Self")), arg_list).into() + make.expr_call(make.expr_path(make.path_from_text("Self")), arg_list).into() } } @@ -177,6 +186,7 @@ fn make_constructors( module: hir::Module, types: &[ast::Type], ) -> Vec> { + let make = SyntaxFactory::without_mappings(); let (db, sema) = (ctx.db(), &ctx.sema); let cfg = ctx.config.find_path_config(ctx.sema.is_nightly(module.krate(ctx.sema.db))); types @@ -184,7 +194,7 @@ fn make_constructors( .map(|ty| { let ty = sema.resolve_type(ty)?; if ty.is_unit() { - return Some(make::expr_tuple([]).into()); + return Some(make.expr_tuple([]).into()); } let item_in_ns = ModuleDef::Adt(ty.as_adt()?).into(); let edition = module.krate(db).edition(db); From e6f36c91ea5b86e575e24529920647a615062439 Mon Sep 17 00:00:00 2001 From: dybucc <149513579+dybucc@users.noreply.github.com> Date: Fri, 3 Apr 2026 18:51:35 +0200 Subject: [PATCH 130/144] internal: add workflow to handle generating lints --- .../.github/workflows/gen-lints.yml | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/tools/rust-analyzer/.github/workflows/gen-lints.yml diff --git a/src/tools/rust-analyzer/.github/workflows/gen-lints.yml b/src/tools/rust-analyzer/.github/workflows/gen-lints.yml new file mode 100644 index 0000000000000..7319b2b3263b9 --- /dev/null +++ b/src/tools/rust-analyzer/.github/workflows/gen-lints.yml @@ -0,0 +1,35 @@ +name: Generate lints and feature flags + +on: + workflow_dispatch: + schedule: + - cron: '50 23 * * 6' + +defaults: + run: + shell: bash + +jobs: + lints-gen: + name: Generate lints + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Install nightly + run: rustup default nightly + + - name: Generate lints/feature flags + run: cargo codegen lint-definitions + + - name: Submit PR + uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0 + with: + commit-message: "internal: update generated lints" + branch: "ci/gen-lints" + delete-branch: true + sign-commits: true + title: "Update generated lints" + body: "Weekly lint updates for `crates/ide-db/src/generated/lints.rs`." + labels: "A-infra" From 690d1938a70074eb8bb7186693d279da056bf37b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20K=C3=A5re=20Alsaker?= Date: Sat, 14 Mar 2026 09:30:03 +0100 Subject: [PATCH 131/144] Break a single query cycle in the deadlock handler --- compiler/rustc_interface/src/util.rs | 4 +- compiler/rustc_query_impl/src/job.rs | 91 ++++++++-------------------- compiler/rustc_query_impl/src/lib.rs | 2 +- 3 files changed, 27 insertions(+), 70 deletions(-) diff --git a/compiler/rustc_interface/src/util.rs b/compiler/rustc_interface/src/util.rs index 0cd0275f96bbb..24b23cc4199e9 100644 --- a/compiler/rustc_interface/src/util.rs +++ b/compiler/rustc_interface/src/util.rs @@ -184,7 +184,7 @@ pub(crate) fn run_in_thread_pool_with_globals< use rustc_data_structures::defer; use rustc_middle::ty::tls; - use rustc_query_impl::break_query_cycles; + use rustc_query_impl::break_query_cycle; let thread_stack_size = init_stack_size(thread_builder_diag); @@ -260,7 +260,7 @@ internal compiler error: query cycle handler thread panicked, aborting process"; ) }, ); - break_query_cycles(job_map, ®istry); + break_query_cycle(job_map, ®istry); }) }) }); diff --git a/compiler/rustc_query_impl/src/job.rs b/compiler/rustc_query_impl/src/job.rs index f8098da593718..2486c0abfde80 100644 --- a/compiler/rustc_query_impl/src/job.rs +++ b/compiler/rustc_query_impl/src/job.rs @@ -303,29 +303,17 @@ fn process_cycle<'tcx>(job_map: &QueryJobMap<'tcx>, stack: Vec<(Span, QueryJobId } } -/// Looks for a query cycle using the last query in `jobs`. -/// If a cycle is found, all queries in the cycle is removed from `jobs` and -/// the function return true. -/// If a cycle was not found, the starting query is removed from `jobs` and -/// the function returns false. -fn remove_cycle<'tcx>( +/// Looks for a query cycle starting at `query`. +/// Returns a waiter to resume if a cycle is found. +fn find_and_process_cycle<'tcx>( job_map: &QueryJobMap<'tcx>, - jobs: &mut Vec, - wakelist: &mut Vec>>, -) -> bool { + query: QueryJobId, +) -> Option>> { let mut visited = FxHashSet::default(); let mut stack = Vec::new(); - // Look for a cycle starting with the last query in `jobs` if let ControlFlow::Break(resumable) = - find_cycle(job_map, jobs.pop().unwrap(), DUMMY_SP, &mut stack, &mut visited) + find_cycle(job_map, query, DUMMY_SP, &mut stack, &mut visited) { - // Remove the queries in our cycle from the list of jobs to look at - for r in &stack { - if let Some(pos) = jobs.iter().position(|j| j == &r.1) { - jobs.remove(pos); - } - } - // Create the cycle error let error = process_cycle(job_map, stack); @@ -340,62 +328,31 @@ fn remove_cycle<'tcx>( *waiter.cycle.lock() = Some(error); // Put the waiter on the list of things to resume - wakelist.push(waiter); - - true + Some(waiter) } else { - false + None } } /// Detects query cycles by using depth first search over all active query jobs. /// If a query cycle is found it will break the cycle by finding an edge which /// uses a query latch and then resuming that waiter. -/// There may be multiple cycles involved in a deadlock, so this searches -/// all active queries for cycles before finally resuming all the waiters at once. -pub fn break_query_cycles<'tcx>( - job_map: QueryJobMap<'tcx>, - registry: &rustc_thread_pool::Registry, -) { - let mut wakelist = Vec::new(); - // It is OK per the comments: - // - https://github.com/rust-lang/rust/pull/131200#issuecomment-2798854932 - // - https://github.com/rust-lang/rust/pull/131200#issuecomment-2798866392 - #[allow(rustc::potential_query_instability)] - let mut jobs: Vec = job_map.map.keys().copied().collect(); - - let mut found_cycle = false; - - while jobs.len() > 0 { - if remove_cycle(&job_map, &mut jobs, &mut wakelist) { - found_cycle = true; - } - } - - // Check that a cycle was found. It is possible for a deadlock to occur without - // a query cycle if a query which can be waited on uses Rayon to do multithreading - // internally. Such a query (X) may be executing on 2 threads (A and B) and A may - // wait using Rayon on B. Rayon may then switch to executing another query (Y) - // which in turn will wait on X causing a deadlock. We have a false dependency from - // X to Y due to Rayon waiting and a true dependency from Y to X. The algorithm here - // only considers the true dependency and won't detect a cycle. - if !found_cycle { - panic!( - "deadlock detected as we're unable to find a query cycle to break\n\ - current query map:\n{job_map:#?}", - ); - } - - // Mark all the thread we're about to wake up as unblocked. This needs to be done before - // we wake the threads up as otherwise Rayon could detect a deadlock if a thread we - // resumed fell asleep and this thread had yet to mark the remaining threads as unblocked. - for _ in 0..wakelist.len() { - rustc_thread_pool::mark_unblocked(registry); - } - - for waiter in wakelist.into_iter() { - waiter.condvar.notify_one(); - } +/// +/// There may be multiple cycles involved in a deadlock, but this only breaks one at a time so +/// there will be multiple rounds through the deadlock handler if multiple cycles are present. +#[allow(rustc::potential_query_instability)] +pub fn break_query_cycle<'tcx>(job_map: QueryJobMap<'tcx>, registry: &rustc_thread_pool::Registry) { + // Look for a cycle starting at each query job + let waiter = job_map + .map + .keys() + .find_map(|query| find_and_process_cycle(&job_map, *query)) + .expect("unable to find a query cycle"); + + // Mark the thread we're about to wake up as unblocked. + rustc_thread_pool::mark_unblocked(registry); + + assert!(waiter.condvar.notify_one(), "unable to wake the waiter"); } pub fn print_query_stack<'tcx>( diff --git a/compiler/rustc_query_impl/src/lib.rs b/compiler/rustc_query_impl/src/lib.rs index 27bfe1451f64f..d13ecaa2dee5c 100644 --- a/compiler/rustc_query_impl/src/lib.rs +++ b/compiler/rustc_query_impl/src/lib.rs @@ -17,7 +17,7 @@ use rustc_middle::ty::TyCtxt; pub use crate::dep_kind_vtables::make_dep_kind_vtables; pub use crate::execution::{CollectActiveJobsKind, collect_active_query_jobs}; -pub use crate::job::{QueryJobMap, break_query_cycles, print_query_stack}; +pub use crate::job::{QueryJobMap, break_query_cycle, print_query_stack}; mod dep_kind_vtables; mod error; From f493cab3e3d99692bf4de0d28acab3a918472d2f Mon Sep 17 00:00:00 2001 From: Jacob Adam Date: Thu, 9 Apr 2026 09:35:54 +0100 Subject: [PATCH 132/144] Add a test for an LLVM crash "Vector elements must have same size" --- tests/ui/derives/clone-vector-element-size.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 tests/ui/derives/clone-vector-element-size.rs diff --git a/tests/ui/derives/clone-vector-element-size.rs b/tests/ui/derives/clone-vector-element-size.rs new file mode 100644 index 0000000000000..1f29657372452 --- /dev/null +++ b/tests/ui/derives/clone-vector-element-size.rs @@ -0,0 +1,17 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/104037. +//! LLVM used to hit an assertion "Vector elements must have same size" +//! when compiling derived Clone with MIR optimisation level of 3. + +//@ build-pass +//@ compile-flags: -Zmir-opt-level=3 -Copt-level=3 + +#[derive(Clone)] +pub struct Foo(Bar, u32); + +#[derive(Clone, Copy)] +pub struct Bar(u8, u8, u8); + +fn main() { + let foo: Vec = Vec::new(); + let _ = foo.clone(); +} From 12e847f75c57deb188c977a1254ad5252fb620e6 Mon Sep 17 00:00:00 2001 From: Jacob Adam Date: Thu, 9 Apr 2026 09:35:57 +0100 Subject: [PATCH 133/144] Add a test for a const evaluator ICE on a self-receiver type mismatch --- .../self-receiver-type-mismatch.rs | 24 +++++++++++++++++++ .../self-receiver-type-mismatch.stderr | 12 ++++++++++ 2 files changed, 36 insertions(+) create mode 100644 tests/ui/traits/const-traits/self-receiver-type-mismatch.rs create mode 100644 tests/ui/traits/const-traits/self-receiver-type-mismatch.stderr diff --git a/tests/ui/traits/const-traits/self-receiver-type-mismatch.rs b/tests/ui/traits/const-traits/self-receiver-type-mismatch.rs new file mode 100644 index 0000000000000..61f0dbe3a9751 --- /dev/null +++ b/tests/ui/traits/const-traits/self-receiver-type-mismatch.rs @@ -0,0 +1,24 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/112623. +//! The const evaluator used to ICE with an assertion failure on a size mismatch +//! when a trait impl changed the `self` receiver type from by-value to by-reference. + +#![feature(const_trait_impl)] + +const trait Func { + fn trigger(self) -> usize; +} + +struct Cls; + +impl const Func for Cls { + fn trigger(&self, a: usize) -> usize { + //~^ ERROR method `trigger` has 2 parameters but the declaration in trait `Func::trigger` has 1 + 0 + } +} + +enum Bug { + V(T), +} + +fn main() {} diff --git a/tests/ui/traits/const-traits/self-receiver-type-mismatch.stderr b/tests/ui/traits/const-traits/self-receiver-type-mismatch.stderr new file mode 100644 index 0000000000000..4fd65d38a40d5 --- /dev/null +++ b/tests/ui/traits/const-traits/self-receiver-type-mismatch.stderr @@ -0,0 +1,12 @@ +error[E0050]: method `trigger` has 2 parameters but the declaration in trait `Func::trigger` has 1 + --> $DIR/self-receiver-type-mismatch.rs:14:16 + | +LL | fn trigger(self) -> usize; + | ---- trait requires 1 parameter +... +LL | fn trigger(&self, a: usize) -> usize { + | ^^^^^^^^^^^^^^^ expected 1 parameter, found 2 + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0050`. From fc9f492049072489988d201dd2baed1b6c513925 Mon Sep 17 00:00:00 2001 From: Jacob Adam Date: Thu, 9 Apr 2026 09:36:00 +0100 Subject: [PATCH 134/144] Add a codegen test for a missed optimisation with spare niches --- .../enum/enum-array-index-spare-niche.rs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 tests/codegen-llvm/enum/enum-array-index-spare-niche.rs diff --git a/tests/codegen-llvm/enum/enum-array-index-spare-niche.rs b/tests/codegen-llvm/enum/enum-array-index-spare-niche.rs new file mode 100644 index 0000000000000..e758996a29e06 --- /dev/null +++ b/tests/codegen-llvm/enum/enum-array-index-spare-niche.rs @@ -0,0 +1,28 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/113899. +//! When indexing into an array of an enum type with spare niches, the compiler +//! used to emit a superfluous branch checking whether the loaded value was +//! a niche value. Every element in the array is a valid variant, so this check +//! is unnecessary and should be optimised away. + +//@ compile-flags: -Copt-level=3 +#![crate_type = "lib"] + +#[derive(Clone, Copy)] +pub enum Outer { + A([u8; 8]), + B([u8; 8]), +} + +pub struct Error(u8); + +// CHECK-LABEL: @test +#[no_mangle] +pub fn test(x: usize) -> Result { + // There should be exactly one comparison: the bounds check on `x`. + // There must be no second comparison checking the discriminant + // against the niche value used by `Option` (from `get()`). + // CHECK: icmp ult + // CHECK-NOT: icmp + // CHECK: ret void + [Outer::A([10; 8]), Outer::B([20; 8])].get(x).copied().ok_or(Error(5)) +} From d383daa8f8bffdf5524775ababba274bf942575b Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Tue, 7 Apr 2026 02:43:09 +0300 Subject: [PATCH 135/144] Disable the fix for missing-fields when the fields are private --- .../crates/hir/src/diagnostics.rs | 9 ++++-- .../src/handlers/missing_fields.rs | 29 ++++++++++++++++--- .../crates/ide-diagnostics/src/lib.rs | 6 ++++ 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs b/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs index 7f672a697c41e..555270bad8309 100644 --- a/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs @@ -282,7 +282,7 @@ pub struct MissingFields { pub file: HirFileId, pub field_list_parent: AstPtr>, pub field_list_parent_path: Option>, - pub missed_fields: Vec, + pub missed_fields: Vec<(Name, Field)>, } #[derive(Debug)] @@ -476,7 +476,12 @@ impl<'db> AnyDiagnostic<'db> { let variant_data = variant.fields(db); let missed_fields = missed_fields .into_iter() - .map(|idx| variant_data.fields()[idx].name.clone()) + .map(|idx| { + ( + variant_data.fields()[idx].name.clone(), + Field { parent: variant.into(), id: idx }, + ) + }) .collect(); let record = match record { diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_fields.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_fields.rs index efbd266714398..85368cc09f915 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_fields.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_fields.rs @@ -1,6 +1,6 @@ use either::Either; use hir::{ - AssocItem, FindPathConfig, HirDisplay, InFile, Type, + AssocItem, FindPathConfig, HasVisibility, HirDisplay, InFile, Type, db::{ExpandDatabase, HirDatabase}, sym, }; @@ -35,7 +35,7 @@ use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext, fix}; // ``` pub(crate) fn missing_fields(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Diagnostic { let mut message = String::from("missing structure fields:\n"); - for field in &d.missed_fields { + for (field, _) in &d.missed_fields { format_to!(message, "- {}\n", field.display(ctx.sema.db, ctx.edition)); } @@ -57,7 +57,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option, d: &hir::MissingFields) -> Option A { let v = loop {}; A { v } +} + "#, + ); + } + + #[test] + fn inaccessible_fields() { + check_no_fix( + r#" +mod foo { + pub struct Bar { baz: i32 } +} + +fn qux() { + foo::Bar {$0}; } "#, ); 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 519639db00a90..09c9f8eab0a0d 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs @@ -285,6 +285,12 @@ struct DiagnosticsContext<'a> { is_nightly: bool, } +impl<'a> DiagnosticsContext<'a> { + fn db(&self) -> &'a RootDatabase { + self.sema.db + } +} + /// Request parser level diagnostics for the given [`FileId`]. pub fn syntax_diagnostics( db: &RootDatabase, From 9f83ca012099d23072c82321e3147129bbd94a51 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Thu, 9 Apr 2026 16:23:19 +0530 Subject: [PATCH 136/144] remove create-struct-def subeditors we don't need them and can use make constructor directly --- .../extract_struct_from_enum_variant.rs | 64 ++++++++----------- 1 file changed, 26 insertions(+), 38 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs index 459e80eae2f32..3bbf9a0ad3a25 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs @@ -159,16 +159,24 @@ pub(crate) fn extract_struct_from_enum_variant( &field_list, generic_params.clone(), &enum_ast, - comments_for_struct, ); let enum_ast = variant.parent_enum(); let indent = enum_ast.indent_level(); let def = def.indent(indent); - editor.insert_all( + let mut insert_items: Vec = Vec::new(); + for attr in enum_ast.attrs() { + insert_items.push(attr.syntax().clone().into()); + insert_items.push(make.whitespace("\n").into()); + } + insert_items.extend(comments_for_struct); + insert_items.push(def.syntax().clone().into()); + insert_items.push(make.whitespace(&format!("\n\n{indent}")).into()); + editor.insert_all_with_whitespace( Position::before(enum_ast.syntax()), - vec![def.syntax().clone().into(), make.whitespace(&format!("\n\n{indent}")).into()], + insert_items, + &make, ); update_variant(&make, &mut editor, &variant, generic_params); @@ -288,7 +296,6 @@ fn create_struct_def( field_list: &Either, generics: Option, enum_: &ast::Enum, - comments: Vec, ) -> ast::Struct { let enum_vis = enum_.visibility(); @@ -296,59 +303,40 @@ fn create_struct_def( let field_list: ast::FieldList = match field_list { Either::Left(field_list) => { if let Some(vis) = &enum_vis { - let (mut fl_editor, new_fl) = SyntaxEditor::with_ast_node(field_list); - for field in new_fl.fields() { + let new_fields = field_list.fields().map(|field| { if field.visibility().is_none() - && let Some(field_name) = field.name() + && let Some(name) = field.name() + && let Some(ty) = field.ty() { - fl_editor.insert_all( - Position::before(field_name.syntax()), - vec![vis.syntax().clone().into(), make.whitespace(" ").into()], - ); + make.record_field(Some(vis.clone()), name, ty) + } else { + field } - } - let new_fl = fl_editor.finish().new_root().clone(); - ast::RecordFieldList::cast(new_fl).unwrap().into() + }); + make.record_field_list(new_fields).into() } else { field_list.clone().into() } } Either::Right(field_list) => { if let Some(vis) = &enum_vis { - let (mut fl_editor, new_fl) = SyntaxEditor::with_ast_node(field_list); - for field in new_fl.fields() { + let new_fields = field_list.fields().map(|field| { if field.visibility().is_none() && let Some(ty) = field.ty() { - fl_editor.insert_all( - Position::before(ty.syntax()), - vec![vis.syntax().clone().into(), make.whitespace(" ").into()], - ); + make.tuple_field(Some(vis.clone()), ty) + } else { + field } - } - let new_fl = fl_editor.finish().new_root().clone(); - ast::TupleFieldList::cast(new_fl).unwrap().into() + }); + make.tuple_field_list(new_fields).into() } else { field_list.clone().into() } } }; - let strukt = make.struct_(enum_vis, name, generics, field_list); - let mut items_to_prepend: Vec = Vec::new(); - for attr in enum_.attrs() { - items_to_prepend.push(attr.syntax().clone().into()); - items_to_prepend.push(make.whitespace("\n").into()); - } - items_to_prepend.extend(comments); - - if !items_to_prepend.is_empty() { - let (mut strukt_editor, strukt_root) = SyntaxEditor::with_ast_node(&strukt); - strukt_editor.insert_all(Position::first_child_of(strukt_root.syntax()), items_to_prepend); - ast::Struct::cast(strukt_editor.finish().new_root().clone()).unwrap() - } else { - strukt - } + make.struct_(enum_vis, name, generics, field_list) } fn update_variant( From 0fbab04fcf95c04ed30fc3173d5b4511890a90d2 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Tue, 10 Mar 2026 16:32:55 +0100 Subject: [PATCH 137/144] minor follow up to removing soft mode `#[unstable]` --- src/tools/lint-docs/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tools/lint-docs/src/lib.rs b/src/tools/lint-docs/src/lib.rs index bc38e931fe515..f7d487333e32b 100644 --- a/src/tools/lint-docs/src/lib.rs +++ b/src/tools/lint-docs/src/lib.rs @@ -332,7 +332,6 @@ impl<'a> LintExtractor<'a> { if matches!( lint.name.as_str(), "unused_features" // broken lint - | "soft_unstable" // cannot have a stable example ) { return Ok(()); } From 6cd5315f1d3d52ce6370ebb844034fc27bbd4059 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Thu, 9 Apr 2026 13:12:24 +0300 Subject: [PATCH 138/144] Implement `GenericTypeVisitable` for some types This is required for rust-analyzer. --- compiler/rustc_type_ir/src/binder.rs | 11 ++-- compiler/rustc_type_ir/src/const_kind.rs | 2 +- compiler/rustc_type_ir/src/generic_visit.rs | 62 ++++----------------- 3 files changed, 20 insertions(+), 55 deletions(-) diff --git a/compiler/rustc_type_ir/src/binder.rs b/compiler/rustc_type_ir/src/binder.rs index 0b0f0fd2f4249..76140e6a762fe 100644 --- a/compiler/rustc_type_ir/src/binder.rs +++ b/compiler/rustc_type_ir/src/binder.rs @@ -956,7 +956,7 @@ pub enum BoundVarIndexKind { /// identified by both a universe, as well as a name residing within that universe. Distinct bound /// regions/types/consts within the same universe simply have an unknown relationship to one #[derive_where(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash; I: Interner, T)] -#[derive(TypeVisitable_Generic, TypeFoldable_Generic)] +#[derive(TypeVisitable_Generic, TypeFoldable_Generic, GenericTypeVisitable)] #[cfg_attr( feature = "nightly", derive(Encodable_NoContext, Decodable_NoContext, HashStable_NoContext) @@ -995,7 +995,7 @@ where } #[derive_where(Clone, Copy, PartialEq, Eq, Hash; I: Interner)] -#[derive(Lift_Generic)] +#[derive(Lift_Generic, GenericTypeVisitable)] #[cfg_attr( feature = "nightly", derive(Encodable_NoContext, Decodable_NoContext, HashStable_NoContext) @@ -1058,7 +1058,7 @@ impl BoundRegionKind { } #[derive_where(Clone, Copy, PartialEq, Eq, Debug, Hash; I: Interner)] -#[derive(Lift_Generic)] +#[derive(Lift_Generic, GenericTypeVisitable)] #[cfg_attr( feature = "nightly", derive(Encodable_NoContext, Decodable_NoContext, HashStable_NoContext) @@ -1069,7 +1069,7 @@ pub enum BoundTyKind { } #[derive_where(Clone, Copy, PartialEq, Eq, Debug, Hash; I: Interner)] -#[derive(Lift_Generic)] +#[derive(Lift_Generic, GenericTypeVisitable)] #[cfg_attr( feature = "nightly", derive(Encodable_NoContext, Decodable_NoContext, HashStable_NoContext) @@ -1104,6 +1104,7 @@ impl BoundVariableKind { } #[derive_where(Clone, Copy, PartialEq, Eq, Hash; I: Interner)] +#[derive(GenericTypeVisitable)] #[cfg_attr( feature = "nightly", derive(Encodable_NoContext, HashStable_NoContext, Decodable_NoContext) @@ -1164,6 +1165,7 @@ impl PlaceholderRegion { } #[derive_where(Clone, Copy, PartialEq, Eq, Hash; I: Interner)] +#[derive(GenericTypeVisitable)] #[cfg_attr( feature = "nightly", derive(Encodable_NoContext, Decodable_NoContext, HashStable_NoContext) @@ -1229,6 +1231,7 @@ impl PlaceholderType { } #[derive_where(Clone, Copy, PartialEq, Debug, Eq, Hash; I: Interner)] +#[derive(GenericTypeVisitable)] #[cfg_attr( feature = "nightly", derive(Encodable_NoContext, Decodable_NoContext, HashStable_NoContext) diff --git a/compiler/rustc_type_ir/src/const_kind.rs b/compiler/rustc_type_ir/src/const_kind.rs index 2877364762f46..68c87dd0bbeb7 100644 --- a/compiler/rustc_type_ir/src/const_kind.rs +++ b/compiler/rustc_type_ir/src/const_kind.rs @@ -141,7 +141,7 @@ impl HashStable for InferConst { /// `ValTree` does not have this problem with representation, as it only contains integers or /// lists of (nested) `ty::Const`s (which may indirectly contain more `ValTree`s). #[derive_where(Clone, Copy, Debug, Hash, Eq, PartialEq; I: Interner)] -#[derive(TypeVisitable_Generic, TypeFoldable_Generic)] +#[derive(TypeVisitable_Generic, TypeFoldable_Generic, GenericTypeVisitable)] #[cfg_attr( feature = "nightly", derive(Decodable_NoContext, Encodable_NoContext, HashStable_NoContext) diff --git a/compiler/rustc_type_ir/src/generic_visit.rs b/compiler/rustc_type_ir/src/generic_visit.rs index ee55c63ab4779..543397ff155b4 100644 --- a/compiler/rustc_type_ir/src/generic_visit.rs +++ b/compiler/rustc_type_ir/src/generic_visit.rs @@ -1,45 +1,13 @@ -//! A visiting traversal mechanism for complex data structures that contain type -//! information. +//! Special visiting used by rust-analyzer only. //! -//! This is a read-only traversal of the data structure. +//! It is different from `TypeVisitable` in two ways: //! -//! This traversal has limited flexibility. Only a small number of "types of -//! interest" within the complex data structures can receive custom -//! visitation. These are the ones containing the most important type-related -//! information, such as `Ty`, `Predicate`, `Region`, and `Const`. -//! -//! There are three traits involved in each traversal. -//! - `GenericTypeVisitable`. This is implemented once for many types, including: -//! - Types of interest, for which the methods delegate to the visitor. -//! - All other types, including generic containers like `Vec` and `Option`. -//! It defines a "skeleton" of how they should be visited. -//! - `TypeSuperVisitable`. This is implemented only for recursive types of -//! interest, and defines the visiting "skeleton" for these types. (This -//! excludes `Region` because it is non-recursive, i.e. it never contains -//! other types of interest.) -//! - `CustomizableTypeVisitor`. This is implemented for each visitor. This defines how -//! types of interest are visited. -//! -//! This means each visit is a mixture of (a) generic visiting operations, and (b) -//! custom visit operations that are specific to the visitor. -//! - The `GenericTypeVisitable` impls handle most of the traversal, and call into -//! `CustomizableTypeVisitor` when they encounter a type of interest. -//! - A `CustomizableTypeVisitor` may call into another `GenericTypeVisitable` impl, because some of -//! the types of interest are recursive and can contain other types of interest. -//! - A `CustomizableTypeVisitor` may also call into a `TypeSuperVisitable` impl, because each -//! visitor might provide custom handling only for some types of interest, or -//! only for some variants of each type of interest, and then use default -//! traversal for the remaining cases. -//! -//! For example, if you have `struct S(Ty, U)` where `S: GenericTypeVisitable` and `U: -//! GenericTypeVisitable`, and an instance `s = S(ty, u)`, it would be visited like so: -//! ```text -//! s.generic_visit_with(visitor) calls -//! - ty.generic_visit_with(visitor) calls -//! - visitor.visit_ty(ty) may call -//! - ty.super_generic_visit_with(visitor) -//! - u.generic_visit_with(visitor) -//! ``` +//! - The visitor is a generic of the trait and not the method, allowing types to attach +//! special behavior to visitors (as long as they know it; we don't use this capability +//! in rustc crates, but rust-analyzer needs it). +//! - It **must visit** every field. This is why we don't have an attribute like `#[type_visitable(ignore)]` +//! for this visit. The reason for this is soundness: rust-analyzer uses this visit to +//! garbage collect types, so a missing field can mean a use after free use std::sync::Arc; @@ -53,16 +21,6 @@ use thin_vec::ThinVec; /// To implement this conveniently, use the derive macro located in /// `rustc_macros`. pub trait GenericTypeVisitable { - /// The entry point for visiting. To visit a value `t` with a visitor `v` - /// call: `t.generic_visit_with(v)`. - /// - /// For most types, this just traverses the value, calling `generic_visit_with` on - /// each field/element. - /// - /// For types of interest (such as `Ty`), the implementation of this method - /// that calls a visitor method specifically for that type (such as - /// `V::visit_ty`). This is where control transfers from `GenericTypeVisitable` to - /// `CustomizableTypeVisitor`. fn generic_visit_with(&self, visitor: &mut V); } @@ -216,6 +174,10 @@ macro_rules! trivial_impls { }; } +impl GenericTypeVisitable for std::marker::PhantomData { + fn generic_visit_with(&self, _visitor: &mut V) {} +} + trivial_impls!( (), rustc_ast_ir::Mutability, From 4bcf30515f60c5e02b7d857fd79788b3e01b9479 Mon Sep 17 00:00:00 2001 From: Georg Semmler Date: Sun, 9 Feb 2025 18:29:32 +0100 Subject: [PATCH 139/144] Introduce a `#[diagnostic::on_unknown_item]` attribute This PR introduces a `#[diagnostic::on_unknown_item]` attribute that allows crate authors to customize the error messages emitted by unresolved imports. The main usecase for this is using this attribute as part of a proc macro that expects a certain external module structure to exist or certain dependencies to be there. For me personally the motivating use-case are several derives in diesel, that expect to refer to a `tabe` module. That is done either implicitly (via the name of the type with the derive) or explicitly by the user. This attribute would allow us to improve the error message in both cases: * For the implicit case we could explicity call out our assumptions (turning the name into lower case, adding an `s` in the end) + point to the explicit variant as alternative * For the explicit variant we would add additional notes to tell the user why this is happening and what they should look for to fix the problem (be more explicit about certain diesel specific assumptions of the module structure) I assume that similar use-cases exist for other proc-macros as well, therefore I decided to put in the work implementing this new attribute. I would also assume that this is likely not useful for std-lib internal usage. --- compiler/rustc_ast_passes/src/feature_gate.rs | 2 +- .../src/attributes/diagnostic/mod.rs | 19 +++- .../attributes/diagnostic/on_unknown_item.rs | 70 ++++++++++++ compiler/rustc_attr_parsing/src/context.rs | 2 + compiler/rustc_attr_parsing/src/interface.rs | 10 +- .../src/deriving/generic/mod.rs | 2 +- .../src/proc_macro_harness.rs | 2 +- compiler/rustc_builtin_macros/src/test.rs | 2 +- .../rustc_builtin_macros/src/test_harness.rs | 2 +- compiler/rustc_expand/src/config.rs | 2 +- compiler/rustc_feature/src/builtin_attrs.rs | 1 + compiler/rustc_feature/src/unstable.rs | 2 + .../rustc_hir/src/attrs/data_structures.rs | 8 ++ .../rustc_hir/src/attrs/encode_cross_crate.rs | 1 + compiler/rustc_interface/src/passes.rs | 6 +- compiler/rustc_lint/src/builtin.rs | 2 +- compiler/rustc_lint/src/early/diagnostics.rs | 9 ++ compiler/rustc_lint/src/lints.rs | 18 +++ compiler/rustc_lint/src/nonstandard_style.rs | 2 +- compiler/rustc_lint_defs/src/lib.rs | 5 + compiler/rustc_passes/src/check_attr.rs | 21 ++++ .../rustc_passes/src/debugger_visualizer.rs | 2 +- .../rustc_resolve/src/build_reduced_graph.rs | 8 +- compiler/rustc_resolve/src/imports.rs | 85 +++++++++++++-- compiler/rustc_resolve/src/macros.rs | 22 +++- compiler/rustc_span/src/symbol.rs | 2 + .../on_unknown_item/incorrect-locations.rs | 52 +++++++++ .../incorrect-locations.stderr | 103 ++++++++++++++++++ .../incorrect_format_string.rs | 33 ++++++ .../incorrect_format_string.stderr | 96 ++++++++++++++++ .../on_unknown_item/malformed_attribute.rs | 19 ++++ .../malformed_attribute.stderr | 44 ++++++++ .../on_unknown_item/multiple_errors.rs | 48 ++++++++ .../on_unknown_item/multiple_errors.stderr | 43 ++++++++ .../on_unknown_item/unknown_import.rs | 17 +++ .../on_unknown_item/unknown_import.stderr | 13 +++ ...feature-gate-diagnostic-on-unknown-item.rs | 8 ++ ...ure-gate-diagnostic-on-unknown-item.stderr | 22 ++++ 38 files changed, 771 insertions(+), 34 deletions(-) create mode 100644 compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown_item.rs create mode 100644 tests/ui/diagnostic_namespace/on_unknown_item/incorrect-locations.rs create mode 100644 tests/ui/diagnostic_namespace/on_unknown_item/incorrect-locations.stderr create mode 100644 tests/ui/diagnostic_namespace/on_unknown_item/incorrect_format_string.rs create mode 100644 tests/ui/diagnostic_namespace/on_unknown_item/incorrect_format_string.stderr create mode 100644 tests/ui/diagnostic_namespace/on_unknown_item/malformed_attribute.rs create mode 100644 tests/ui/diagnostic_namespace/on_unknown_item/malformed_attribute.stderr create mode 100644 tests/ui/diagnostic_namespace/on_unknown_item/multiple_errors.rs create mode 100644 tests/ui/diagnostic_namespace/on_unknown_item/multiple_errors.stderr create mode 100644 tests/ui/diagnostic_namespace/on_unknown_item/unknown_import.rs create mode 100644 tests/ui/diagnostic_namespace/on_unknown_item/unknown_import.stderr create mode 100644 tests/ui/feature-gates/feature-gate-diagnostic-on-unknown-item.rs create mode 100644 tests/ui/feature-gates/feature-gate-diagnostic-on-unknown-item.stderr diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index 1b615b611258f..4e3310d3fb097 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -649,7 +649,7 @@ fn maybe_stage_features(sess: &Session, features: &Features, krate: &ast::Crate) AttributeParser::parse_limited( sess, &krate.attrs, - sym::feature, + &[sym::feature], DUMMY_SP, krate.id, Some(&features), diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs index e63baf77c0852..61fd3f0962488 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs @@ -24,6 +24,7 @@ pub(crate) mod do_not_recommend; pub(crate) mod on_const; pub(crate) mod on_move; pub(crate) mod on_unimplemented; +pub(crate) mod on_unknown_item; #[derive(Copy, Clone)] pub(crate) enum Mode { @@ -35,6 +36,8 @@ pub(crate) enum Mode { DiagnosticOnConst, /// `#[diagnostic::on_move]` DiagnosticOnMove, + /// `#[diagnostic::on_unknown_item]` + DiagnosticOnUnknownItem, } fn merge_directives( @@ -122,6 +125,13 @@ fn parse_directive_items<'p, S: Stage>( span, ); } + Mode::DiagnosticOnUnknownItem => { + cx.emit_lint( + MALFORMED_DIAGNOSTIC_ATTRIBUTES, + AttributeLintKind::MalformedOnUnknownItemdAttr { span }, + span, + ); + } } continue; }} @@ -140,7 +150,7 @@ fn parse_directive_items<'p, S: Stage>( Mode::RustcOnUnimplemented => { cx.emit_err(NoValueInOnUnimplemented { span: item.span() }); } - Mode::DiagnosticOnUnimplemented |Mode::DiagnosticOnConst | Mode::DiagnosticOnMove => { + Mode::DiagnosticOnUnimplemented |Mode::DiagnosticOnConst | Mode::DiagnosticOnMove | Mode::DiagnosticOnUnknownItem => { cx.emit_lint( MALFORMED_DIAGNOSTIC_ATTRIBUTES, AttributeLintKind::IgnoredDiagnosticOption { @@ -176,7 +186,8 @@ fn parse_directive_items<'p, S: Stage>( Ok((f, warnings)) => { for warning in warnings { let (FormatWarning::InvalidSpecifier { span, .. } - | FormatWarning::PositionalArgument { span, .. }) = warning; + | FormatWarning::PositionalArgument { span, .. } + | FormatWarning::DisallowedPlaceholder { span }) = warning; cx.emit_lint( MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, AttributeLintKind::MalformedDiagnosticFormat { warning }, @@ -326,6 +337,10 @@ fn parse_arg( is_source_literal: bool, ) -> FormatArg { let span = slice_span(input_span, arg.position_span.clone(), is_source_literal); + if matches!(mode, Mode::DiagnosticOnUnknownItem) { + warnings.push(FormatWarning::DisallowedPlaceholder { span }); + return FormatArg::AsIs(sym::empty_braces); + } match arg.position { // Something like "hello {name}" diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown_item.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown_item.rs new file mode 100644 index 0000000000000..a7abecc671ec3 --- /dev/null +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown_item.rs @@ -0,0 +1,70 @@ +use rustc_hir::attrs::diagnostic::Directive; +use rustc_session::lint::builtin::MALFORMED_DIAGNOSTIC_ATTRIBUTES; + +use crate::attributes::diagnostic::*; +use crate::attributes::prelude::*; + +#[derive(Default)] +pub(crate) struct OnUnknownItemParser { + span: Option, + directive: Option<(Span, Directive)>, +} + +impl OnUnknownItemParser { + fn parse<'sess, S: Stage>( + &mut self, + cx: &mut AcceptContext<'_, 'sess, S>, + args: &ArgParser, + mode: Mode, + ) { + let span = cx.attr_span; + self.span = Some(span); + + let items = match args { + ArgParser::List(items) if !items.is_empty() => items, + ArgParser::NoArgs | ArgParser::List(_) => { + cx.emit_lint( + MALFORMED_DIAGNOSTIC_ATTRIBUTES, + AttributeLintKind::MissingOptionsForOnUnknownItem, + span, + ); + return; + } + ArgParser::NameValue(_) => { + cx.emit_lint( + MALFORMED_DIAGNOSTIC_ATTRIBUTES, + AttributeLintKind::MalformedOnUnknownItemdAttr { span }, + span, + ); + return; + } + }; + + if let Some(directive) = parse_directive_items(cx, mode, items.mixed(), true) { + merge_directives(cx, &mut self.directive, (span, directive)); + }; + } +} + +impl AttributeParser for OnUnknownItemParser { + const ATTRIBUTES: AcceptMapping = &[( + &[sym::diagnostic, sym::on_unknown_item], + template!(List: &[r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#]), + |this, cx, args| { + this.parse(cx, args, Mode::DiagnosticOnUnknownItem); + }, + )]; + //FIXME attribute is not parsed for non-traits but diagnostics are issued in `check_attr.rs` + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); + + fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option { + if let Some(span) = self.span { + Some(AttributeKind::OnUnknownItem { + span, + directive: self.directive.map(|d| Box::new(d.1)), + }) + } else { + None + } + } +} diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index b87a71bcbd92b..727094dafa944 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -32,6 +32,7 @@ use crate::attributes::diagnostic::do_not_recommend::*; use crate::attributes::diagnostic::on_const::*; use crate::attributes::diagnostic::on_move::*; use crate::attributes::diagnostic::on_unimplemented::*; +use crate::attributes::diagnostic::on_unknown_item::*; use crate::attributes::doc::*; use crate::attributes::dummy::*; use crate::attributes::inline::*; @@ -156,6 +157,7 @@ attribute_parsers!( OnConstParser, OnMoveParser, OnUnimplementedParser, + OnUnknownItemParser, RustcAlignParser, RustcAlignStaticParser, RustcCguTestAttributeParser, diff --git a/compiler/rustc_attr_parsing/src/interface.rs b/compiler/rustc_attr_parsing/src/interface.rs index 06738b9c73375..c6c6bff5f5a4a 100644 --- a/compiler/rustc_attr_parsing/src/interface.rs +++ b/compiler/rustc_attr_parsing/src/interface.rs @@ -30,7 +30,7 @@ pub struct AttributeParser<'sess, S: Stage = Late> { /// *Only* parse attributes with this symbol. /// /// Used in cases where we want the lowering infrastructure for parse just a single attribute. - parse_only: Option, + parse_only: Option<&'static [Symbol]>, } impl<'sess> AttributeParser<'sess, Early> { @@ -53,7 +53,7 @@ impl<'sess> AttributeParser<'sess, Early> { pub fn parse_limited( sess: &'sess Session, attrs: &[ast::Attribute], - sym: Symbol, + sym: &'static [Symbol], target_span: Span, target_node_id: NodeId, features: Option<&'sess Features>, @@ -77,7 +77,7 @@ impl<'sess> AttributeParser<'sess, Early> { pub fn parse_limited_should_emit( sess: &'sess Session, attrs: &[ast::Attribute], - sym: Symbol, + sym: &'static [Symbol], target_span: Span, target_node_id: NodeId, target: Target, @@ -109,7 +109,7 @@ impl<'sess> AttributeParser<'sess, Early> { pub fn parse_limited_all<'a>( sess: &'sess Session, attrs: impl IntoIterator, - parse_only: Option, + parse_only: Option<&'static [Symbol]>, target: Target, target_span: Span, target_node_id: NodeId, @@ -313,7 +313,7 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { for attr in attrs.into_iter() { // If we're only looking for a single attribute, skip all the ones we don't care about. if let Some(expected) = self.parse_only { - if !attr.has_name(expected) { + if !attr.path_matches(expected) { continue; } } diff --git a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs index 63c06e672727d..ae0078523adbc 100644 --- a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs +++ b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs @@ -493,7 +493,7 @@ impl<'a> TraitDef<'a> { match item { Annotatable::Item(item) => { let is_packed = matches!( - AttributeParser::parse_limited(cx.sess, &item.attrs, sym::repr, item.span, item.id, None), + AttributeParser::parse_limited(cx.sess, &item.attrs, &[sym::repr], item.span, item.id, None), Some(Attribute::Parsed(AttributeKind::Repr { reprs, .. })) if reprs.iter().any(|(x, _)| matches!(x, ReprPacked(..))) ); diff --git a/compiler/rustc_builtin_macros/src/proc_macro_harness.rs b/compiler/rustc_builtin_macros/src/proc_macro_harness.rs index 24a5d79958c60..84f2a8e35b02c 100644 --- a/compiler/rustc_builtin_macros/src/proc_macro_harness.rs +++ b/compiler/rustc_builtin_macros/src/proc_macro_harness.rs @@ -108,7 +108,7 @@ impl<'a> CollectProcMacros<'a> { })) = AttributeParser::parse_limited( self.session, slice::from_ref(attr), - sym::proc_macro_derive, + &[sym::proc_macro_derive], item.span, item.node_id(), None, diff --git a/compiler/rustc_builtin_macros/src/test.rs b/compiler/rustc_builtin_macros/src/test.rs index 5764dfc83927a..071b807109b71 100644 --- a/compiler/rustc_builtin_macros/src/test.rs +++ b/compiler/rustc_builtin_macros/src/test.rs @@ -483,7 +483,7 @@ fn should_panic(cx: &ExtCtxt<'_>, i: &ast::Item) -> ShouldPanic { AttributeParser::parse_limited( cx.sess, &i.attrs, - sym::should_panic, + &[sym::should_panic], i.span, i.node_id(), None, diff --git a/compiler/rustc_builtin_macros/src/test_harness.rs b/compiler/rustc_builtin_macros/src/test_harness.rs index 1bb6d8a6bfd05..1c947ea07d1a9 100644 --- a/compiler/rustc_builtin_macros/src/test_harness.rs +++ b/compiler/rustc_builtin_macros/src/test_harness.rs @@ -391,7 +391,7 @@ fn get_test_runner(sess: &Session, features: &Features, krate: &ast::Crate) -> O match AttributeParser::parse_limited( sess, &krate.attrs, - sym::test_runner, + &[sym::test_runner], krate.spans.inner_span, krate.id, Some(features), diff --git a/compiler/rustc_expand/src/config.rs b/compiler/rustc_expand/src/config.rs index ec5951e50e3a8..87e157babe8dc 100644 --- a/compiler/rustc_expand/src/config.rs +++ b/compiler/rustc_expand/src/config.rs @@ -54,7 +54,7 @@ pub fn features(sess: &Session, krate_attrs: &[Attribute], crate_name: Symbol) - AttributeParser::parse_limited( sess, krate_attrs, - sym::feature, + &[sym::feature], DUMMY_SP, DUMMY_NODE_ID, Some(&features), diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index b8b9226cc6021..72b07577847bd 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -814,6 +814,7 @@ pub fn is_stable_diagnostic_attribute(sym: Symbol, features: &Features) -> bool sym::on_unimplemented | sym::do_not_recommend => true, sym::on_const => features.diagnostic_on_const(), sym::on_move => features.diagnostic_on_move(), + sym::on_unknown_item => features.diagnostic_on_unknown_item(), _ => false, } } diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 859a1ad391cb9..7886a4fcac0df 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -474,6 +474,8 @@ declare_features! ( (unstable, diagnostic_on_const, "1.93.0", Some(143874)), /// Allows giving on-move borrowck custom diagnostic messages for a type (unstable, diagnostic_on_move, "CURRENT_RUSTC_VERSION", Some(154181)), + /// Allows giving unresolved imports a custom diagnostic message + (unstable, diagnostic_on_unknown_item, "CURRENT_RUSTC_VERSION", Some(152900)), /// Allows `#[doc(cfg(...))]`. (unstable, doc_cfg, "1.21.0", Some(43781)), /// Allows `#[doc(masked)]`. diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index f18d5a1f190a2..d476963533914 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -1334,6 +1334,14 @@ pub enum AttributeKind { /// None if the directive was malformed in some way. directive: Option>, }, + + /// Represents `#[diagnostic::on_unknown_item]` + OnUnknownItem { + span: Span, + /// None if the directive was malformed in some way. + directive: Option>, + }, + /// Represents `#[optimize(size|speed)]` Optimize(OptimizeAttr, Span), diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index 6612ebd6135b8..36830483e8e4a 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -80,6 +80,7 @@ impl AttributeKind { OnConst { .. } => Yes, OnMove { .. } => Yes, OnUnimplemented { .. } => Yes, + OnUnknownItem { .. } => Yes, Optimize(..) => No, PanicRuntime => No, PatchableFunctionEntry { .. } => Yes, diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index 43efce545fc28..762e3d580f2b0 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -1369,7 +1369,7 @@ pub(crate) fn parse_crate_name( AttributeParser::parse_limited_should_emit( sess, attrs, - sym::crate_name, + &[sym::crate_name], DUMMY_SP, rustc_ast::node_id::CRATE_NODE_ID, Target::Crate, @@ -1419,7 +1419,7 @@ pub fn collect_crate_types( AttributeParser::::parse_limited_should_emit( session, attrs, - sym::crate_type, + &[sym::crate_type], crate_span, CRATE_NODE_ID, Target::Crate, @@ -1476,7 +1476,7 @@ fn get_recursion_limit(krate_attrs: &[ast::Attribute], sess: &Session) -> Limit let attr = AttributeParser::parse_limited_should_emit( sess, &krate_attrs, - sym::recursion_limit, + &[sym::recursion_limit], DUMMY_SP, rustc_ast::node_id::CRATE_NODE_ID, Target::Crate, diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index b49a6e261d73d..c1e7e73c63e25 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -312,7 +312,7 @@ impl EarlyLintPass for UnsafeCode { AttributeParser::parse_limited( cx.builder.sess(), &it.attrs, - sym::allow_internal_unsafe, + &[sym::allow_internal_unsafe], it.span, DUMMY_NODE_ID, Some(cx.builder.features()), diff --git a/compiler/rustc_lint/src/early/diagnostics.rs b/compiler/rustc_lint/src/early/diagnostics.rs index c1779909e67d0..e0f1fb02af125 100644 --- a/compiler/rustc_lint/src/early/diagnostics.rs +++ b/compiler/rustc_lint/src/early/diagnostics.rs @@ -278,6 +278,9 @@ impl<'a> Diagnostic<'a, ()> for DecorateAttrLint<'_, '_, '_> { &AttributeLintKind::MalformedOnUnimplementedAttr { span } => { lints::MalformedOnUnimplementedAttrLint { span }.into_diag(dcx, level) } + &AttributeLintKind::MalformedOnUnknownItemdAttr { span } => { + lints::MalformedOnUnknownItemAttrLint { span }.into_diag(dcx, level) + } &AttributeLintKind::MalformedOnConstAttr { span } => { lints::MalformedOnConstAttrLint { span }.into_diag(dcx, level) } @@ -288,6 +291,9 @@ impl<'a> Diagnostic<'a, ()> for DecorateAttrLint<'_, '_, '_> { FormatWarning::InvalidSpecifier { .. } => { lints::InvalidFormatSpecifier.into_diag(dcx, level) } + FormatWarning::DisallowedPlaceholder { .. } => { + lints::DisallowedPlaceholder.into_diag(dcx, level) + } }, AttributeLintKind::DiagnosticWrappedParserError { description, label, span } => { lints::WrappedParserError { description, label, span: *span }.into_diag(dcx, level) @@ -336,6 +342,9 @@ impl<'a> Diagnostic<'a, ()> for DecorateAttrLint<'_, '_, '_> { &AttributeLintKind::IgnoredUnlessCrateSpecified { level: attr_level, name } => { lints::IgnoredUnlessCrateSpecified { level: attr_level, name }.into_diag(dcx, level) } + &AttributeLintKind::MissingOptionsForOnUnknownItem => { + lints::MissingOptionsForOnUnknownItemAttr.into_diag(dcx, level) + } } } } diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 107f31260b9e2..7bf9ce63348db 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -3612,6 +3612,11 @@ pub(crate) struct UnknownCrateTypesSuggestion { )] pub(crate) struct DisallowedPositionalArgument; +#[derive(Diagnostic)] +#[diag("format arguments are not allowed here")] +#[help("consider removing this format argument")] +pub(crate) struct DisallowedPlaceholder; + #[derive(Diagnostic)] #[diag("invalid format specifier")] #[help("no format specifier are supported in this position")] @@ -3641,6 +3646,11 @@ pub(crate) struct IgnoredDiagnosticOption { #[help("at least one of the `message`, `note` and `label` options are expected")] pub(crate) struct MissingOptionsForOnUnimplementedAttr; +#[derive(Diagnostic)] +#[diag("missing options for `on_unknown_item` attribute")] +#[help("at least one of the `message`, `note` and `label` options are expected")] +pub(crate) struct MissingOptionsForOnUnknownItemAttr; + #[derive(Diagnostic)] #[diag("missing options for `on_const` attribute")] #[help("at least one of the `message`, `note` and `label` options are expected")] @@ -3659,6 +3669,14 @@ pub(crate) struct MalformedOnUnimplementedAttrLint { pub span: Span, } +#[derive(Diagnostic)] +#[diag("malformed `on_unknown_item` attribute")] +#[help("only `message`, `note` and `label` are allowed as options")] +pub(crate) struct MalformedOnUnknownItemAttrLint { + #[label("invalid option found here")] + pub span: Span, +} + #[derive(Diagnostic)] #[diag("malformed `on_const` attribute")] #[help("only `message`, `note` and `label` are allowed as options")] diff --git a/compiler/rustc_lint/src/nonstandard_style.rs b/compiler/rustc_lint/src/nonstandard_style.rs index 1fd6699e2506e..297dfac4a5f78 100644 --- a/compiler/rustc_lint/src/nonstandard_style.rs +++ b/compiler/rustc_lint/src/nonstandard_style.rs @@ -145,7 +145,7 @@ impl NonCamelCaseTypes { impl EarlyLintPass for NonCamelCaseTypes { fn check_item(&mut self, cx: &EarlyContext<'_>, it: &ast::Item) { let has_repr_c = matches!( - AttributeParser::parse_limited(cx.sess(), &it.attrs, sym::repr, it.span, it.id, None), + AttributeParser::parse_limited(cx.sess(), &it.attrs, &[sym::repr], it.span, it.id, None), Some(Attribute::Parsed(AttributeKind::Repr { reprs, ..})) if reprs.iter().any(|(r, _)| r == &ReprAttr::ReprC) ); diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index 2ebbe633ecd1a..7370efe40c861 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -764,6 +764,9 @@ pub enum AttributeLintKind { MalformedOnUnimplementedAttr { span: Span, }, + MalformedOnUnknownItemdAttr { + span: Span, + }, MalformedOnConstAttr { span: Span, }, @@ -785,6 +788,7 @@ pub enum AttributeLintKind { }, MissingOptionsForOnUnimplemented, MissingOptionsForOnConst, + MissingOptionsForOnUnknownItem, MissingOptionsForOnMove, OnMoveMalformedFormatLiterals { name: Symbol, @@ -819,6 +823,7 @@ pub enum AttributeLintKind { pub enum FormatWarning { PositionalArgument { span: Span, help: String }, InvalidSpecifier { name: String, span: Span }, + DisallowedPlaceholder { span: Span }, } #[derive(Debug)] diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 3e3810b7f1257..a92a3518c98e3 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -71,6 +71,13 @@ struct DiagnosticOnConstOnlyForNonConstTraitImpls { #[diag("`#[diagnostic::on_move]` can only be applied to enums, structs or unions")] struct DiagnosticOnMoveOnlyForAdt; +#[derive(Diagnostic)] +#[diag("`#[diagnostic::on_unknown_item]` can only be applied to `use` statements")] +struct DiagnosticOnUnknownItemOnlyForImports { + #[label("not an import")] + item_span: Span, +} + fn target_from_impl_item<'tcx>(tcx: TyCtxt<'tcx>, impl_item: &hir::ImplItem<'_>) -> Target { match impl_item.kind { hir::ImplItemKind::Const(..) => Target::AssocConst, @@ -214,6 +221,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { }, Attribute::Parsed(AttributeKind::DoNotRecommend{attr_span}) => {self.check_do_not_recommend(*attr_span, hir_id, target, item)}, Attribute::Parsed(AttributeKind::OnUnimplemented{span, directive}) => {self.check_diagnostic_on_unimplemented(*span, hir_id, target,directive.as_deref())}, + Attribute::Parsed(AttributeKind::OnUnknownItem { span, .. }) => { self.check_diagnostic_on_unknown_item(*span, hir_id, target) }, Attribute::Parsed(AttributeKind::OnConst{span, ..}) => {self.check_diagnostic_on_const(*span, hir_id, target, item)} Attribute::Parsed(AttributeKind::OnMove { span, directive }) => { self.check_diagnostic_on_move(*span, hir_id, target, directive.as_deref()) @@ -654,6 +662,19 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } } + /// Checks if `#[diagnostic::on_unknown_item]` is applied to a trait impl + fn check_diagnostic_on_unknown_item(&self, attr_span: Span, hir_id: HirId, target: Target) { + if !matches!(target, Target::Use) { + let item_span = self.tcx.hir_span(hir_id); + self.tcx.emit_node_span_lint( + MISPLACED_DIAGNOSTIC_ATTRIBUTES, + hir_id, + attr_span, + DiagnosticOnUnknownItemOnlyForImports { item_span }, + ); + } + } + /// Checks if an `#[inline]` is applied to a function or a closure. fn check_inline(&self, hir_id: HirId, attr_span: Span, kind: &InlineAttr, target: Target) { match target { diff --git a/compiler/rustc_passes/src/debugger_visualizer.rs b/compiler/rustc_passes/src/debugger_visualizer.rs index 7211f3cf85b31..828ba698e0f2f 100644 --- a/compiler/rustc_passes/src/debugger_visualizer.rs +++ b/compiler/rustc_passes/src/debugger_visualizer.rs @@ -25,7 +25,7 @@ impl DebuggerVisualizerCollector<'_> { AttributeParser::parse_limited( &self.sess, attrs, - sym::debugger_visualizer, + &[sym::debugger_visualizer], span, node_id, None, diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index 29b5cdb2c8999..6c41cc2090d8b 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -32,7 +32,7 @@ use tracing::debug; use crate::Namespace::{MacroNS, TypeNS, ValueNS}; use crate::def_collector::collect_definitions; -use crate::imports::{ImportData, ImportKind}; +use crate::imports::{ImportData, ImportKind, OnUnknownItemData}; use crate::macros::{MacroRulesDecl, MacroRulesScope, MacroRulesScopeRef}; use crate::ref_mut::CmCell; use crate::{ @@ -545,6 +545,7 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> { root_id, vis, vis_span: item.vis.span, + on_unknown_item_attr: OnUnknownItemData::from_attrs(self.r.tcx, item), }); self.r.indeterminate_imports.push(import); @@ -1024,6 +1025,7 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> { module_path: Vec::new(), vis, vis_span: item.vis.span, + on_unknown_item_attr: OnUnknownItemData::from_attrs(self.r.tcx, item), }); if used { self.r.import_use_map.insert(import, Used::Other); @@ -1119,7 +1121,7 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> { AttributeParser::parse_limited( self.r.tcx.sess, &item.attrs, - sym::macro_use, + &[sym::macro_use], item.span, item.id, None, @@ -1156,6 +1158,7 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> { module_path: Vec::new(), vis: Visibility::Restricted(CRATE_DEF_ID), vis_span: item.vis.span, + on_unknown_item_attr: OnUnknownItemData::from_attrs(this.r.tcx, item), }) }; @@ -1327,6 +1330,7 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> { module_path: Vec::new(), vis, vis_span: item.vis.span, + on_unknown_item_attr: OnUnknownItemData::from_attrs(self.r.tcx, item), }); self.r.import_use_map.insert(import, Used::Other); let import_decl = self.r.new_import_decl(decl, import); diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs index d51ce9fb7946b..846efdf22b97c 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -2,16 +2,20 @@ use std::mem; -use rustc_ast::NodeId; +use rustc_ast::{Item, NodeId}; +use rustc_attr_parsing::AttributeParser; use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; use rustc_data_structures::intern::Interned; use rustc_errors::codes::*; use rustc_errors::{Applicability, Diagnostic, MultiSpan, pluralize, struct_span_code_err}; +use rustc_hir::Attribute; +use rustc_hir::attrs::AttributeKind; +use rustc_hir::attrs::diagnostic::{CustomDiagnostic, Directive, FormatArgs}; use rustc_hir::def::{self, DefKind, PartialRes}; use rustc_hir::def_id::{DefId, LocalDefIdMap}; use rustc_middle::metadata::{AmbigModChild, ModChild, Reexport}; use rustc_middle::span_bug; -use rustc_middle::ty::Visibility; +use rustc_middle::ty::{TyCtxt, Visibility}; use rustc_session::lint::builtin::{ AMBIGUOUS_GLOB_REEXPORTS, EXPORTED_PRIVATE_DEPENDENCIES, HIDDEN_GLOB_REEXPORTS, PUB_USE_OF_PRIVATE_EXTERN_CRATE, REDUNDANT_IMPORTS, UNUSED_IMPORTS, @@ -140,6 +144,30 @@ impl<'ra> std::fmt::Debug for ImportKind<'ra> { } } +#[derive(Debug, Clone, Default)] +pub(crate) struct OnUnknownItemData { + directive: Directive, +} + +impl OnUnknownItemData { + pub(crate) fn from_attrs<'tcx>(tcx: TyCtxt<'tcx>, item: &Item) -> Option { + if let Some(Attribute::Parsed(AttributeKind::OnUnknownItem { directive, .. })) = + AttributeParser::parse_limited( + tcx.sess, + &item.attrs, + &[sym::diagnostic, sym::on_unknown_item], + item.span, + item.id, + Some(tcx.features()), + ) + { + Some(Self { directive: *directive? }) + } else { + None + } + } +} + /// One import. #[derive(Debug, Clone)] pub(crate) struct ImportData<'ra> { @@ -186,6 +214,11 @@ pub(crate) struct ImportData<'ra> { /// Span of the visibility. pub vis_span: Span, + + /// A `#[diagnostic::on_unknown_item]` attribute applied + /// to the given import. This allows crates to specify + /// custom error messages for a specific import + pub on_unknown_item_attr: Option, } /// All imports are unique and allocated on a same arena, @@ -284,6 +317,7 @@ struct UnresolvedImportError { segment: Option, /// comes from `PathRes::Failed { module }` module: Option, + on_unknown_item_attr: Option, } // Reexports of the form `pub use foo as bar;` where `foo` is `extern crate foo;` @@ -700,6 +734,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { candidates: None, segment: None, module: None, + on_unknown_item_attr: import.on_unknown_item_attr.clone(), }; errors.push((*import, err)) } @@ -822,11 +857,41 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { format!("`{path}`") }) .collect::>(); - let msg = format!("unresolved import{} {}", pluralize!(paths.len()), paths.join(", "),); + let default_message = + format!("unresolved import{} {}", pluralize!(paths.len()), paths.join(", "),); + let (message, label, notes) = if self.tcx.features().diagnostic_on_unknown_item() + && let Some(directive) = errors[0].1.on_unknown_item_attr.as_ref().map(|a| &a.directive) + { + let args = FormatArgs { + this: paths.join(", "), + // Unused + this_sugared: String::new(), + // Unused + item_context: "", + // Unused + generic_args: Vec::new(), + }; + let CustomDiagnostic { message, label, notes, .. } = directive.eval(None, &args); - let mut diag = struct_span_code_err!(self.dcx(), span, E0432, "{msg}"); + (message, label, notes) + } else { + (None, None, Vec::new()) + }; + let has_custom_message = message.is_some(); + let message = message.as_deref().unwrap_or(default_message.as_str()); - if let Some((_, UnresolvedImportError { note: Some(note), .. })) = errors.iter().last() { + let mut diag = struct_span_code_err!(self.dcx(), span, E0432, "{message}"); + if has_custom_message { + diag.note(default_message); + } + + if !notes.is_empty() { + for note in notes { + diag.note(note); + } + } else if let Some((_, UnresolvedImportError { note: Some(note), .. })) = + errors.iter().last() + { diag.note(note.clone()); } @@ -834,8 +899,10 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { const MAX_LABEL_COUNT: usize = 10; for (import, err) in errors.into_iter().take(MAX_LABEL_COUNT) { - if let Some(label) = err.label { - diag.span_label(err.span, label); + if let Some(label) = &label { + diag.span_label(err.span, label.clone()); + } else if let Some(label) = &err.label { + diag.span_label(err.span, label.clone()); } if let Some((suggestions, msg, applicability)) = err.suggestion { @@ -1101,6 +1168,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { candidates: None, segment: Some(segment_name), module, + on_unknown_item_attr: import.on_unknown_item_attr.clone(), }, None => UnresolvedImportError { span, @@ -1110,6 +1178,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { candidates: None, segment: Some(segment_name), module, + on_unknown_item_attr: import.on_unknown_item_attr.clone(), }, }; return Some(err); @@ -1152,6 +1221,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { candidates: None, segment: None, module: None, + on_unknown_item_attr: None, }); } if let Some(max_vis) = max_vis.get() @@ -1374,6 +1444,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } }), segment: Some(ident.name), + on_unknown_item_attr: import.on_unknown_item_attr.clone(), }) } else { // `resolve_ident_in_module` reported a privacy error. diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index 619e61211984d..d9c71ed2f4338 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -140,7 +140,7 @@ pub fn registered_tools_ast( AttributeParser::parse_limited( sess, pre_configured_attrs, - sym::register_tool, + &[sym::register_tool], DUMMY_SP, DUMMY_NODE_ID, Some(features), @@ -712,17 +712,27 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { feature_err(&self.tcx.sess, sym::custom_inner_attributes, path.span, msg).emit(); } - const DIAG_ATTRS: &[Symbol] = - &[sym::on_unimplemented, sym::do_not_recommend, sym::on_const, sym::on_move]; + let diagnostic_attributes: &[(Symbol, bool)] = &[ + (sym::on_unimplemented, true), + (sym::do_not_recommend, true), + (sym::on_move, true), + (sym::on_const, self.tcx.features().diagnostic_on_const()), + (sym::on_unknown_item, self.tcx.features().diagnostic_on_unknown_item()), + ]; if res == Res::NonMacroAttr(NonMacroAttrKind::Tool) && let [namespace, attribute, ..] = &*path.segments && namespace.ident.name == sym::diagnostic - && !DIAG_ATTRS.contains(&attribute.ident.name) + && !diagnostic_attributes + .iter() + .any(|(attr, stable)| *stable && attribute.ident.name == *attr) { let span = attribute.span(); - - let typo = find_best_match_for_name(DIAG_ATTRS, attribute.ident.name, Some(5)) + let candidates = diagnostic_attributes + .iter() + .filter_map(|(sym, stable)| stable.then_some(*sym)) + .collect::>(); + let typo = find_best_match_for_name(&candidates, attribute.ident.name, Some(5)) .map(|typo_name| errors::UnknownDiagnosticAttributeTypoSugg { span, typo_name }); self.tcx.sess.psess.buffer_lint( diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index aab4aef43a53c..3bf416a18061d 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -799,6 +799,7 @@ symbols! { diagnostic_namespace, diagnostic_on_const, diagnostic_on_move, + diagnostic_on_unknown_item, dialect, direct, discriminant_kind, @@ -1414,6 +1415,7 @@ symbols! { on_const, on_move, on_unimplemented, + on_unknown_item, opaque, opaque_module_name_placeholder: "", ops, diff --git a/tests/ui/diagnostic_namespace/on_unknown_item/incorrect-locations.rs b/tests/ui/diagnostic_namespace/on_unknown_item/incorrect-locations.rs new file mode 100644 index 0000000000000..7b450f2fd4fac --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown_item/incorrect-locations.rs @@ -0,0 +1,52 @@ +//@ run-pass +#![allow(dead_code, unused_imports)] +#![feature(diagnostic_on_unknown_item)] + +#[diagnostic::on_unknown_item(message = "foo")] +//~^WARN `#[diagnostic::on_unknown_item]` can only be applied to `use` statements +extern crate std as other_std; + +#[diagnostic::on_unknown_item(message = "foo")] +//~^WARN `#[diagnostic::on_unknown_item]` can only be applied to `use` statements +const CONST: () = (); + +#[diagnostic::on_unknown_item(message = "foo")] +//~^WARN `#[diagnostic::on_unknown_item]` can only be applied to `use` statements +static STATIC: () = (); + +#[diagnostic::on_unknown_item(message = "foo")] +//~^WARN `#[diagnostic::on_unknown_item]` can only be applied to `use` statements +type Type = (); + +#[diagnostic::on_unknown_item(message = "foo")] +//~^WARN `#[diagnostic::on_unknown_item]` can only be applied to `use` statements +enum Enum {} + +#[diagnostic::on_unknown_item(message = "foo")] +//~^WARN `#[diagnostic::on_unknown_item]` can only be applied to `use` statements +impl Enum {} + +#[diagnostic::on_unknown_item(message = "foo")] +//~^WARN `#[diagnostic::on_unknown_item]` can only be applied to `use` statements +extern "C" {} + +#[diagnostic::on_unknown_item(message = "foo")] +//~^WARN `#[diagnostic::on_unknown_item]` can only be applied to `use` statements +fn fun() {} + +#[diagnostic::on_unknown_item(message = "foo")] +//~^WARN `#[diagnostic::on_unknown_item]` can only be applied to `use` statements +struct Struct {} + +#[diagnostic::on_unknown_item(message = "foo")] +//~^WARN `#[diagnostic::on_unknown_item]` can only be applied to `use` statements +trait Trait {} + +#[diagnostic::on_unknown_item(message = "foo")] +//~^WARN `#[diagnostic::on_unknown_item]` can only be applied to `use` statements +impl Trait for i32 {} + +#[diagnostic::on_unknown_item(message = "foo")] +use std::str::FromStr; + +fn main() {} diff --git a/tests/ui/diagnostic_namespace/on_unknown_item/incorrect-locations.stderr b/tests/ui/diagnostic_namespace/on_unknown_item/incorrect-locations.stderr new file mode 100644 index 0000000000000..b09b6c90fd0e2 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown_item/incorrect-locations.stderr @@ -0,0 +1,103 @@ +warning: `#[diagnostic::on_unknown_item]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:5:1 + | +LL | #[diagnostic::on_unknown_item(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | extern crate std as other_std; + | ----------------------------- not an import + | + = note: `#[warn(misplaced_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default + +warning: `#[diagnostic::on_unknown_item]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:9:1 + | +LL | #[diagnostic::on_unknown_item(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | const CONST: () = (); + | --------------- not an import + +warning: `#[diagnostic::on_unknown_item]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:13:1 + | +LL | #[diagnostic::on_unknown_item(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | static STATIC: () = (); + | ----------------- not an import + +warning: `#[diagnostic::on_unknown_item]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:17:1 + | +LL | #[diagnostic::on_unknown_item(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | type Type = (); + | --------- not an import + +warning: `#[diagnostic::on_unknown_item]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:21:1 + | +LL | #[diagnostic::on_unknown_item(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | enum Enum {} + | --------- not an import + +warning: `#[diagnostic::on_unknown_item]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:25:1 + | +LL | #[diagnostic::on_unknown_item(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | impl Enum {} + | --------- not an import + +warning: `#[diagnostic::on_unknown_item]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:29:1 + | +LL | #[diagnostic::on_unknown_item(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | extern "C" {} + | ------------- not an import + +warning: `#[diagnostic::on_unknown_item]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:33:1 + | +LL | #[diagnostic::on_unknown_item(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | fn fun() {} + | -------- not an import + +warning: `#[diagnostic::on_unknown_item]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:37:1 + | +LL | #[diagnostic::on_unknown_item(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | struct Struct {} + | ------------- not an import + +warning: `#[diagnostic::on_unknown_item]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:41:1 + | +LL | #[diagnostic::on_unknown_item(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | trait Trait {} + | ----------- not an import + +warning: `#[diagnostic::on_unknown_item]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:45:1 + | +LL | #[diagnostic::on_unknown_item(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | impl Trait for i32 {} + | ------------------ not an import + +warning: 11 warnings emitted + diff --git a/tests/ui/diagnostic_namespace/on_unknown_item/incorrect_format_string.rs b/tests/ui/diagnostic_namespace/on_unknown_item/incorrect_format_string.rs new file mode 100644 index 0000000000000..d7d6f1845060d --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown_item/incorrect_format_string.rs @@ -0,0 +1,33 @@ +#![feature(diagnostic_on_unknown_item)] + +#[diagnostic::on_unknown_item(message = "foo {}")] +//~^ WARN: format arguments are not allowed here +use std::does_not_exist; +//~^ ERROR: foo {} + +#[diagnostic::on_unknown_item(message = "foo {A}")] +//~^ WARN: format arguments are not allowed here +use std::does_not_exist2; +//~^ ERROR: foo {} + +#[diagnostic::on_unknown_item(label = "foo {}")] +//~^ WARN: format arguments are not allowed here +use std::does_not_exist3; +//~^ ERROR: unresolved import `std::does_not_exist3` + +#[diagnostic::on_unknown_item(label = "foo {A}")] +//~^ WARN: format arguments are not allowed here +use std::does_not_exist4; +//~^ ERROR: unresolved import `std::does_not_exist4` + +#[diagnostic::on_unknown_item(note = "foo {}")] +//~^ WARN: format arguments are not allowed here +use std::does_not_exist5; +//~^ ERROR: unresolved import `std::does_not_exist5` + +#[diagnostic::on_unknown_item(note = "foo {A}")] +//~^ WARN: format arguments are not allowed here +use std::does_not_exist6; +//~^ ERROR: unresolved import `std::does_not_exist6` + +fn main() {} diff --git a/tests/ui/diagnostic_namespace/on_unknown_item/incorrect_format_string.stderr b/tests/ui/diagnostic_namespace/on_unknown_item/incorrect_format_string.stderr new file mode 100644 index 0000000000000..e8551479287eb --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown_item/incorrect_format_string.stderr @@ -0,0 +1,96 @@ +error[E0432]: foo {} + --> $DIR/incorrect_format_string.rs:5:5 + | +LL | use std::does_not_exist; + | ^^^^^^^^^^^^^^^^^^^ no `does_not_exist` in the root + | + = note: unresolved import `std::does_not_exist` + +error[E0432]: foo {} + --> $DIR/incorrect_format_string.rs:10:5 + | +LL | use std::does_not_exist2; + | ^^^^^^^^^^^^^^^^^^^^ no `does_not_exist2` in the root + | + = note: unresolved import `std::does_not_exist2` + +error[E0432]: unresolved import `std::does_not_exist3` + --> $DIR/incorrect_format_string.rs:15:5 + | +LL | use std::does_not_exist3; + | ^^^^^^^^^^^^^^^^^^^^ foo {} + +error[E0432]: unresolved import `std::does_not_exist4` + --> $DIR/incorrect_format_string.rs:20:5 + | +LL | use std::does_not_exist4; + | ^^^^^^^^^^^^^^^^^^^^ foo {} + +error[E0432]: unresolved import `std::does_not_exist5` + --> $DIR/incorrect_format_string.rs:25:5 + | +LL | use std::does_not_exist5; + | ^^^^^^^^^^^^^^^^^^^^ no `does_not_exist5` in the root + | + = note: foo {} + +error[E0432]: unresolved import `std::does_not_exist6` + --> $DIR/incorrect_format_string.rs:30:5 + | +LL | use std::does_not_exist6; + | ^^^^^^^^^^^^^^^^^^^^ no `does_not_exist6` in the root + | + = note: foo {} + +warning: format arguments are not allowed here + --> $DIR/incorrect_format_string.rs:3:47 + | +LL | #[diagnostic::on_unknown_item(message = "foo {}")] + | ^ + | + = help: consider removing this format argument + = note: `#[warn(malformed_diagnostic_format_literals)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default + +warning: format arguments are not allowed here + --> $DIR/incorrect_format_string.rs:8:47 + | +LL | #[diagnostic::on_unknown_item(message = "foo {A}")] + | ^ + | + = help: consider removing this format argument + +warning: format arguments are not allowed here + --> $DIR/incorrect_format_string.rs:13:45 + | +LL | #[diagnostic::on_unknown_item(label = "foo {}")] + | ^ + | + = help: consider removing this format argument + +warning: format arguments are not allowed here + --> $DIR/incorrect_format_string.rs:18:45 + | +LL | #[diagnostic::on_unknown_item(label = "foo {A}")] + | ^ + | + = help: consider removing this format argument + +warning: format arguments are not allowed here + --> $DIR/incorrect_format_string.rs:23:44 + | +LL | #[diagnostic::on_unknown_item(note = "foo {}")] + | ^ + | + = help: consider removing this format argument + +warning: format arguments are not allowed here + --> $DIR/incorrect_format_string.rs:28:44 + | +LL | #[diagnostic::on_unknown_item(note = "foo {A}")] + | ^ + | + = help: consider removing this format argument + +error: aborting due to 6 previous errors; 6 warnings emitted + +For more information about this error, try `rustc --explain E0432`. diff --git a/tests/ui/diagnostic_namespace/on_unknown_item/malformed_attribute.rs b/tests/ui/diagnostic_namespace/on_unknown_item/malformed_attribute.rs new file mode 100644 index 0000000000000..4ffa9ffe37b5b --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown_item/malformed_attribute.rs @@ -0,0 +1,19 @@ +#![feature(diagnostic_on_unknown_item)] +#[diagnostic::on_unknown_item] +//~^WARN missing options for `on_unknown_item` attribute +use std::str::FromStr; + +#[diagnostic::on_unknown_item(foo = "bar", message = "foo")] +//~^WARN malformed `on_unknown_item` attribute +use std::str::Bytes; + +#[diagnostic::on_unknown_item(label = "foo", label = "bar")] +//~^WARN `label` is ignored due to previous definition of `label` +use std::str::Chars; + +#[diagnostic::on_unknown_item(message = "Foo", message = "Bar")] +//~^WARN `message` is ignored due to previous definition of `message` +use std::str::NotExisting; +//~^ERROR Foo + +fn main() {} diff --git a/tests/ui/diagnostic_namespace/on_unknown_item/malformed_attribute.stderr b/tests/ui/diagnostic_namespace/on_unknown_item/malformed_attribute.stderr new file mode 100644 index 0000000000000..42caaa9354afa --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown_item/malformed_attribute.stderr @@ -0,0 +1,44 @@ +error[E0432]: Foo + --> $DIR/malformed_attribute.rs:16:5 + | +LL | use std::str::NotExisting; + | ^^^^^^^^^^^^^^^^^^^^^ no `NotExisting` in `str` + | + = note: unresolved import `std::str::NotExisting` + +warning: missing options for `on_unknown_item` attribute + --> $DIR/malformed_attribute.rs:2:1 + | +LL | #[diagnostic::on_unknown_item] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: at least one of the `message`, `note` and `label` options are expected + = note: `#[warn(malformed_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default + +warning: malformed `on_unknown_item` attribute + --> $DIR/malformed_attribute.rs:6:31 + | +LL | #[diagnostic::on_unknown_item(foo = "bar", message = "foo")] + | ^^^^^^^^^^^ invalid option found here + | + = help: only `message`, `note` and `label` are allowed as options + +warning: `label` is ignored due to previous definition of `label` + --> $DIR/malformed_attribute.rs:10:46 + | +LL | #[diagnostic::on_unknown_item(label = "foo", label = "bar")] + | ------------- ^^^^^^^^^^^^^ `label` is later redundantly declared here + | | + | `label` is first declared here + +warning: `message` is ignored due to previous definition of `message` + --> $DIR/malformed_attribute.rs:14:48 + | +LL | #[diagnostic::on_unknown_item(message = "Foo", message = "Bar")] + | --------------- ^^^^^^^^^^^^^^^ `message` is later redundantly declared here + | | + | `message` is first declared here + +error: aborting due to 1 previous error; 4 warnings emitted + +For more information about this error, try `rustc --explain E0432`. diff --git a/tests/ui/diagnostic_namespace/on_unknown_item/multiple_errors.rs b/tests/ui/diagnostic_namespace/on_unknown_item/multiple_errors.rs new file mode 100644 index 0000000000000..431ab6cdd831a --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown_item/multiple_errors.rs @@ -0,0 +1,48 @@ +#![feature(diagnostic_on_unknown_item)] + +mod test1 { + #[diagnostic::on_unknown_item( + message = "custom message", + label = "custom label", + note = "custom note" + )] + use std::vec::{NonExisting, Vec, Whatever}; + //~^ ERROR: custom message +} + +mod test2 { + #[diagnostic::on_unknown_item( + message = "custom message", + label = "custom label", + note = "custom note" + )] + use std::{Whatever, vec::NonExisting, vec::Vec, *}; + //~^ ERROR: custom message +} + +mod test3 { + #[diagnostic::on_unknown_item( + message = "custom message", + label = "custom label", + note = "custom note" + )] + use std::{ + string::String, + vec::{NonExisting, Vec}, + //~^ ERROR: custom message + }; +} + +mod test4 { + #[diagnostic::on_unknown_item( + message = "custom message", + label = "custom label", + note = "custom note" + )] + use std::{ + string::String, + vec::{Vec, non_existing::*}, + //~^ ERROR: custom message + }; +} +fn main() {} diff --git a/tests/ui/diagnostic_namespace/on_unknown_item/multiple_errors.stderr b/tests/ui/diagnostic_namespace/on_unknown_item/multiple_errors.stderr new file mode 100644 index 0000000000000..fcce77f6aebbe --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown_item/multiple_errors.stderr @@ -0,0 +1,43 @@ +error[E0432]: custom message + --> $DIR/multiple_errors.rs:9:20 + | +LL | use std::vec::{NonExisting, Vec, Whatever}; + | ^^^^^^^^^^^ ^^^^^^^^ custom label + | | + | custom label + | + = note: unresolved imports `std::vec::NonExisting`, `std::vec::Whatever` + = note: custom note + +error[E0432]: custom message + --> $DIR/multiple_errors.rs:19:15 + | +LL | use std::{Whatever, vec::NonExisting, vec::Vec, *}; + | ^^^^^^^^ ^^^^^^^^^^^^^^^^ custom label + | | + | custom label + | + = note: unresolved imports `std::Whatever`, `std::vec::NonExisting` + = note: custom note + +error[E0432]: custom message + --> $DIR/multiple_errors.rs:31:15 + | +LL | vec::{NonExisting, Vec}, + | ^^^^^^^^^^^ custom label + | + = note: unresolved import `std::vec::NonExisting` + = note: custom note + +error[E0432]: custom message + --> $DIR/multiple_errors.rs:44:20 + | +LL | vec::{Vec, non_existing::*}, + | ^^^^^^^^^^^^ custom label + | + = note: unresolved import `std::vec::non_existing` + = note: custom note + +error: aborting due to 4 previous errors + +For more information about this error, try `rustc --explain E0432`. diff --git a/tests/ui/diagnostic_namespace/on_unknown_item/unknown_import.rs b/tests/ui/diagnostic_namespace/on_unknown_item/unknown_import.rs new file mode 100644 index 0000000000000..5af79af23c2cc --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown_item/unknown_import.rs @@ -0,0 +1,17 @@ +#![feature(diagnostic_on_unknown_item)] +pub mod foo { + pub struct Bar; +} + +#[diagnostic::on_unknown_item( + message = "first message", + label = "first label", + note = "custom note", + note = "custom note 2" +)] +use foo::Foo; +//~^ERROR first message + +use foo::Bar; + +fn main() {} diff --git a/tests/ui/diagnostic_namespace/on_unknown_item/unknown_import.stderr b/tests/ui/diagnostic_namespace/on_unknown_item/unknown_import.stderr new file mode 100644 index 0000000000000..a9867fd74bfb0 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown_item/unknown_import.stderr @@ -0,0 +1,13 @@ +error[E0432]: first message + --> $DIR/unknown_import.rs:12:5 + | +LL | use foo::Foo; + | ^^^^^^^^ first label + | + = note: unresolved import `foo::Foo` + = note: custom note + = note: custom note 2 + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0432`. diff --git a/tests/ui/feature-gates/feature-gate-diagnostic-on-unknown-item.rs b/tests/ui/feature-gates/feature-gate-diagnostic-on-unknown-item.rs new file mode 100644 index 0000000000000..fffb54636cfb7 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-diagnostic-on-unknown-item.rs @@ -0,0 +1,8 @@ +#![deny(warnings)] + +#[diagnostic::on_unknown_item(message = "Tada")] +//~^ ERROR: unknown diagnostic attribute +use std::vec::NotExisting; +//~^ ERROR: unresolved import `std::vec::NotExisting` + +fn main() {} diff --git a/tests/ui/feature-gates/feature-gate-diagnostic-on-unknown-item.stderr b/tests/ui/feature-gates/feature-gate-diagnostic-on-unknown-item.stderr new file mode 100644 index 0000000000000..10662eb83b4a5 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-diagnostic-on-unknown-item.stderr @@ -0,0 +1,22 @@ +error[E0432]: unresolved import `std::vec::NotExisting` + --> $DIR/feature-gate-diagnostic-on-unknown-item.rs:5:5 + | +LL | use std::vec::NotExisting; + | ^^^^^^^^^^^^^^^^^^^^^ no `NotExisting` in `vec` + +error: unknown diagnostic attribute + --> $DIR/feature-gate-diagnostic-on-unknown-item.rs:3:15 + | +LL | #[diagnostic::on_unknown_item(message = "Tada")] + | ^^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/feature-gate-diagnostic-on-unknown-item.rs:1:9 + | +LL | #![deny(warnings)] + | ^^^^^^^^ + = note: `#[deny(unknown_diagnostic_attributes)]` implied by `#[deny(warnings)]` + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0432`. From a5f3c841150d89c7f32cf3506738474cad7dc240 Mon Sep 17 00:00:00 2001 From: Georg Semmler Date: Mon, 16 Mar 2026 13:01:13 +0100 Subject: [PATCH 140/144] Address review comments --- .../src/attributes/diagnostic/on_unknown_item.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown_item.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown_item.rs index a7abecc671ec3..f2a243a8e0bba 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown_item.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown_item.rs @@ -17,6 +17,9 @@ impl OnUnknownItemParser { args: &ArgParser, mode: Mode, ) { + if !cx.features().diagnostic_on_unknown_item() { + return; + } let span = cx.attr_span; self.span = Some(span); @@ -54,7 +57,7 @@ impl AttributeParser for OnUnknownItemParser { this.parse(cx, args, Mode::DiagnosticOnUnknownItem); }, )]; - //FIXME attribute is not parsed for non-traits but diagnostics are issued in `check_attr.rs` + //FIXME attribute is not parsed for non-use statements but diagnostics are issued in `check_attr.rs` const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option { From 93f9aeca37464f85b34047c0e8ab72ecd1e91a35 Mon Sep 17 00:00:00 2001 From: Georg Semmler Date: Wed, 8 Apr 2026 08:36:39 +0200 Subject: [PATCH 141/144] Rename the attribute to `on_unknown` --- .../src/attributes/diagnostic/mod.rs | 14 +-- .../{on_unknown_item.rs => on_unknown.rs} | 18 +-- compiler/rustc_attr_parsing/src/context.rs | 4 +- compiler/rustc_feature/src/builtin_attrs.rs | 2 +- compiler/rustc_feature/src/unstable.rs | 2 +- .../rustc_hir/src/attrs/data_structures.rs | 4 +- .../rustc_hir/src/attrs/encode_cross_crate.rs | 2 +- compiler/rustc_lint/src/early/diagnostics.rs | 8 +- compiler/rustc_lint/src/lints.rs | 8 +- compiler/rustc_lint_defs/src/lib.rs | 4 +- compiler/rustc_passes/src/check_attr.rs | 12 +- .../rustc_resolve/src/build_reduced_graph.rs | 10 +- compiler/rustc_resolve/src/imports.rs | 30 ++--- compiler/rustc_resolve/src/macros.rs | 2 +- compiler/rustc_span/src/symbol.rs | 4 +- .../on_unknown/incorrect-locations.rs | 52 +++++++++ .../on_unknown/incorrect-locations.stderr | 103 ++++++++++++++++++ .../incorrect_format_string.rs | 14 +-- .../incorrect_format_string.stderr | 36 +++--- .../on_unknown/malformed_attribute.rs | 19 ++++ .../on_unknown/malformed_attribute.stderr | 44 ++++++++ .../multiple_errors.rs | 10 +- .../multiple_errors.stderr | 0 .../unknown_import.rs | 4 +- .../unknown_import.stderr | 0 .../on_unknown_item/incorrect-locations.rs | 52 --------- .../incorrect-locations.stderr | 103 ------------------ .../on_unknown_item/malformed_attribute.rs | 19 ---- .../malformed_attribute.stderr | 44 -------- ... => feature-gate-diagnostic-on-unknown.rs} | 2 +- ...feature-gate-diagnostic-on-unknown.stderr} | 10 +- 31 files changed, 318 insertions(+), 318 deletions(-) rename compiler/rustc_attr_parsing/src/attributes/diagnostic/{on_unknown_item.rs => on_unknown.rs} (82%) create mode 100644 tests/ui/diagnostic_namespace/on_unknown/incorrect-locations.rs create mode 100644 tests/ui/diagnostic_namespace/on_unknown/incorrect-locations.stderr rename tests/ui/diagnostic_namespace/{on_unknown_item => on_unknown}/incorrect_format_string.rs (67%) rename tests/ui/diagnostic_namespace/{on_unknown_item => on_unknown}/incorrect_format_string.stderr (70%) create mode 100644 tests/ui/diagnostic_namespace/on_unknown/malformed_attribute.rs create mode 100644 tests/ui/diagnostic_namespace/on_unknown/malformed_attribute.stderr rename tests/ui/diagnostic_namespace/{on_unknown_item => on_unknown}/multiple_errors.rs (83%) rename tests/ui/diagnostic_namespace/{on_unknown_item => on_unknown}/multiple_errors.stderr (100%) rename tests/ui/diagnostic_namespace/{on_unknown_item => on_unknown}/unknown_import.rs (75%) rename tests/ui/diagnostic_namespace/{on_unknown_item => on_unknown}/unknown_import.stderr (100%) delete mode 100644 tests/ui/diagnostic_namespace/on_unknown_item/incorrect-locations.rs delete mode 100644 tests/ui/diagnostic_namespace/on_unknown_item/incorrect-locations.stderr delete mode 100644 tests/ui/diagnostic_namespace/on_unknown_item/malformed_attribute.rs delete mode 100644 tests/ui/diagnostic_namespace/on_unknown_item/malformed_attribute.stderr rename tests/ui/feature-gates/{feature-gate-diagnostic-on-unknown-item.rs => feature-gate-diagnostic-on-unknown.rs} (76%) rename tests/ui/feature-gates/{feature-gate-diagnostic-on-unknown-item.stderr => feature-gate-diagnostic-on-unknown.stderr} (64%) diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs index 61fd3f0962488..f68bed620f1b3 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs @@ -24,7 +24,7 @@ pub(crate) mod do_not_recommend; pub(crate) mod on_const; pub(crate) mod on_move; pub(crate) mod on_unimplemented; -pub(crate) mod on_unknown_item; +pub(crate) mod on_unknown; #[derive(Copy, Clone)] pub(crate) enum Mode { @@ -36,8 +36,8 @@ pub(crate) enum Mode { DiagnosticOnConst, /// `#[diagnostic::on_move]` DiagnosticOnMove, - /// `#[diagnostic::on_unknown_item]` - DiagnosticOnUnknownItem, + /// `#[diagnostic::on_unknown]` + DiagnosticOnUnknown, } fn merge_directives( @@ -125,10 +125,10 @@ fn parse_directive_items<'p, S: Stage>( span, ); } - Mode::DiagnosticOnUnknownItem => { + Mode::DiagnosticOnUnknown => { cx.emit_lint( MALFORMED_DIAGNOSTIC_ATTRIBUTES, - AttributeLintKind::MalformedOnUnknownItemdAttr { span }, + AttributeLintKind::MalformedOnUnknownAttr { span }, span, ); } @@ -150,7 +150,7 @@ fn parse_directive_items<'p, S: Stage>( Mode::RustcOnUnimplemented => { cx.emit_err(NoValueInOnUnimplemented { span: item.span() }); } - Mode::DiagnosticOnUnimplemented |Mode::DiagnosticOnConst | Mode::DiagnosticOnMove | Mode::DiagnosticOnUnknownItem => { + Mode::DiagnosticOnUnimplemented |Mode::DiagnosticOnConst | Mode::DiagnosticOnMove | Mode::DiagnosticOnUnknown => { cx.emit_lint( MALFORMED_DIAGNOSTIC_ATTRIBUTES, AttributeLintKind::IgnoredDiagnosticOption { @@ -337,7 +337,7 @@ fn parse_arg( is_source_literal: bool, ) -> FormatArg { let span = slice_span(input_span, arg.position_span.clone(), is_source_literal); - if matches!(mode, Mode::DiagnosticOnUnknownItem) { + if matches!(mode, Mode::DiagnosticOnUnknown) { warnings.push(FormatWarning::DisallowedPlaceholder { span }); return FormatArg::AsIs(sym::empty_braces); } diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown_item.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown.rs similarity index 82% rename from compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown_item.rs rename to compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown.rs index f2a243a8e0bba..bd5eb4cbf82c7 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown_item.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown.rs @@ -5,19 +5,19 @@ use crate::attributes::diagnostic::*; use crate::attributes::prelude::*; #[derive(Default)] -pub(crate) struct OnUnknownItemParser { +pub(crate) struct OnUnknownParser { span: Option, directive: Option<(Span, Directive)>, } -impl OnUnknownItemParser { +impl OnUnknownParser { fn parse<'sess, S: Stage>( &mut self, cx: &mut AcceptContext<'_, 'sess, S>, args: &ArgParser, mode: Mode, ) { - if !cx.features().diagnostic_on_unknown_item() { + if !cx.features().diagnostic_on_unknown() { return; } let span = cx.attr_span; @@ -28,7 +28,7 @@ impl OnUnknownItemParser { ArgParser::NoArgs | ArgParser::List(_) => { cx.emit_lint( MALFORMED_DIAGNOSTIC_ATTRIBUTES, - AttributeLintKind::MissingOptionsForOnUnknownItem, + AttributeLintKind::MissingOptionsForOnUnknown, span, ); return; @@ -36,7 +36,7 @@ impl OnUnknownItemParser { ArgParser::NameValue(_) => { cx.emit_lint( MALFORMED_DIAGNOSTIC_ATTRIBUTES, - AttributeLintKind::MalformedOnUnknownItemdAttr { span }, + AttributeLintKind::MalformedOnUnknownAttr { span }, span, ); return; @@ -49,12 +49,12 @@ impl OnUnknownItemParser { } } -impl AttributeParser for OnUnknownItemParser { +impl AttributeParser for OnUnknownParser { const ATTRIBUTES: AcceptMapping = &[( - &[sym::diagnostic, sym::on_unknown_item], + &[sym::diagnostic, sym::on_unknown], template!(List: &[r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#]), |this, cx, args| { - this.parse(cx, args, Mode::DiagnosticOnUnknownItem); + this.parse(cx, args, Mode::DiagnosticOnUnknown); }, )]; //FIXME attribute is not parsed for non-use statements but diagnostics are issued in `check_attr.rs` @@ -62,7 +62,7 @@ impl AttributeParser for OnUnknownItemParser { fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option { if let Some(span) = self.span { - Some(AttributeKind::OnUnknownItem { + Some(AttributeKind::OnUnknown { span, directive: self.directive.map(|d| Box::new(d.1)), }) diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 727094dafa944..24796cea43373 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -32,7 +32,7 @@ use crate::attributes::diagnostic::do_not_recommend::*; use crate::attributes::diagnostic::on_const::*; use crate::attributes::diagnostic::on_move::*; use crate::attributes::diagnostic::on_unimplemented::*; -use crate::attributes::diagnostic::on_unknown_item::*; +use crate::attributes::diagnostic::on_unknown::*; use crate::attributes::doc::*; use crate::attributes::dummy::*; use crate::attributes::inline::*; @@ -157,7 +157,7 @@ attribute_parsers!( OnConstParser, OnMoveParser, OnUnimplementedParser, - OnUnknownItemParser, + OnUnknownParser, RustcAlignParser, RustcAlignStaticParser, RustcCguTestAttributeParser, diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index 72b07577847bd..dba0b7ab7432c 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -814,7 +814,7 @@ pub fn is_stable_diagnostic_attribute(sym: Symbol, features: &Features) -> bool sym::on_unimplemented | sym::do_not_recommend => true, sym::on_const => features.diagnostic_on_const(), sym::on_move => features.diagnostic_on_move(), - sym::on_unknown_item => features.diagnostic_on_unknown_item(), + sym::on_unknown => features.diagnostic_on_unknown(), _ => false, } } diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 7886a4fcac0df..c2fe6e1360201 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -475,7 +475,7 @@ declare_features! ( /// Allows giving on-move borrowck custom diagnostic messages for a type (unstable, diagnostic_on_move, "CURRENT_RUSTC_VERSION", Some(154181)), /// Allows giving unresolved imports a custom diagnostic message - (unstable, diagnostic_on_unknown_item, "CURRENT_RUSTC_VERSION", Some(152900)), + (unstable, diagnostic_on_unknown, "CURRENT_RUSTC_VERSION", Some(152900)), /// Allows `#[doc(cfg(...))]`. (unstable, doc_cfg, "1.21.0", Some(43781)), /// Allows `#[doc(masked)]`. diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index d476963533914..8b5e1bff8536e 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -1335,8 +1335,8 @@ pub enum AttributeKind { directive: Option>, }, - /// Represents `#[diagnostic::on_unknown_item]` - OnUnknownItem { + /// Represents `#[diagnostic::on_unknown]` + OnUnknown { span: Span, /// None if the directive was malformed in some way. directive: Option>, diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index 36830483e8e4a..d9c2fdcabfd8d 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -80,7 +80,7 @@ impl AttributeKind { OnConst { .. } => Yes, OnMove { .. } => Yes, OnUnimplemented { .. } => Yes, - OnUnknownItem { .. } => Yes, + OnUnknown { .. } => Yes, Optimize(..) => No, PanicRuntime => No, PatchableFunctionEntry { .. } => Yes, diff --git a/compiler/rustc_lint/src/early/diagnostics.rs b/compiler/rustc_lint/src/early/diagnostics.rs index e0f1fb02af125..ba5e950af6a9f 100644 --- a/compiler/rustc_lint/src/early/diagnostics.rs +++ b/compiler/rustc_lint/src/early/diagnostics.rs @@ -278,8 +278,8 @@ impl<'a> Diagnostic<'a, ()> for DecorateAttrLint<'_, '_, '_> { &AttributeLintKind::MalformedOnUnimplementedAttr { span } => { lints::MalformedOnUnimplementedAttrLint { span }.into_diag(dcx, level) } - &AttributeLintKind::MalformedOnUnknownItemdAttr { span } => { - lints::MalformedOnUnknownItemAttrLint { span }.into_diag(dcx, level) + &AttributeLintKind::MalformedOnUnknownAttr { span } => { + lints::MalformedOnUnknownAttrLint { span }.into_diag(dcx, level) } &AttributeLintKind::MalformedOnConstAttr { span } => { lints::MalformedOnConstAttrLint { span }.into_diag(dcx, level) @@ -342,8 +342,8 @@ impl<'a> Diagnostic<'a, ()> for DecorateAttrLint<'_, '_, '_> { &AttributeLintKind::IgnoredUnlessCrateSpecified { level: attr_level, name } => { lints::IgnoredUnlessCrateSpecified { level: attr_level, name }.into_diag(dcx, level) } - &AttributeLintKind::MissingOptionsForOnUnknownItem => { - lints::MissingOptionsForOnUnknownItemAttr.into_diag(dcx, level) + &AttributeLintKind::MissingOptionsForOnUnknown => { + lints::MissingOptionsForOnUnknownAttr.into_diag(dcx, level) } } } diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 7bf9ce63348db..1ab659aecdd16 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -3647,9 +3647,9 @@ pub(crate) struct IgnoredDiagnosticOption { pub(crate) struct MissingOptionsForOnUnimplementedAttr; #[derive(Diagnostic)] -#[diag("missing options for `on_unknown_item` attribute")] +#[diag("missing options for `on_unknown` attribute")] #[help("at least one of the `message`, `note` and `label` options are expected")] -pub(crate) struct MissingOptionsForOnUnknownItemAttr; +pub(crate) struct MissingOptionsForOnUnknownAttr; #[derive(Diagnostic)] #[diag("missing options for `on_const` attribute")] @@ -3670,9 +3670,9 @@ pub(crate) struct MalformedOnUnimplementedAttrLint { } #[derive(Diagnostic)] -#[diag("malformed `on_unknown_item` attribute")] +#[diag("malformed `on_unknown` attribute")] #[help("only `message`, `note` and `label` are allowed as options")] -pub(crate) struct MalformedOnUnknownItemAttrLint { +pub(crate) struct MalformedOnUnknownAttrLint { #[label("invalid option found here")] pub span: Span, } diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index 7370efe40c861..8b7a6eaf643dd 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -764,7 +764,7 @@ pub enum AttributeLintKind { MalformedOnUnimplementedAttr { span: Span, }, - MalformedOnUnknownItemdAttr { + MalformedOnUnknownAttr { span: Span, }, MalformedOnConstAttr { @@ -788,7 +788,7 @@ pub enum AttributeLintKind { }, MissingOptionsForOnUnimplemented, MissingOptionsForOnConst, - MissingOptionsForOnUnknownItem, + MissingOptionsForOnUnknown, MissingOptionsForOnMove, OnMoveMalformedFormatLiterals { name: Symbol, diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index a92a3518c98e3..ba543d15f3043 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -72,8 +72,8 @@ struct DiagnosticOnConstOnlyForNonConstTraitImpls { struct DiagnosticOnMoveOnlyForAdt; #[derive(Diagnostic)] -#[diag("`#[diagnostic::on_unknown_item]` can only be applied to `use` statements")] -struct DiagnosticOnUnknownItemOnlyForImports { +#[diag("`#[diagnostic::on_unknown]` can only be applied to `use` statements")] +struct DiagnosticOnUnknownOnlyForImports { #[label("not an import")] item_span: Span, } @@ -221,7 +221,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { }, Attribute::Parsed(AttributeKind::DoNotRecommend{attr_span}) => {self.check_do_not_recommend(*attr_span, hir_id, target, item)}, Attribute::Parsed(AttributeKind::OnUnimplemented{span, directive}) => {self.check_diagnostic_on_unimplemented(*span, hir_id, target,directive.as_deref())}, - Attribute::Parsed(AttributeKind::OnUnknownItem { span, .. }) => { self.check_diagnostic_on_unknown_item(*span, hir_id, target) }, + Attribute::Parsed(AttributeKind::OnUnknown { span, .. }) => { self.check_diagnostic_on_unknown(*span, hir_id, target) }, Attribute::Parsed(AttributeKind::OnConst{span, ..}) => {self.check_diagnostic_on_const(*span, hir_id, target, item)} Attribute::Parsed(AttributeKind::OnMove { span, directive }) => { self.check_diagnostic_on_move(*span, hir_id, target, directive.as_deref()) @@ -662,15 +662,15 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } } - /// Checks if `#[diagnostic::on_unknown_item]` is applied to a trait impl - fn check_diagnostic_on_unknown_item(&self, attr_span: Span, hir_id: HirId, target: Target) { + /// Checks if `#[diagnostic::on_unknown]` is applied to a trait impl + fn check_diagnostic_on_unknown(&self, attr_span: Span, hir_id: HirId, target: Target) { if !matches!(target, Target::Use) { let item_span = self.tcx.hir_span(hir_id); self.tcx.emit_node_span_lint( MISPLACED_DIAGNOSTIC_ATTRIBUTES, hir_id, attr_span, - DiagnosticOnUnknownItemOnlyForImports { item_span }, + DiagnosticOnUnknownOnlyForImports { item_span }, ); } } diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index 6c41cc2090d8b..8320a99e437ce 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -32,7 +32,7 @@ use tracing::debug; use crate::Namespace::{MacroNS, TypeNS, ValueNS}; use crate::def_collector::collect_definitions; -use crate::imports::{ImportData, ImportKind, OnUnknownItemData}; +use crate::imports::{ImportData, ImportKind, OnUnknownData}; use crate::macros::{MacroRulesDecl, MacroRulesScope, MacroRulesScopeRef}; use crate::ref_mut::CmCell; use crate::{ @@ -545,7 +545,7 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> { root_id, vis, vis_span: item.vis.span, - on_unknown_item_attr: OnUnknownItemData::from_attrs(self.r.tcx, item), + on_unknown_attr: OnUnknownData::from_attrs(self.r.tcx, item), }); self.r.indeterminate_imports.push(import); @@ -1025,7 +1025,7 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> { module_path: Vec::new(), vis, vis_span: item.vis.span, - on_unknown_item_attr: OnUnknownItemData::from_attrs(self.r.tcx, item), + on_unknown_attr: OnUnknownData::from_attrs(self.r.tcx, item), }); if used { self.r.import_use_map.insert(import, Used::Other); @@ -1158,7 +1158,7 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> { module_path: Vec::new(), vis: Visibility::Restricted(CRATE_DEF_ID), vis_span: item.vis.span, - on_unknown_item_attr: OnUnknownItemData::from_attrs(this.r.tcx, item), + on_unknown_attr: OnUnknownData::from_attrs(this.r.tcx, item), }) }; @@ -1330,7 +1330,7 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> { module_path: Vec::new(), vis, vis_span: item.vis.span, - on_unknown_item_attr: OnUnknownItemData::from_attrs(self.r.tcx, item), + on_unknown_attr: OnUnknownData::from_attrs(self.r.tcx, item), }); self.r.import_use_map.insert(import, Used::Other); let import_decl = self.r.new_import_decl(decl, import); diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs index 846efdf22b97c..18db60167c27c 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -145,17 +145,17 @@ impl<'ra> std::fmt::Debug for ImportKind<'ra> { } #[derive(Debug, Clone, Default)] -pub(crate) struct OnUnknownItemData { +pub(crate) struct OnUnknownData { directive: Directive, } -impl OnUnknownItemData { - pub(crate) fn from_attrs<'tcx>(tcx: TyCtxt<'tcx>, item: &Item) -> Option { - if let Some(Attribute::Parsed(AttributeKind::OnUnknownItem { directive, .. })) = +impl OnUnknownData { + pub(crate) fn from_attrs<'tcx>(tcx: TyCtxt<'tcx>, item: &Item) -> Option { + if let Some(Attribute::Parsed(AttributeKind::OnUnknown { directive, .. })) = AttributeParser::parse_limited( tcx.sess, &item.attrs, - &[sym::diagnostic, sym::on_unknown_item], + &[sym::diagnostic, sym::on_unknown], item.span, item.id, Some(tcx.features()), @@ -215,10 +215,10 @@ pub(crate) struct ImportData<'ra> { /// Span of the visibility. pub vis_span: Span, - /// A `#[diagnostic::on_unknown_item]` attribute applied + /// A `#[diagnostic::on_unknown]` attribute applied /// to the given import. This allows crates to specify /// custom error messages for a specific import - pub on_unknown_item_attr: Option, + pub on_unknown_attr: Option, } /// All imports are unique and allocated on a same arena, @@ -317,7 +317,7 @@ struct UnresolvedImportError { segment: Option, /// comes from `PathRes::Failed { module }` module: Option, - on_unknown_item_attr: Option, + on_unknown_attr: Option, } // Reexports of the form `pub use foo as bar;` where `foo` is `extern crate foo;` @@ -734,7 +734,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { candidates: None, segment: None, module: None, - on_unknown_item_attr: import.on_unknown_item_attr.clone(), + on_unknown_attr: import.on_unknown_attr.clone(), }; errors.push((*import, err)) } @@ -859,8 +859,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { .collect::>(); let default_message = format!("unresolved import{} {}", pluralize!(paths.len()), paths.join(", "),); - let (message, label, notes) = if self.tcx.features().diagnostic_on_unknown_item() - && let Some(directive) = errors[0].1.on_unknown_item_attr.as_ref().map(|a| &a.directive) + let (message, label, notes) = if self.tcx.features().diagnostic_on_unknown() + && let Some(directive) = errors[0].1.on_unknown_attr.as_ref().map(|a| &a.directive) { let args = FormatArgs { this: paths.join(", "), @@ -1168,7 +1168,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { candidates: None, segment: Some(segment_name), module, - on_unknown_item_attr: import.on_unknown_item_attr.clone(), + on_unknown_attr: import.on_unknown_attr.clone(), }, None => UnresolvedImportError { span, @@ -1178,7 +1178,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { candidates: None, segment: Some(segment_name), module, - on_unknown_item_attr: import.on_unknown_item_attr.clone(), + on_unknown_attr: import.on_unknown_attr.clone(), }, }; return Some(err); @@ -1221,7 +1221,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { candidates: None, segment: None, module: None, - on_unknown_item_attr: None, + on_unknown_attr: None, }); } if let Some(max_vis) = max_vis.get() @@ -1444,7 +1444,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } }), segment: Some(ident.name), - on_unknown_item_attr: import.on_unknown_item_attr.clone(), + on_unknown_attr: import.on_unknown_attr.clone(), }) } else { // `resolve_ident_in_module` reported a privacy error. diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index d9c71ed2f4338..67a896bdd7557 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -717,7 +717,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { (sym::do_not_recommend, true), (sym::on_move, true), (sym::on_const, self.tcx.features().diagnostic_on_const()), - (sym::on_unknown_item, self.tcx.features().diagnostic_on_unknown_item()), + (sym::on_unknown, self.tcx.features().diagnostic_on_unknown()), ]; if res == Res::NonMacroAttr(NonMacroAttrKind::Tool) diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 3bf416a18061d..d4febd702b0e5 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -799,7 +799,7 @@ symbols! { diagnostic_namespace, diagnostic_on_const, diagnostic_on_move, - diagnostic_on_unknown_item, + diagnostic_on_unknown, dialect, direct, discriminant_kind, @@ -1415,7 +1415,7 @@ symbols! { on_const, on_move, on_unimplemented, - on_unknown_item, + on_unknown, opaque, opaque_module_name_placeholder: "", ops, diff --git a/tests/ui/diagnostic_namespace/on_unknown/incorrect-locations.rs b/tests/ui/diagnostic_namespace/on_unknown/incorrect-locations.rs new file mode 100644 index 0000000000000..b8852e7dd216c --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown/incorrect-locations.rs @@ -0,0 +1,52 @@ +//@ run-pass +#![allow(dead_code, unused_imports)] +#![feature(diagnostic_on_unknown)] + +#[diagnostic::on_unknown(message = "foo")] +//~^WARN `#[diagnostic::on_unknown]` can only be applied to `use` statements +extern crate std as other_std; + +#[diagnostic::on_unknown(message = "foo")] +//~^WARN `#[diagnostic::on_unknown]` can only be applied to `use` statements +const CONST: () = (); + +#[diagnostic::on_unknown(message = "foo")] +//~^WARN `#[diagnostic::on_unknown]` can only be applied to `use` statements +static STATIC: () = (); + +#[diagnostic::on_unknown(message = "foo")] +//~^WARN `#[diagnostic::on_unknown]` can only be applied to `use` statements +type Type = (); + +#[diagnostic::on_unknown(message = "foo")] +//~^WARN `#[diagnostic::on_unknown]` can only be applied to `use` statements +enum Enum {} + +#[diagnostic::on_unknown(message = "foo")] +//~^WARN `#[diagnostic::on_unknown]` can only be applied to `use` statements +impl Enum {} + +#[diagnostic::on_unknown(message = "foo")] +//~^WARN `#[diagnostic::on_unknown]` can only be applied to `use` statements +extern "C" {} + +#[diagnostic::on_unknown(message = "foo")] +//~^WARN `#[diagnostic::on_unknown]` can only be applied to `use` statements +fn fun() {} + +#[diagnostic::on_unknown(message = "foo")] +//~^WARN `#[diagnostic::on_unknown]` can only be applied to `use` statements +struct Struct {} + +#[diagnostic::on_unknown(message = "foo")] +//~^WARN `#[diagnostic::on_unknown]` can only be applied to `use` statements +trait Trait {} + +#[diagnostic::on_unknown(message = "foo")] +//~^WARN `#[diagnostic::on_unknown]` can only be applied to `use` statements +impl Trait for i32 {} + +#[diagnostic::on_unknown(message = "foo")] +use std::str::FromStr; + +fn main() {} diff --git a/tests/ui/diagnostic_namespace/on_unknown/incorrect-locations.stderr b/tests/ui/diagnostic_namespace/on_unknown/incorrect-locations.stderr new file mode 100644 index 0000000000000..33636e1fcfc3f --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown/incorrect-locations.stderr @@ -0,0 +1,103 @@ +warning: `#[diagnostic::on_unknown]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:5:1 + | +LL | #[diagnostic::on_unknown(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | extern crate std as other_std; + | ----------------------------- not an import + | + = note: `#[warn(misplaced_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default + +warning: `#[diagnostic::on_unknown]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:9:1 + | +LL | #[diagnostic::on_unknown(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | const CONST: () = (); + | --------------- not an import + +warning: `#[diagnostic::on_unknown]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:13:1 + | +LL | #[diagnostic::on_unknown(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | static STATIC: () = (); + | ----------------- not an import + +warning: `#[diagnostic::on_unknown]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:17:1 + | +LL | #[diagnostic::on_unknown(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | type Type = (); + | --------- not an import + +warning: `#[diagnostic::on_unknown]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:21:1 + | +LL | #[diagnostic::on_unknown(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | enum Enum {} + | --------- not an import + +warning: `#[diagnostic::on_unknown]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:25:1 + | +LL | #[diagnostic::on_unknown(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | impl Enum {} + | --------- not an import + +warning: `#[diagnostic::on_unknown]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:29:1 + | +LL | #[diagnostic::on_unknown(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | extern "C" {} + | ------------- not an import + +warning: `#[diagnostic::on_unknown]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:33:1 + | +LL | #[diagnostic::on_unknown(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | fn fun() {} + | -------- not an import + +warning: `#[diagnostic::on_unknown]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:37:1 + | +LL | #[diagnostic::on_unknown(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | struct Struct {} + | ------------- not an import + +warning: `#[diagnostic::on_unknown]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:41:1 + | +LL | #[diagnostic::on_unknown(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | trait Trait {} + | ----------- not an import + +warning: `#[diagnostic::on_unknown]` can only be applied to `use` statements + --> $DIR/incorrect-locations.rs:45:1 + | +LL | #[diagnostic::on_unknown(message = "foo")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | +LL | impl Trait for i32 {} + | ------------------ not an import + +warning: 11 warnings emitted + diff --git a/tests/ui/diagnostic_namespace/on_unknown_item/incorrect_format_string.rs b/tests/ui/diagnostic_namespace/on_unknown/incorrect_format_string.rs similarity index 67% rename from tests/ui/diagnostic_namespace/on_unknown_item/incorrect_format_string.rs rename to tests/ui/diagnostic_namespace/on_unknown/incorrect_format_string.rs index d7d6f1845060d..cdf0f1e89efc1 100644 --- a/tests/ui/diagnostic_namespace/on_unknown_item/incorrect_format_string.rs +++ b/tests/ui/diagnostic_namespace/on_unknown/incorrect_format_string.rs @@ -1,31 +1,31 @@ -#![feature(diagnostic_on_unknown_item)] +#![feature(diagnostic_on_unknown)] -#[diagnostic::on_unknown_item(message = "foo {}")] +#[diagnostic::on_unknown(message = "foo {}")] //~^ WARN: format arguments are not allowed here use std::does_not_exist; //~^ ERROR: foo {} -#[diagnostic::on_unknown_item(message = "foo {A}")] +#[diagnostic::on_unknown(message = "foo {A}")] //~^ WARN: format arguments are not allowed here use std::does_not_exist2; //~^ ERROR: foo {} -#[diagnostic::on_unknown_item(label = "foo {}")] +#[diagnostic::on_unknown(label = "foo {}")] //~^ WARN: format arguments are not allowed here use std::does_not_exist3; //~^ ERROR: unresolved import `std::does_not_exist3` -#[diagnostic::on_unknown_item(label = "foo {A}")] +#[diagnostic::on_unknown(label = "foo {A}")] //~^ WARN: format arguments are not allowed here use std::does_not_exist4; //~^ ERROR: unresolved import `std::does_not_exist4` -#[diagnostic::on_unknown_item(note = "foo {}")] +#[diagnostic::on_unknown(note = "foo {}")] //~^ WARN: format arguments are not allowed here use std::does_not_exist5; //~^ ERROR: unresolved import `std::does_not_exist5` -#[diagnostic::on_unknown_item(note = "foo {A}")] +#[diagnostic::on_unknown(note = "foo {A}")] //~^ WARN: format arguments are not allowed here use std::does_not_exist6; //~^ ERROR: unresolved import `std::does_not_exist6` diff --git a/tests/ui/diagnostic_namespace/on_unknown_item/incorrect_format_string.stderr b/tests/ui/diagnostic_namespace/on_unknown/incorrect_format_string.stderr similarity index 70% rename from tests/ui/diagnostic_namespace/on_unknown_item/incorrect_format_string.stderr rename to tests/ui/diagnostic_namespace/on_unknown/incorrect_format_string.stderr index e8551479287eb..2b942338ffb4e 100644 --- a/tests/ui/diagnostic_namespace/on_unknown_item/incorrect_format_string.stderr +++ b/tests/ui/diagnostic_namespace/on_unknown/incorrect_format_string.stderr @@ -43,51 +43,51 @@ LL | use std::does_not_exist6; = note: foo {} warning: format arguments are not allowed here - --> $DIR/incorrect_format_string.rs:3:47 + --> $DIR/incorrect_format_string.rs:3:42 | -LL | #[diagnostic::on_unknown_item(message = "foo {}")] - | ^ +LL | #[diagnostic::on_unknown(message = "foo {}")] + | ^ | = help: consider removing this format argument = note: `#[warn(malformed_diagnostic_format_literals)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default warning: format arguments are not allowed here - --> $DIR/incorrect_format_string.rs:8:47 + --> $DIR/incorrect_format_string.rs:8:42 | -LL | #[diagnostic::on_unknown_item(message = "foo {A}")] - | ^ +LL | #[diagnostic::on_unknown(message = "foo {A}")] + | ^ | = help: consider removing this format argument warning: format arguments are not allowed here - --> $DIR/incorrect_format_string.rs:13:45 + --> $DIR/incorrect_format_string.rs:13:40 | -LL | #[diagnostic::on_unknown_item(label = "foo {}")] - | ^ +LL | #[diagnostic::on_unknown(label = "foo {}")] + | ^ | = help: consider removing this format argument warning: format arguments are not allowed here - --> $DIR/incorrect_format_string.rs:18:45 + --> $DIR/incorrect_format_string.rs:18:40 | -LL | #[diagnostic::on_unknown_item(label = "foo {A}")] - | ^ +LL | #[diagnostic::on_unknown(label = "foo {A}")] + | ^ | = help: consider removing this format argument warning: format arguments are not allowed here - --> $DIR/incorrect_format_string.rs:23:44 + --> $DIR/incorrect_format_string.rs:23:39 | -LL | #[diagnostic::on_unknown_item(note = "foo {}")] - | ^ +LL | #[diagnostic::on_unknown(note = "foo {}")] + | ^ | = help: consider removing this format argument warning: format arguments are not allowed here - --> $DIR/incorrect_format_string.rs:28:44 + --> $DIR/incorrect_format_string.rs:28:39 | -LL | #[diagnostic::on_unknown_item(note = "foo {A}")] - | ^ +LL | #[diagnostic::on_unknown(note = "foo {A}")] + | ^ | = help: consider removing this format argument diff --git a/tests/ui/diagnostic_namespace/on_unknown/malformed_attribute.rs b/tests/ui/diagnostic_namespace/on_unknown/malformed_attribute.rs new file mode 100644 index 0000000000000..d8fcd1336bce1 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown/malformed_attribute.rs @@ -0,0 +1,19 @@ +#![feature(diagnostic_on_unknown)] +#[diagnostic::on_unknown] +//~^WARN missing options for `on_unknown` attribute +use std::str::FromStr; + +#[diagnostic::on_unknown(foo = "bar", message = "foo")] +//~^WARN malformed `on_unknown` attribute +use std::str::Bytes; + +#[diagnostic::on_unknown(label = "foo", label = "bar")] +//~^WARN `label` is ignored due to previous definition of `label` +use std::str::Chars; + +#[diagnostic::on_unknown(message = "Foo", message = "Bar")] +//~^WARN `message` is ignored due to previous definition of `message` +use std::str::NotExisting; +//~^ERROR Foo + +fn main() {} diff --git a/tests/ui/diagnostic_namespace/on_unknown/malformed_attribute.stderr b/tests/ui/diagnostic_namespace/on_unknown/malformed_attribute.stderr new file mode 100644 index 0000000000000..319d45c88c429 --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_unknown/malformed_attribute.stderr @@ -0,0 +1,44 @@ +error[E0432]: Foo + --> $DIR/malformed_attribute.rs:16:5 + | +LL | use std::str::NotExisting; + | ^^^^^^^^^^^^^^^^^^^^^ no `NotExisting` in `str` + | + = note: unresolved import `std::str::NotExisting` + +warning: missing options for `on_unknown` attribute + --> $DIR/malformed_attribute.rs:2:1 + | +LL | #[diagnostic::on_unknown] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: at least one of the `message`, `note` and `label` options are expected + = note: `#[warn(malformed_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default + +warning: malformed `on_unknown` attribute + --> $DIR/malformed_attribute.rs:6:26 + | +LL | #[diagnostic::on_unknown(foo = "bar", message = "foo")] + | ^^^^^^^^^^^ invalid option found here + | + = help: only `message`, `note` and `label` are allowed as options + +warning: `label` is ignored due to previous definition of `label` + --> $DIR/malformed_attribute.rs:10:41 + | +LL | #[diagnostic::on_unknown(label = "foo", label = "bar")] + | ------------- ^^^^^^^^^^^^^ `label` is later redundantly declared here + | | + | `label` is first declared here + +warning: `message` is ignored due to previous definition of `message` + --> $DIR/malformed_attribute.rs:14:43 + | +LL | #[diagnostic::on_unknown(message = "Foo", message = "Bar")] + | --------------- ^^^^^^^^^^^^^^^ `message` is later redundantly declared here + | | + | `message` is first declared here + +error: aborting due to 1 previous error; 4 warnings emitted + +For more information about this error, try `rustc --explain E0432`. diff --git a/tests/ui/diagnostic_namespace/on_unknown_item/multiple_errors.rs b/tests/ui/diagnostic_namespace/on_unknown/multiple_errors.rs similarity index 83% rename from tests/ui/diagnostic_namespace/on_unknown_item/multiple_errors.rs rename to tests/ui/diagnostic_namespace/on_unknown/multiple_errors.rs index 431ab6cdd831a..3ccf2fc5f6cab 100644 --- a/tests/ui/diagnostic_namespace/on_unknown_item/multiple_errors.rs +++ b/tests/ui/diagnostic_namespace/on_unknown/multiple_errors.rs @@ -1,7 +1,7 @@ -#![feature(diagnostic_on_unknown_item)] +#![feature(diagnostic_on_unknown)] mod test1 { - #[diagnostic::on_unknown_item( + #[diagnostic::on_unknown( message = "custom message", label = "custom label", note = "custom note" @@ -11,7 +11,7 @@ mod test1 { } mod test2 { - #[diagnostic::on_unknown_item( + #[diagnostic::on_unknown( message = "custom message", label = "custom label", note = "custom note" @@ -21,7 +21,7 @@ mod test2 { } mod test3 { - #[diagnostic::on_unknown_item( + #[diagnostic::on_unknown( message = "custom message", label = "custom label", note = "custom note" @@ -34,7 +34,7 @@ mod test3 { } mod test4 { - #[diagnostic::on_unknown_item( + #[diagnostic::on_unknown( message = "custom message", label = "custom label", note = "custom note" diff --git a/tests/ui/diagnostic_namespace/on_unknown_item/multiple_errors.stderr b/tests/ui/diagnostic_namespace/on_unknown/multiple_errors.stderr similarity index 100% rename from tests/ui/diagnostic_namespace/on_unknown_item/multiple_errors.stderr rename to tests/ui/diagnostic_namespace/on_unknown/multiple_errors.stderr diff --git a/tests/ui/diagnostic_namespace/on_unknown_item/unknown_import.rs b/tests/ui/diagnostic_namespace/on_unknown/unknown_import.rs similarity index 75% rename from tests/ui/diagnostic_namespace/on_unknown_item/unknown_import.rs rename to tests/ui/diagnostic_namespace/on_unknown/unknown_import.rs index 5af79af23c2cc..f2b0f059bb0a8 100644 --- a/tests/ui/diagnostic_namespace/on_unknown_item/unknown_import.rs +++ b/tests/ui/diagnostic_namespace/on_unknown/unknown_import.rs @@ -1,9 +1,9 @@ -#![feature(diagnostic_on_unknown_item)] +#![feature(diagnostic_on_unknown)] pub mod foo { pub struct Bar; } -#[diagnostic::on_unknown_item( +#[diagnostic::on_unknown( message = "first message", label = "first label", note = "custom note", diff --git a/tests/ui/diagnostic_namespace/on_unknown_item/unknown_import.stderr b/tests/ui/diagnostic_namespace/on_unknown/unknown_import.stderr similarity index 100% rename from tests/ui/diagnostic_namespace/on_unknown_item/unknown_import.stderr rename to tests/ui/diagnostic_namespace/on_unknown/unknown_import.stderr diff --git a/tests/ui/diagnostic_namespace/on_unknown_item/incorrect-locations.rs b/tests/ui/diagnostic_namespace/on_unknown_item/incorrect-locations.rs deleted file mode 100644 index 7b450f2fd4fac..0000000000000 --- a/tests/ui/diagnostic_namespace/on_unknown_item/incorrect-locations.rs +++ /dev/null @@ -1,52 +0,0 @@ -//@ run-pass -#![allow(dead_code, unused_imports)] -#![feature(diagnostic_on_unknown_item)] - -#[diagnostic::on_unknown_item(message = "foo")] -//~^WARN `#[diagnostic::on_unknown_item]` can only be applied to `use` statements -extern crate std as other_std; - -#[diagnostic::on_unknown_item(message = "foo")] -//~^WARN `#[diagnostic::on_unknown_item]` can only be applied to `use` statements -const CONST: () = (); - -#[diagnostic::on_unknown_item(message = "foo")] -//~^WARN `#[diagnostic::on_unknown_item]` can only be applied to `use` statements -static STATIC: () = (); - -#[diagnostic::on_unknown_item(message = "foo")] -//~^WARN `#[diagnostic::on_unknown_item]` can only be applied to `use` statements -type Type = (); - -#[diagnostic::on_unknown_item(message = "foo")] -//~^WARN `#[diagnostic::on_unknown_item]` can only be applied to `use` statements -enum Enum {} - -#[diagnostic::on_unknown_item(message = "foo")] -//~^WARN `#[diagnostic::on_unknown_item]` can only be applied to `use` statements -impl Enum {} - -#[diagnostic::on_unknown_item(message = "foo")] -//~^WARN `#[diagnostic::on_unknown_item]` can only be applied to `use` statements -extern "C" {} - -#[diagnostic::on_unknown_item(message = "foo")] -//~^WARN `#[diagnostic::on_unknown_item]` can only be applied to `use` statements -fn fun() {} - -#[diagnostic::on_unknown_item(message = "foo")] -//~^WARN `#[diagnostic::on_unknown_item]` can only be applied to `use` statements -struct Struct {} - -#[diagnostic::on_unknown_item(message = "foo")] -//~^WARN `#[diagnostic::on_unknown_item]` can only be applied to `use` statements -trait Trait {} - -#[diagnostic::on_unknown_item(message = "foo")] -//~^WARN `#[diagnostic::on_unknown_item]` can only be applied to `use` statements -impl Trait for i32 {} - -#[diagnostic::on_unknown_item(message = "foo")] -use std::str::FromStr; - -fn main() {} diff --git a/tests/ui/diagnostic_namespace/on_unknown_item/incorrect-locations.stderr b/tests/ui/diagnostic_namespace/on_unknown_item/incorrect-locations.stderr deleted file mode 100644 index b09b6c90fd0e2..0000000000000 --- a/tests/ui/diagnostic_namespace/on_unknown_item/incorrect-locations.stderr +++ /dev/null @@ -1,103 +0,0 @@ -warning: `#[diagnostic::on_unknown_item]` can only be applied to `use` statements - --> $DIR/incorrect-locations.rs:5:1 - | -LL | #[diagnostic::on_unknown_item(message = "foo")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -LL | -LL | extern crate std as other_std; - | ----------------------------- not an import - | - = note: `#[warn(misplaced_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default - -warning: `#[diagnostic::on_unknown_item]` can only be applied to `use` statements - --> $DIR/incorrect-locations.rs:9:1 - | -LL | #[diagnostic::on_unknown_item(message = "foo")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -LL | -LL | const CONST: () = (); - | --------------- not an import - -warning: `#[diagnostic::on_unknown_item]` can only be applied to `use` statements - --> $DIR/incorrect-locations.rs:13:1 - | -LL | #[diagnostic::on_unknown_item(message = "foo")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -LL | -LL | static STATIC: () = (); - | ----------------- not an import - -warning: `#[diagnostic::on_unknown_item]` can only be applied to `use` statements - --> $DIR/incorrect-locations.rs:17:1 - | -LL | #[diagnostic::on_unknown_item(message = "foo")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -LL | -LL | type Type = (); - | --------- not an import - -warning: `#[diagnostic::on_unknown_item]` can only be applied to `use` statements - --> $DIR/incorrect-locations.rs:21:1 - | -LL | #[diagnostic::on_unknown_item(message = "foo")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -LL | -LL | enum Enum {} - | --------- not an import - -warning: `#[diagnostic::on_unknown_item]` can only be applied to `use` statements - --> $DIR/incorrect-locations.rs:25:1 - | -LL | #[diagnostic::on_unknown_item(message = "foo")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -LL | -LL | impl Enum {} - | --------- not an import - -warning: `#[diagnostic::on_unknown_item]` can only be applied to `use` statements - --> $DIR/incorrect-locations.rs:29:1 - | -LL | #[diagnostic::on_unknown_item(message = "foo")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -LL | -LL | extern "C" {} - | ------------- not an import - -warning: `#[diagnostic::on_unknown_item]` can only be applied to `use` statements - --> $DIR/incorrect-locations.rs:33:1 - | -LL | #[diagnostic::on_unknown_item(message = "foo")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -LL | -LL | fn fun() {} - | -------- not an import - -warning: `#[diagnostic::on_unknown_item]` can only be applied to `use` statements - --> $DIR/incorrect-locations.rs:37:1 - | -LL | #[diagnostic::on_unknown_item(message = "foo")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -LL | -LL | struct Struct {} - | ------------- not an import - -warning: `#[diagnostic::on_unknown_item]` can only be applied to `use` statements - --> $DIR/incorrect-locations.rs:41:1 - | -LL | #[diagnostic::on_unknown_item(message = "foo")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -LL | -LL | trait Trait {} - | ----------- not an import - -warning: `#[diagnostic::on_unknown_item]` can only be applied to `use` statements - --> $DIR/incorrect-locations.rs:45:1 - | -LL | #[diagnostic::on_unknown_item(message = "foo")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -LL | -LL | impl Trait for i32 {} - | ------------------ not an import - -warning: 11 warnings emitted - diff --git a/tests/ui/diagnostic_namespace/on_unknown_item/malformed_attribute.rs b/tests/ui/diagnostic_namespace/on_unknown_item/malformed_attribute.rs deleted file mode 100644 index 4ffa9ffe37b5b..0000000000000 --- a/tests/ui/diagnostic_namespace/on_unknown_item/malformed_attribute.rs +++ /dev/null @@ -1,19 +0,0 @@ -#![feature(diagnostic_on_unknown_item)] -#[diagnostic::on_unknown_item] -//~^WARN missing options for `on_unknown_item` attribute -use std::str::FromStr; - -#[diagnostic::on_unknown_item(foo = "bar", message = "foo")] -//~^WARN malformed `on_unknown_item` attribute -use std::str::Bytes; - -#[diagnostic::on_unknown_item(label = "foo", label = "bar")] -//~^WARN `label` is ignored due to previous definition of `label` -use std::str::Chars; - -#[diagnostic::on_unknown_item(message = "Foo", message = "Bar")] -//~^WARN `message` is ignored due to previous definition of `message` -use std::str::NotExisting; -//~^ERROR Foo - -fn main() {} diff --git a/tests/ui/diagnostic_namespace/on_unknown_item/malformed_attribute.stderr b/tests/ui/diagnostic_namespace/on_unknown_item/malformed_attribute.stderr deleted file mode 100644 index 42caaa9354afa..0000000000000 --- a/tests/ui/diagnostic_namespace/on_unknown_item/malformed_attribute.stderr +++ /dev/null @@ -1,44 +0,0 @@ -error[E0432]: Foo - --> $DIR/malformed_attribute.rs:16:5 - | -LL | use std::str::NotExisting; - | ^^^^^^^^^^^^^^^^^^^^^ no `NotExisting` in `str` - | - = note: unresolved import `std::str::NotExisting` - -warning: missing options for `on_unknown_item` attribute - --> $DIR/malformed_attribute.rs:2:1 - | -LL | #[diagnostic::on_unknown_item] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = help: at least one of the `message`, `note` and `label` options are expected - = note: `#[warn(malformed_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default - -warning: malformed `on_unknown_item` attribute - --> $DIR/malformed_attribute.rs:6:31 - | -LL | #[diagnostic::on_unknown_item(foo = "bar", message = "foo")] - | ^^^^^^^^^^^ invalid option found here - | - = help: only `message`, `note` and `label` are allowed as options - -warning: `label` is ignored due to previous definition of `label` - --> $DIR/malformed_attribute.rs:10:46 - | -LL | #[diagnostic::on_unknown_item(label = "foo", label = "bar")] - | ------------- ^^^^^^^^^^^^^ `label` is later redundantly declared here - | | - | `label` is first declared here - -warning: `message` is ignored due to previous definition of `message` - --> $DIR/malformed_attribute.rs:14:48 - | -LL | #[diagnostic::on_unknown_item(message = "Foo", message = "Bar")] - | --------------- ^^^^^^^^^^^^^^^ `message` is later redundantly declared here - | | - | `message` is first declared here - -error: aborting due to 1 previous error; 4 warnings emitted - -For more information about this error, try `rustc --explain E0432`. diff --git a/tests/ui/feature-gates/feature-gate-diagnostic-on-unknown-item.rs b/tests/ui/feature-gates/feature-gate-diagnostic-on-unknown.rs similarity index 76% rename from tests/ui/feature-gates/feature-gate-diagnostic-on-unknown-item.rs rename to tests/ui/feature-gates/feature-gate-diagnostic-on-unknown.rs index fffb54636cfb7..11cc0d50e0c94 100644 --- a/tests/ui/feature-gates/feature-gate-diagnostic-on-unknown-item.rs +++ b/tests/ui/feature-gates/feature-gate-diagnostic-on-unknown.rs @@ -1,6 +1,6 @@ #![deny(warnings)] -#[diagnostic::on_unknown_item(message = "Tada")] +#[diagnostic::on_unknown(message = "Tada")] //~^ ERROR: unknown diagnostic attribute use std::vec::NotExisting; //~^ ERROR: unresolved import `std::vec::NotExisting` diff --git a/tests/ui/feature-gates/feature-gate-diagnostic-on-unknown-item.stderr b/tests/ui/feature-gates/feature-gate-diagnostic-on-unknown.stderr similarity index 64% rename from tests/ui/feature-gates/feature-gate-diagnostic-on-unknown-item.stderr rename to tests/ui/feature-gates/feature-gate-diagnostic-on-unknown.stderr index 10662eb83b4a5..f6d7ffadaceae 100644 --- a/tests/ui/feature-gates/feature-gate-diagnostic-on-unknown-item.stderr +++ b/tests/ui/feature-gates/feature-gate-diagnostic-on-unknown.stderr @@ -1,17 +1,17 @@ error[E0432]: unresolved import `std::vec::NotExisting` - --> $DIR/feature-gate-diagnostic-on-unknown-item.rs:5:5 + --> $DIR/feature-gate-diagnostic-on-unknown.rs:5:5 | LL | use std::vec::NotExisting; | ^^^^^^^^^^^^^^^^^^^^^ no `NotExisting` in `vec` error: unknown diagnostic attribute - --> $DIR/feature-gate-diagnostic-on-unknown-item.rs:3:15 + --> $DIR/feature-gate-diagnostic-on-unknown.rs:3:15 | -LL | #[diagnostic::on_unknown_item(message = "Tada")] - | ^^^^^^^^^^^^^^^ +LL | #[diagnostic::on_unknown(message = "Tada")] + | ^^^^^^^^^^ | note: the lint level is defined here - --> $DIR/feature-gate-diagnostic-on-unknown-item.rs:1:9 + --> $DIR/feature-gate-diagnostic-on-unknown.rs:1:9 | LL | #![deny(warnings)] | ^^^^^^^^ From d024e5762f188ac90f79c7b8030315c854666cb5 Mon Sep 17 00:00:00 2001 From: malezjaa Date: Thu, 9 Apr 2026 16:12:51 +0200 Subject: [PATCH 142/144] Fix if branch in op.rs --- compiler/rustc_hir_typeck/src/op.rs | 38 +++++++++++++++-------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/op.rs b/compiler/rustc_hir_typeck/src/op.rs index 4eac1fc004cc8..8b08c7b68b784 100644 --- a/compiler/rustc_hir_typeck/src/op.rs +++ b/compiler/rustc_hir_typeck/src/op.rs @@ -294,24 +294,26 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.apply_adjustments(lhs_expr, vec![autoref]); } - if let ty::Ref(_, _, mutbl) = method.sig.inputs()[1].kind() { - // Allow two-phase borrows for binops in initial deployment - // since they desugar to methods - let mutbl = AutoBorrowMutability::new(*mutbl, AllowTwoPhase::Yes); - let autoref = Adjustment { - kind: Adjust::Borrow(AutoBorrow::Ref(mutbl)), - target: method.sig.inputs()[1], - }; - // HACK(eddyb) Bypass checks due to reborrows being in - // some cases applied on the RHS, on top of which we need - // to autoref, which is not allowed by apply_adjustments. - // self.apply_adjustments(rhs_expr, vec![autoref]); - self.typeck_results - .borrow_mut() - .adjustments_mut() - .entry(rhs_expr.hir_id) - .or_default() - .push(autoref); + if by_ref_binop { + if let ty::Ref(_, _, mutbl) = method.sig.inputs()[1].kind() { + // Allow two-phase borrows for binops in initial deployment + // since they desugar to methods + let mutbl = AutoBorrowMutability::new(*mutbl, AllowTwoPhase::Yes); + let autoref = Adjustment { + kind: Adjust::Borrow(AutoBorrow::Ref(mutbl)), + target: method.sig.inputs()[1], + }; + // HACK(eddyb) Bypass checks due to reborrows being in + // some cases applied on the RHS, on top of which we need + // to autoref, which is not allowed by apply_adjustments. + // self.apply_adjustments(rhs_expr, vec![autoref]); + self.typeck_results + .borrow_mut() + .adjustments_mut() + .entry(rhs_expr.hir_id) + .or_default() + .push(autoref); + } } } From 95852ab2ae031e4a1c7a25062e72570e5c2e1eff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lauren=C8=9Biu=20Nicola?= Date: Thu, 9 Apr 2026 17:31:54 +0300 Subject: [PATCH 143/144] Prepare for merging from rust-lang/rust This updates the rust-version file to 4c4205163abcbd08948b3efab796c543ba1ea687. --- src/tools/rust-analyzer/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/rust-version b/src/tools/rust-analyzer/rust-version index a89983e1de3de..38f153f78d026 100644 --- a/src/tools/rust-analyzer/rust-version +++ b/src/tools/rust-analyzer/rust-version @@ -1 +1 @@ -80ad55752e5ae6c2d1bc143b819eb8d1c00167d1 +4c4205163abcbd08948b3efab796c543ba1ea687 From dbc08cfd5798ce91930e11da36145f84bfb7ce9c Mon Sep 17 00:00:00 2001 From: xonx <119700621+xonx4l@users.noreply.github.com> Date: Mon, 19 Jan 2026 17:39:46 +0000 Subject: [PATCH 144/144] main-termination --- .../rustc_hir_analysis/src/check/entry.rs | 67 +++++++++---------- .../rustc_hir_analysis/src/check/wfcheck.rs | 3 +- .../main-termination-lifetime-issue-148421.rs | 17 +++++ ...n-termination-lifetime-issue-148421.stderr | 11 +++ 4 files changed, 63 insertions(+), 35 deletions(-) create mode 100644 tests/ui/typeck/main-termination-lifetime-issue-148421.rs create mode 100644 tests/ui/typeck/main-termination-lifetime-issue-148421.stderr diff --git a/compiler/rustc_hir_analysis/src/check/entry.rs b/compiler/rustc_hir_analysis/src/check/entry.rs index a6dae521db884..4c72f5a654e1d 100644 --- a/compiler/rustc_hir_analysis/src/check/entry.rs +++ b/compiler/rustc_hir_analysis/src/check/entry.rs @@ -7,22 +7,23 @@ use rustc_infer::infer::TyCtxtInferExt; use rustc_middle::span_bug; use rustc_middle::ty::{self, TyCtxt, TypingMode}; use rustc_session::config::EntryFnType; -use rustc_span::Span; use rustc_span::def_id::{CRATE_DEF_ID, DefId, LocalDefId}; +use rustc_span::{ErrorGuaranteed, Span}; use rustc_trait_selection::error_reporting::InferCtxtErrorExt; +use rustc_trait_selection::regions::InferCtxtRegionExt; use rustc_trait_selection::traits::{self, ObligationCause, ObligationCauseCode}; use super::check_function_signature; use crate::errors; -pub(crate) fn check_for_entry_fn(tcx: TyCtxt<'_>) { +pub(crate) fn check_for_entry_fn(tcx: TyCtxt<'_>) -> Result<(), ErrorGuaranteed> { match tcx.entry_fn(()) { Some((def_id, EntryFnType::Main { .. })) => check_main_fn_ty(tcx, def_id), - _ => {} + _ => Ok(()), } } -fn check_main_fn_ty(tcx: TyCtxt<'_>, main_def_id: DefId) { +fn check_main_fn_ty(tcx: TyCtxt<'_>, main_def_id: DefId) -> Result<(), ErrorGuaranteed> { let main_fnsig = tcx.fn_sig(main_def_id).instantiate_identity(); let main_span = tcx.def_span(main_def_id); @@ -87,20 +88,20 @@ fn check_main_fn_ty(tcx: TyCtxt<'_>, main_def_id: DefId) { } } - let mut error = false; let main_diagnostics_def_id = main_fn_diagnostics_def_id(tcx, main_def_id, main_span); let main_asyncness = tcx.asyncness(main_def_id); if main_asyncness.is_async() { let asyncness_span = main_fn_asyncness_span(tcx, main_def_id); - tcx.dcx() - .emit_err(errors::MainFunctionAsync { span: main_span, asyncness: asyncness_span }); - error = true; + return Err(tcx + .dcx() + .emit_err(errors::MainFunctionAsync { span: main_span, asyncness: asyncness_span })); } if let Some(attr_span) = find_attr!(tcx, main_def_id, TrackCaller(span) => *span) { - tcx.dcx().emit_err(errors::TrackCallerOnMain { span: attr_span, annotated: main_span }); - error = true; + return Err(tcx + .dcx() + .emit_err(errors::TrackCallerOnMain { span: attr_span, annotated: main_span })); } if !tcx.codegen_fn_attrs(main_def_id).target_features.is_empty() @@ -108,12 +109,7 @@ fn check_main_fn_ty(tcx: TyCtxt<'_>, main_def_id: DefId) { && !tcx.sess.target.is_like_wasm && !tcx.sess.opts.actually_rustdoc { - tcx.dcx().emit_err(errors::TargetFeatureOnMain { main: main_span }); - error = true; - } - - if error { - return; + return Err(tcx.dcx().emit_err(errors::TargetFeatureOnMain { main: main_span })); } // Main should have no WC, so empty param env is OK here. @@ -123,8 +119,9 @@ fn check_main_fn_ty(tcx: TyCtxt<'_>, main_def_id: DefId) { let return_ty = main_fnsig.output(); let return_ty_span = main_fn_return_type_span(tcx, main_def_id).unwrap_or(main_span); let Some(return_ty) = return_ty.no_bound_vars() else { - tcx.dcx().emit_err(errors::MainFunctionReturnTypeGeneric { span: return_ty_span }); - return; + return Err(tcx + .dcx() + .emit_err(errors::MainFunctionReturnTypeGeneric { span: return_ty_span })); }; let infcx = tcx.infer_ctxt().build(TypingMode::non_body_analysis()); let cause = traits::ObligationCause::new( @@ -137,8 +134,16 @@ fn check_main_fn_ty(tcx: TyCtxt<'_>, main_def_id: DefId) { ocx.register_bound(cause, param_env, norm_return_ty, term_did); let errors = ocx.evaluate_obligations_error_on_ambiguity(); if !errors.is_empty() { - infcx.err_ctxt().report_fulfillment_errors(errors); - error = true; + return Err(infcx.err_ctxt().report_fulfillment_errors(errors)); + } + + let region_errors = + infcx.resolve_regions(main_diagnostics_def_id, param_env, ty::List::empty()); + + if !region_errors.is_empty() { + return Err(infcx + .err_ctxt() + .report_region_errors(main_diagnostics_def_id, ®ion_errors)); } // now we can take the return type of the given main function expected_return_type = norm_return_ty; @@ -147,10 +152,6 @@ fn check_main_fn_ty(tcx: TyCtxt<'_>, main_def_id: DefId) { expected_return_type = tcx.types.unit; } - if error { - return; - } - let expected_sig = ty::Binder::dummy(tcx.mk_fn_sig( [], expected_return_type, @@ -159,7 +160,7 @@ fn check_main_fn_ty(tcx: TyCtxt<'_>, main_def_id: DefId) { ExternAbi::Rust, )); - if check_function_signature( + check_function_signature( tcx, ObligationCause::new( main_span, @@ -168,26 +169,24 @@ fn check_main_fn_ty(tcx: TyCtxt<'_>, main_def_id: DefId) { ), main_def_id, expected_sig, - ) - .is_err() - { - return; - } + )?; let main_fn_generics = tcx.generics_of(main_def_id); let main_fn_predicates = tcx.predicates_of(main_def_id); if main_fn_generics.count() != 0 || !main_fnsig.bound_vars().is_empty() { let generics_param_span = main_fn_generics_params_span(tcx, main_def_id); - tcx.dcx().emit_err(errors::MainFunctionGenericParameters { + return Err(tcx.dcx().emit_err(errors::MainFunctionGenericParameters { span: generics_param_span.unwrap_or(main_span), label_span: generics_param_span, - }); + })); } else if !main_fn_predicates.predicates.is_empty() { // generics may bring in implicit predicates, so we skip this check if generics is present. let generics_where_clauses_span = main_fn_where_clauses_span(tcx, main_def_id); - tcx.dcx().emit_err(errors::WhereClauseOnMain { + return Err(tcx.dcx().emit_err(errors::WhereClauseOnMain { span: generics_where_clauses_span.unwrap_or(main_span), generics_span: generics_where_clauses_span, - }); + })); } + + Ok(()) } diff --git a/compiler/rustc_hir_analysis/src/check/wfcheck.rs b/compiler/rustc_hir_analysis/src/check/wfcheck.rs index 5656c4566d9ff..734154238b1f7 100644 --- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs +++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs @@ -2350,7 +2350,8 @@ pub(super) fn check_type_wf(tcx: TyCtxt<'_>, (): ()) -> Result<(), ErrorGuarante })) .and(items.par_nested_bodies(|item| tcx.ensure_result().check_well_formed(item))) .and(items.par_opaques(|item| tcx.ensure_result().check_well_formed(item))); - super::entry::check_for_entry_fn(tcx); + + super::entry::check_for_entry_fn(tcx)?; res } diff --git a/tests/ui/typeck/main-termination-lifetime-issue-148421.rs b/tests/ui/typeck/main-termination-lifetime-issue-148421.rs new file mode 100644 index 0000000000000..ce8a36cc2932f --- /dev/null +++ b/tests/ui/typeck/main-termination-lifetime-issue-148421.rs @@ -0,0 +1,17 @@ +// This test checks that the compiler correctly handles lifetime requirements +// on the `Termination` trait for the `main` function return type. +// See https://github.com/rust-lang/rust/issues/148421 + +use std::process::ExitCode; +use std::process::Termination; + +trait IsStatic {} +impl<'a: 'static> IsStatic for &'a () {} + +struct Thing; + +impl Termination for Thing where for<'a> &'a (): IsStatic { + fn report(self) -> ExitCode { panic!() } +} + +fn main() -> Thing { Thing } //~ ERROR implementation of `IsStatic` is not general enough diff --git a/tests/ui/typeck/main-termination-lifetime-issue-148421.stderr b/tests/ui/typeck/main-termination-lifetime-issue-148421.stderr new file mode 100644 index 0000000000000..f0bb77cfe4c8c --- /dev/null +++ b/tests/ui/typeck/main-termination-lifetime-issue-148421.stderr @@ -0,0 +1,11 @@ +error: implementation of `IsStatic` is not general enough + --> $DIR/main-termination-lifetime-issue-148421.rs:17:14 + | +LL | fn main() -> Thing { Thing } + | ^^^^^ implementation of `IsStatic` is not general enough + | + = note: `IsStatic` would have to be implemented for the type `&'0 ()`, for any lifetime `'0`... + = note: ...but `IsStatic` is actually implemented for the type `&'1 ()`, for some specific lifetime `'1` + +error: aborting due to 1 previous error +