From d119977fad9c5c667371f1bf22a4dde206ff8cfc Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Wed, 6 May 2026 23:23:59 +0300 Subject: [PATCH 01/63] Diagnose inactive code in macros But only if the source is code passed to the macro, not code inside the macro. --- .../src/handlers/inactive_code.rs | 59 +++++++++++++++++-- 1 file changed, 55 insertions(+), 4 deletions(-) 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 09f3e8bfb319b..71cac6af1346f 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 @@ -10,10 +10,8 @@ pub(crate) fn inactive_code( ctx: &DiagnosticsContext<'_, '_>, d: &hir::InactiveCode, ) -> Option { - // If there's inactive code somewhere in a macro, don't propagate to the call-site. - if d.node.file_id.is_macro() { - return None; - } + // If there's inactive code somewhere in a macro that doesn't map to something in the call, don't propagate to the call-site. + d.node.map(|it| it.text_range()).original_node_file_range_rooted_opt(ctx.db())?; let inactive = DnfExpr::new(&d.cfg).why_inactive(&d.opts); let mut message = "code is inactive due to #[cfg] directives".to_owned(); @@ -252,4 +250,57 @@ fn foo() {} ide_db::FileRange { file_id: file_id.file_id(&db), range: full_file_range }, ); } + + #[test] + fn cfg_in_macro_does_not_diagnose_the_whole_call() { + check( + r#" +macro_rules! m { + ($e:item) => { + #[cfg(false)] + const _: () = (); + + $e + }; +} + +m! { + fn foo() {} +} + "#, + ); + } + + #[test] + fn in_macro() { + check( + r#" +macro_rules! m { + ($e:item) => { + $e + }; +} + +m! { + #[cfg(false)] fn foo() {} + // ^^^^^^^^^^^^^^^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: false is disabled +} + "#, + ); + check( + r#" +macro_rules! m { + ($e:item) => { + #[cfg(false)] + $e + }; +} + +m! { + fn foo() {} + // ^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: false is disabled +} + "#, + ); + } } From be1869080454203c0ee90ade616d87ba6d5eb2d6 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Wed, 6 May 2026 23:48:08 +0300 Subject: [PATCH 02/63] Allow disabling the inactive-code diagnostic in code With `#[allow(rust_analyzer::inactive_code)]`. --- src/tools/rust-analyzer/Cargo.lock | 1 + .../crates/ide-diagnostics/Cargo.toml | 1 + .../src/handlers/inactive_code.rs | 30 +++++++++-- .../src/handlers/mismatched_arg_count.rs | 4 ++ .../src/handlers/no_such_field.rs | 8 +++ .../crates/ide-diagnostics/src/lib.rs | 54 +++++++++++++------ .../src/tests/overly_long_real_world_cases.rs | 1 + 7 files changed, 79 insertions(+), 20 deletions(-) diff --git a/src/tools/rust-analyzer/Cargo.lock b/src/tools/rust-analyzer/Cargo.lock index be9a8c491572f..00a212f7c4c2c 100644 --- a/src/tools/rust-analyzer/Cargo.lock +++ b/src/tools/rust-analyzer/Cargo.lock @@ -1151,6 +1151,7 @@ dependencies = [ "itertools 0.14.0", "paths", "serde_json", + "smallvec", "stdx", "syntax", "test-fixture", diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/Cargo.toml b/src/tools/rust-analyzer/crates/ide-diagnostics/Cargo.toml index ddf5999036d21..ce836197571a0 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/Cargo.toml +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/Cargo.toml @@ -18,6 +18,7 @@ either.workspace = true itertools.workspace = true serde_json.workspace = true tracing.workspace = true +smallvec.workspace = true # local deps stdx.workspace = true 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 71cac6af1346f..9e38d8f1b9e43 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 @@ -6,6 +6,8 @@ use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext, Severity}; // Diagnostic: inactive-code // // This diagnostic is shown for code with inactive `#[cfg]` attributes. +// +// It can be disabled selectively with `#[allow(rust_analyzer::inactive_code)]`. pub(crate) fn inactive_code( ctx: &DiagnosticsContext<'_, '_>, d: &hir::InactiveCode, @@ -26,10 +28,11 @@ pub(crate) fn inactive_code( } } // FIXME: This shouldn't be a diagnostic - let res = Diagnostic::new( - DiagnosticCode::Ra("inactive-code", Severity::WeakWarning), + let res = Diagnostic::new_with_syntax_node_ptr( + ctx, + DiagnosticCode::RaLint("inactive_code", Severity::WeakWarning), message, - ctx.sema.diagnostics_display_range(d.node), + d.node, ) .stable() .with_unused(true); @@ -237,7 +240,7 @@ fn foo() {} }; assert_eq!( inactive_code.code, - DiagnosticCode::Ra("inactive-code", ide_db::Severity::WeakWarning) + DiagnosticCode::RaLint("inactive_code", ide_db::Severity::WeakWarning) ); assert_eq!( inactive_code.message, @@ -299,6 +302,25 @@ macro_rules! m { m! { fn foo() {} // ^^^^^^^^^^^ weak: code is inactive due to #[cfg] directives: false is disabled +} + "#, + ); + } + + #[test] + fn allow() { + check( + r#" +macro_rules! m { + ($e:item) => { + #[cfg(false)] + #[allow(rust_analyzer::inactive_code)] + $e + }; +} + +m! { + fn foo() {} } "#, ); diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs index f6293e35d0c37..754d90d112b85 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs @@ -375,6 +375,8 @@ fn main() { fn cfgd_out_call_arguments() { check_diagnostics( r#" +#![allow(rust_analyzer::inactive_code)] + struct C(#[cfg(FALSE)] ()); impl C { fn new() -> Self { @@ -398,6 +400,8 @@ fn main() { fn cfgd_out_fn_params() { check_diagnostics( r#" +#![allow(rust_analyzer::inactive_code)] + fn foo(#[cfg(NEVER)] x: ()) {} struct S; diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/no_such_field.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/no_such_field.rs index 7959fddc757f4..a96b92dd4a123 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/no_such_field.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/no_such_field.rs @@ -153,6 +153,8 @@ mod tests { fn dont_work_for_field_with_disabled_cfg() { check_diagnostics( r#" +#![allow(rust_analyzer::inactive_code)] + struct Test { #[cfg(feature = "hello")] test: u32, @@ -224,6 +226,8 @@ impl S { check_diagnostics( r#" //- /lib.rs crate:foo cfg:feature=foo +#![allow(rust_analyzer::inactive_code)] + struct MyStruct { my_val: usize, #[cfg(feature = "foo")] @@ -249,6 +253,8 @@ impl MyStruct { check_diagnostics( r#" //- /lib.rs crate:foo cfg:feature=foo +#![allow(rust_analyzer::inactive_code)] + enum Foo { #[cfg(not(feature = "foo"))] Buz, @@ -272,6 +278,8 @@ fn test_fn(f: Foo) { check_diagnostics( r#" //- /lib.rs crate:foo cfg:feature=foo +#![allow(rust_analyzer::inactive_code)] + struct S { #[cfg(feature = "foo")] foo: u32, 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 e2e465e26c78d..f77c20b085435 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs @@ -113,9 +113,11 @@ use ide_db::{ rename::RenameConfig, source_change::SourceChange, }; +use smallvec::{SmallVec, smallvec}; use syntax::{ AstPtr, Edition, SmolStr, SyntaxNode, SyntaxNodePtr, TextRange, ast::{self, AstNode}, + format_smolstr, }; #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] @@ -125,6 +127,7 @@ pub enum DiagnosticCode { RustcLint(&'static str), Clippy(&'static str), Ra(&'static str, Severity), + RaLint(&'static str, Severity), } impl DiagnosticCode { @@ -142,7 +145,7 @@ impl DiagnosticCode { DiagnosticCode::Clippy(e) => { format!("https://rust-lang.github.io/rust-clippy/master/#/{e}") } - DiagnosticCode::Ra(e, _) => { + DiagnosticCode::Ra(e, _) | DiagnosticCode::RaLint(e, _) => { format!("https://rust-analyzer.github.io/book/diagnostics.html#{e}") } } @@ -153,7 +156,8 @@ impl DiagnosticCode { DiagnosticCode::RustcHardError(r) | DiagnosticCode::RustcLint(r) | DiagnosticCode::Clippy(r) - | DiagnosticCode::Ra(r, _) => r, + | DiagnosticCode::Ra(r, _) + | DiagnosticCode::RaLint(r, _) => r, DiagnosticCode::SyntaxError => "syntax-error", } } @@ -190,7 +194,7 @@ impl Diagnostic { // FIXME: We can make this configurable, and if the user uses `cargo clippy` on flycheck, we can // make it normal warning. DiagnosticCode::Clippy(_) => Severity::WeakWarning, - DiagnosticCode::Ra(_, s) => s, + DiagnosticCode::Ra(_, s) | DiagnosticCode::RaLint(_, s) => s, }, unused: false, experimental: true, @@ -529,7 +533,14 @@ pub fn semantic_diagnostics( let mut lints = res .iter_mut() - .filter(|it| matches!(it.code, DiagnosticCode::Clippy(_) | DiagnosticCode::RustcLint(_))) + .filter(|it| { + matches!( + it.code, + DiagnosticCode::Clippy(_) + | DiagnosticCode::RustcLint(_) + | DiagnosticCode::RaLint(..) + ) + }) .filter_map(|it| Some((it.main_node(&ctx.sema)?, it))) .collect::>(); @@ -602,7 +613,7 @@ fn handle_diag_from_macros( struct BuiltLint { lint: &'static Lint, - groups: Vec<&'static str>, + groups: SmallVec<[SmolStr; 5]>, } static RUSTC_LINTS: LazyLock> = @@ -623,12 +634,17 @@ fn build_lints_map( ) -> FxHashMap<&'static str, BuiltLint> { let mut map_with_prefixes: FxHashMap<_, _> = lints .iter() - .map(|lint| (lint.label, BuiltLint { lint, groups: vec![lint.label, "__RA_EVERY_LINT"] })) + .map(|lint| { + ( + lint.label, + BuiltLint { lint, groups: smallvec![lint.label.into(), "__RA_EVERY_LINT".into()] }, + ) + }) .collect(); for g in lint_group { let mut add_children = |label: &'static str| { for child in g.children { - map_with_prefixes.get_mut(child).unwrap().groups.push(label); + map_with_prefixes.get_mut(child).unwrap().groups.push(label.into()); } }; add_children(g.lint.label); @@ -649,12 +665,15 @@ fn handle_lints( edition: Edition, ) { for (node, diag) in diagnostics { - let lint = match diag.code { - DiagnosticCode::RustcLint(lint) => RUSTC_LINTS[lint].lint, - DiagnosticCode::Clippy(lint) => CLIPPY_LINTS[lint].lint, - _ => panic!("non-lint passed to `handle_lints()`"), + let default_severity = 'find_severity: { + let lint = match diag.code { + DiagnosticCode::RustcLint(lint) => RUSTC_LINTS[lint].lint, + DiagnosticCode::Clippy(lint) => CLIPPY_LINTS[lint].lint, + DiagnosticCode::RaLint(_, severity) => break 'find_severity severity, + _ => panic!("non-lint passed to `handle_lints()`"), + }; + default_lint_severity(lint, edition) }; - let default_severity = default_lint_severity(lint, edition); if !(default_severity == Severity::Allow && diag.severity == Severity::WeakWarning) { diag.severity = default_severity; } @@ -754,13 +773,13 @@ fn lint_attrs( #[derive(Debug)] struct LintGroups { - groups: &'static [&'static str], + groups: SmallVec<[SmolStr; 5]>, inside_warnings: bool, } impl LintGroups { fn contains(&self, group: &str) -> bool { - self.groups.contains(&group) || (self.inside_warnings && group == "warnings") + self.groups.iter().any(|g| g == group) || (self.inside_warnings && group == "warnings") } } @@ -769,12 +788,15 @@ fn lint_groups(lint: &DiagnosticCode, edition: Edition) -> LintGroups { DiagnosticCode::RustcLint(name) => { let lint = &RUSTC_LINTS[name]; let inside_warnings = default_lint_severity(lint.lint, edition) == Severity::Warning; - (&lint.groups, inside_warnings) + (lint.groups.clone(), inside_warnings) } DiagnosticCode::Clippy(name) => { let lint = &CLIPPY_LINTS[name]; let inside_warnings = default_lint_severity(lint.lint, edition) == Severity::Warning; - (&lint.groups, inside_warnings) + (lint.groups.clone(), inside_warnings) + } + DiagnosticCode::RaLint(name, severity) => { + (smallvec![format_smolstr!("rust_analyzer::{name}")], *severity == Severity::Warning) } _ => panic!("non-lint passed to `handle_lints()`"), }; diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/tests/overly_long_real_world_cases.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/tests/overly_long_real_world_cases.rs index 301613e920191..34cf80a85b98e 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/tests/overly_long_real_world_cases.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/tests/overly_long_real_world_cases.rs @@ -2729,6 +2729,7 @@ tracing::error!(); "unresolved-macro-call", "syntax-error", "macro-error", + "inactive_code", ], ); } From 6dfb135ccf074b7e24dc8e0d306c4b8b034ce09e Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Mon, 29 Jun 2026 01:04:20 +0300 Subject: [PATCH 03/63] Respect `references.exclude[Tests/Imports]` in references lens --- .../crates/ide/src/annotations.rs | 45 +++++++++++++++++-- .../rust-analyzer/src/cli/analysis_stats.rs | 2 + .../crates/rust-analyzer/src/config.rs | 32 ++++++------- 3 files changed, 61 insertions(+), 18 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide/src/annotations.rs b/src/tools/rust-analyzer/crates/ide/src/annotations.rs index f716f94d7141b..884bc111caeaf 100644 --- a/src/tools/rust-analyzer/crates/ide/src/annotations.rs +++ b/src/tools/rust-analyzer/crates/ide/src/annotations.rs @@ -43,6 +43,8 @@ pub struct AnnotationConfig<'a> { pub annotate_references: bool, pub annotate_method_references: bool, pub annotate_enum_variant_references: bool, + pub references_exclude_imports: bool, + pub references_exclude_tests: bool, pub location: AnnotationLocation, pub filter_adjacent_derive_implementations: bool, pub ra_fixture: RaFixtureConfig<'a>, @@ -219,8 +221,8 @@ pub(crate) fn resolve_annotation( &FindAllRefsConfig { search_scope: None, ra_fixture: config.ra_fixture, - exclude_imports: false, - exclude_tests: false, + exclude_imports: config.references_exclude_imports, + exclude_tests: config.references_exclude_tests, }, ) .map(|result| { @@ -262,6 +264,8 @@ mod tests { annotate_references: true, annotate_method_references: true, annotate_enum_variant_references: true, + references_exclude_imports: false, + references_exclude_tests: false, location: AnnotationLocation::AboveName, ra_fixture: RaFixtureConfig::default(), filter_adjacent_derive_implementations: false, @@ -278,7 +282,7 @@ mod tests { .annotations(config, file_id) .unwrap() .into_iter() - .map(|annotation| analysis.resolve_annotation(&DEFAULT_CONFIG, annotation).unwrap()) + .map(|annotation| analysis.resolve_annotation(config, annotation).unwrap()) .collect(); expect.assert_debug_eq(&annotations); @@ -1045,4 +1049,39 @@ struct Foo; &AnnotationConfig { location: AnnotationLocation::AboveWholeItem, ..DEFAULT_CONFIG }, ); } + + #[test] + fn refs_exclude_tests() { + check_with_config( + r#" +fn foo() {} + +#[test] +fn bar() { foo() } + "#, + expect![[r#" + [ + Annotation { + range: 3..6, + kind: HasReferences { + pos: FilePositionWrapper { + file_id: FileId( + 0, + ), + offset: 3, + }, + data: Some( + [], + ), + }, + }, + ] + "#]], + &AnnotationConfig { + references_exclude_tests: true, + annotate_runnables: false, + ..DEFAULT_CONFIG + }, + ); + } } 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 1a036c3b99195..00561a7bd8d76 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 @@ -1435,6 +1435,8 @@ impl flags::AnalysisStats { annotate_references: false, annotate_method_references: false, annotate_enum_variant_references: false, + references_exclude_imports: false, + references_exclude_tests: false, location: ide::AnnotationLocation::AboveName, filter_adjacent_derive_implementations: false, ra_fixture: RaFixtureConfig::default(), 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 64411bf73f083..67cb51f6e156b 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs @@ -1548,6 +1548,9 @@ pub struct LensConfig { pub refs_trait: bool, // for Struct, Enum, Union and Trait pub enum_variant_refs: bool, + pub refs_exclude_imports: bool, + pub refs_exclude_tests: bool, + // annotations pub location: AnnotationLocation, pub filter_adjacent_derive_implementations: bool, @@ -1591,10 +1594,6 @@ impl LensConfig { self.run || self.debug || self.update_test } - pub fn references(&self) -> bool { - self.method_refs || self.refs_adt || self.refs_trait || self.enum_variant_refs - } - pub fn into_annotation_config<'a>( self, binary_target: bool, @@ -1607,6 +1606,8 @@ impl LensConfig { annotate_references: self.refs_adt, annotate_method_references: self.method_refs, annotate_enum_variant_references: self.enum_variant_refs, + references_exclude_imports: self.refs_exclude_imports, + references_exclude_tests: self.refs_exclude_tests, location: self.location.into(), ra_fixture: RaFixtureConfig { minicore, disable_ra_fixture: self.disable_ra_fixture }, filter_adjacent_derive_implementations: self.filter_adjacent_derive_implementations, @@ -2688,18 +2689,19 @@ impl Config { } pub fn lens(&self) -> LensConfig { + let enable = *self.lens_enable(); LensConfig { - run: *self.lens_enable() && *self.lens_run_enable(), - debug: *self.lens_enable() && *self.lens_debug_enable(), - update_test: *self.lens_enable() - && *self.lens_updateTest_enable() - && *self.lens_run_enable(), - interpret: *self.lens_enable() && *self.lens_run_enable() && *self.interpret_tests(), - implementations: *self.lens_enable() && *self.lens_implementations_enable(), - method_refs: *self.lens_enable() && *self.lens_references_method_enable(), - refs_adt: *self.lens_enable() && *self.lens_references_adt_enable(), - refs_trait: *self.lens_enable() && *self.lens_references_trait_enable(), - enum_variant_refs: *self.lens_enable() && *self.lens_references_enumVariant_enable(), + run: enable && *self.lens_run_enable(), + debug: enable && *self.lens_debug_enable(), + update_test: enable && *self.lens_updateTest_enable() && *self.lens_run_enable(), + interpret: enable && *self.lens_run_enable() && *self.interpret_tests(), + implementations: enable && *self.lens_implementations_enable(), + method_refs: enable && *self.lens_references_method_enable(), + refs_adt: enable && *self.lens_references_adt_enable(), + refs_trait: enable && *self.lens_references_trait_enable(), + enum_variant_refs: enable && *self.lens_references_enumVariant_enable(), + refs_exclude_imports: *self.references_excludeImports(), + refs_exclude_tests: *self.references_excludeTests(), location: *self.lens_location(), filter_adjacent_derive_implementations: *self .gotoImplementations_filterAdjacentDerives(), From 41954a2d3cd99cd24215b11e50f48eac02e94552 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sun, 19 Jul 2026 16:55:11 +0200 Subject: [PATCH 04/63] internal: Port `ExprScopes` over to visitor fully --- .../crates/hir-def/src/expr_store/scope.rs | 390 ++++++++++-------- .../crates/hir-def/src/item_scope.rs | 2 +- 2 files changed, 227 insertions(+), 165 deletions(-) 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 d6568b8cfe6ae..ee5396b4cde97 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 @@ -5,9 +5,9 @@ use la_arena::{Arena, ArenaMap, Idx, IdxRange, RawIdx}; use crate::{ BlockId, DefWithBodyId, ExpressionStoreOwnerId, GenericDefId, VariantId, - expr_store::{Body, ExpressionStore, HygieneId, StoreVisitor, body::Param}, + expr_store::{Body, ExpressionStore, HygieneId, StoreVisitor, StoreVisitorExt, body::Param}, hir::{ - Array, Binding, BindingId, Expr, ExprId, Item, LabelId, Pat, PatId, Statement, + Binding, BindingId, Expr, ExprId, Item, LabelId, Pat, PatId, Statement, generics::GenericParams, }, signatures::VariantFields, @@ -167,7 +167,13 @@ impl ExprScopes { scopes.add_bindings(body, root, self_param, body.binding_hygiene(self_param)); } body.params.iter().for_each(|param| scopes.add_pat_bindings(body, root, param.formal)); - compute_expr_scopes(body.root_expr(), body, &mut scopes, &mut { root }, &mut root); + ExprScopeVisitor { + store: body, + scopes: &mut scopes, + scope: &mut { root }, + const_scope: &mut root, + } + .on_expr(body.root_expr()); scopes } @@ -182,7 +188,13 @@ impl ExprScopes { let root = scopes.root_scope(); for root_expr in roots { let mut scope = scopes.new_scope(root); - compute_expr_scopes(root_expr, store, &mut scopes, &mut { scope }, &mut scope); + ExprScopeVisitor { + store, + scopes: &mut scopes, + scope: &mut { scope }, + const_scope: &mut scope, + } + .on_expr(root_expr); } scopes } @@ -282,174 +294,161 @@ struct ExprScopeVisitor<'a> { const_scope: &'a mut ScopeId, } -impl StoreVisitor for ExprScopeVisitor<'_> { - fn on_expr(&mut self, expr: ExprId) { - compute_expr_scopes(expr, self.store, self.scopes, self.scope, self.const_scope); - } - - fn on_anon_const_expr(&mut self, expr: ExprId) { - let mut scope = *self.const_scope; - compute_expr_scopes(expr, self.store, self.scopes, &mut scope, self.const_scope); - } - - fn on_pat(&mut self, pat: PatId) { - self.store.visit_pat_children(pat, &mut *self); - } +impl ExprScopeVisitor<'_> { + fn visit_block( + &mut self, + expr: ExprId, + id: Option, + statements: &[Statement], + tail: Option, + label: Option, + ) { + let mut scope = self.scopes.new_block_scope(*self.scope, id, label); + let mut const_scope = if id.is_some() { + self.scopes.new_block_scope(*self.const_scope, id, None) + } else { + // We don't need to allocate a new scope, since only items matter to us. + *self.const_scope + }; + // Overwrite the old scope for the block expr, so that every block scope can be found + // via the block itself (important for blocks that only contain items, no expressions). + self.scopes.set_scope(expr, scope); - fn on_type(&mut self, ty: TypeRefId) { - self.store.visit_type_ref_children(ty, &mut *self); + let mut visitor = ExprScopeVisitor { + store: self.store, + scopes: self.scopes, + scope: &mut scope, + const_scope: &mut const_scope, + }; + for stmt in statements { + match stmt { + Statement::Let { pat, initializer, else_branch, type_ref } => { + visitor.on_type_opt(*type_ref); + visitor.on_expr_opt(*initializer); + visitor.on_expr_opt(*else_branch); + *visitor.scope = visitor.scopes.new_scope(*visitor.scope); + visitor.scopes.add_pat_bindings(visitor.store, *visitor.scope, *pat); + } + Statement::Expr { expr, has_semi: _ } => visitor.on_expr(*expr), + Statement::Item(Item::MacroDef(macro_id)) => { + *visitor.scope = + visitor.scopes.new_macro_def_scope(*visitor.scope, macro_id.clone()); + *visitor.const_scope = + visitor.scopes.new_macro_def_scope(*visitor.const_scope, macro_id.clone()); + } + Statement::Item(Item::Other) => (), + } + } + visitor.on_expr_opt(tail); } } -fn compute_type_scopes( - ty: TypeRefId, - store: &ExpressionStore, - scopes: &mut ExprScopes, - const_scope: &mut ScopeId, -) { - let mut scope = *const_scope; - ExprScopeVisitor { store, scopes, scope: &mut scope, const_scope }.on_type(ty); -} - -fn compute_block_scopes( - statements: &[Statement], - tail: Option, - store: &ExpressionStore, - scopes: &mut ExprScopes, - scope: &mut ScopeId, - const_scope: &mut ScopeId, -) { - for stmt in statements { - match stmt { - Statement::Let { pat, initializer, else_branch, type_ref } => { - if let Some(type_ref) = type_ref { - compute_type_scopes(*type_ref, store, scopes, const_scope); +impl StoreVisitor for ExprScopeVisitor<'_> { + fn on_expr(&mut self, expr: ExprId) { + self.scopes.set_scope(expr, *self.scope); + match &self.store[expr] { + Expr::Block { statements, tail, id, label } => { + self.visit_block(expr, *id, statements, *tail, *label); + } + Expr::Const(expr) => self.on_anon_const_expr(*expr), + Expr::Unsafe { id, statements, tail } => { + self.visit_block(expr, *id, statements, *tail, None); + } + Expr::Loop { body, label, source: _ } => { + let mut scope = self.scopes.new_labeled_scope(*self.scope, *label); + ExprScopeVisitor { + store: self.store, + scopes: self.scopes, + scope: &mut scope, + const_scope: self.const_scope, } - if let Some(expr) = initializer { - compute_expr_scopes(*expr, store, scopes, scope, const_scope); + .on_expr(*body); + } + Expr::Closure { args, arg_types, ret_type, body, capture_by: _, closure_kind: _ } => { + arg_types.iter().flatten().for_each(|type_ref| self.on_type(*type_ref)); + self.on_type_opt(*ret_type); + let mut scope = self.scopes.new_scope(*self.scope); + args.iter().for_each(|arg| self.scopes.add_pat_bindings(self.store, scope, *arg)); + ExprScopeVisitor { + store: self.store, + scopes: self.scopes, + scope: &mut scope, + const_scope: self.const_scope, } - if let Some(expr) = else_branch { - compute_expr_scopes(*expr, store, scopes, scope, const_scope); + .on_expr(*body); + } + Expr::Match { expr, arms } => { + self.on_expr(*expr); + for arm in arms.iter() { + let mut scope = self.scopes.new_scope(*self.scope); + self.scopes.add_pat_bindings(self.store, scope, arm.pat); + if let Some(guard) = arm.guard { + scope = self.scopes.new_scope(scope); + ExprScopeVisitor { + store: self.store, + scopes: self.scopes, + scope: &mut scope, + const_scope: self.const_scope, + } + .on_expr(guard); + } + ExprScopeVisitor { + store: self.store, + scopes: self.scopes, + scope: &mut scope, + const_scope: self.const_scope, + } + .on_expr(arm.expr); } - - *scope = scopes.new_scope(*scope); - scopes.add_pat_bindings(store, *scope, *pat); } - Statement::Expr { expr, .. } => { - compute_expr_scopes(*expr, store, scopes, scope, const_scope); + &Expr::If { condition, then_branch, else_branch } => { + let mut then_branch_scope = self.scopes.new_scope(*self.scope); + let mut visitor = ExprScopeVisitor { + store: self.store, + scopes: self.scopes, + scope: &mut then_branch_scope, + const_scope: self.const_scope, + }; + visitor.on_expr(condition); + visitor.on_expr(then_branch); + self.on_expr_opt(else_branch); } - Statement::Item(Item::MacroDef(macro_id)) => { - *scope = scopes.new_macro_def_scope(*scope, macro_id.clone()); - *const_scope = scopes.new_macro_def_scope(*const_scope, macro_id.clone()); + &Expr::Let { pat, expr } => { + self.on_expr(expr); + *self.scope = self.scopes.new_scope(*self.scope); + self.scopes.add_pat_bindings(self.store, *self.scope, pat); } - Statement::Item(Item::Other) => (), + _ => self.store.visit_expr_children(expr, &mut *self), } } - if let Some(expr) = tail { - compute_expr_scopes(expr, store, scopes, scope, const_scope); + + fn on_anon_const_expr(&mut self, expr: ExprId) { + let mut scope = *self.const_scope; + ExprScopeVisitor { + store: self.store, + scopes: self.scopes, + scope: &mut scope, + const_scope: self.const_scope, + } + .on_expr(expr); } -} -fn compute_expr_scopes( - expr: ExprId, - store: &ExpressionStore, - scopes: &mut ExprScopes, - scope: &mut ScopeId, - const_scope: &mut ScopeId, -) { - let compute_expr_scopes = - |scopes: &mut ExprScopes, expr: ExprId, scope: &mut ScopeId, const_scope: &mut ScopeId| { - compute_expr_scopes(expr, store, scopes, scope, const_scope) - }; - let handle_block = |id, - statements, - tail, - label, - scopes: &mut ExprScopes, - scope: &mut ScopeId, - const_scope: &mut ScopeId| { - let mut scope = scopes.new_block_scope(*scope, id, label); - let mut const_scope = if id.is_some() { - scopes.new_block_scope(*const_scope, id, None) - } else { - // We don't need to allocate a new scope, since only items matter to us. - *const_scope - }; - // Overwrite the old scope for the block expr, so that every block scope can be found - // via the block itself (important for blocks that only contain items, no expressions). - scopes.set_scope(expr, scope); - compute_block_scopes(statements, tail, store, scopes, &mut scope, &mut const_scope); - }; + fn on_pat(&mut self, pat: PatId) { + self.store.visit_pat_children(pat, &mut *self); + } - scopes.set_scope(expr, *scope); - match &store[expr] { - Expr::Block { statements, tail, id, label } => { - handle_block(*id, statements, *tail, *label, scopes, scope, const_scope); - } - Expr::Const(id) => { - let mut scope = *const_scope; - compute_expr_scopes(scopes, *id, &mut scope, const_scope); - } - Expr::Array(Array::Repeat { initializer, repeat }) => { - compute_expr_scopes(scopes, *initializer, scope, const_scope); - let mut repeat_scope = *const_scope; - compute_expr_scopes(scopes, *repeat, &mut repeat_scope, const_scope); - } - Expr::Unsafe { id, statements, tail } => { - handle_block(*id, statements, *tail, None, scopes, scope, const_scope); - } - Expr::Loop { body: body_expr, label, source: _ } => { - let mut scope = scopes.new_labeled_scope(*scope, *label); - compute_expr_scopes(scopes, *body_expr, &mut scope, const_scope); - } - Expr::Closure { - args, - arg_types, - ret_type, - body: body_expr, - capture_by: _, - closure_kind: _, - } => { - arg_types - .iter() - .flatten() - .for_each(|type_ref| compute_type_scopes(*type_ref, store, scopes, const_scope)); - if let Some(type_ref) = ret_type { - compute_type_scopes(*type_ref, store, scopes, const_scope); - } - let mut scope = scopes.new_scope(*scope); - args.iter().for_each(|arg| scopes.add_pat_bindings(store, scope, *arg)); - compute_expr_scopes(scopes, *body_expr, &mut scope, const_scope); - } - Expr::Match { expr, arms } => { - compute_expr_scopes(scopes, *expr, scope, const_scope); - for arm in arms.iter() { - let mut scope = scopes.new_scope(*scope); - scopes.add_pat_bindings(store, scope, arm.pat); - if let Some(guard) = arm.guard { - scope = scopes.new_scope(scope); - compute_expr_scopes(scopes, guard, &mut scope, const_scope); - } - compute_expr_scopes(scopes, arm.expr, &mut scope, const_scope); - } - } - &Expr::If { condition, then_branch, else_branch } => { - let mut then_branch_scope = scopes.new_scope(*scope); - compute_expr_scopes(scopes, condition, &mut then_branch_scope, const_scope); - compute_expr_scopes(scopes, then_branch, &mut then_branch_scope, const_scope); - if let Some(else_branch) = else_branch { - compute_expr_scopes(scopes, else_branch, scope, const_scope); - } - } - &Expr::Let { pat, expr } => { - compute_expr_scopes(scopes, expr, scope, const_scope); - *scope = scopes.new_scope(*scope); - scopes.add_pat_bindings(store, *scope, pat); - } - _ => { - store.visit_expr_children(expr, ExprScopeVisitor { store, scopes, scope, const_scope }) - } - }; + fn on_type(&mut self, ty: TypeRefId) { + let mut scope = *self.const_scope; + self.store.visit_type_ref_children( + ty, + ExprScopeVisitor { + store: self.store, + scopes: self.scopes, + scope: &mut scope, + const_scope: self.const_scope, + }, + ); + } } #[cfg(test)] @@ -497,16 +496,27 @@ mod tests { let (file_id, _) = editioned_file_id.unpack(&db); let file_syntax = editioned_file_id.parse(&db).syntax_node(); - let marker: ast::PathExpr = find_node_at_offset(&file_syntax, offset).unwrap(); + let marker: Option = find_node_at_offset(&file_syntax, offset); let function = find_function(&db, file_id); let scopes = ExprScopes::of(&db, DefWithBodyId::from(function)); - let (_body, source_map) = Body::with_source_map(&db, function.into()); - - let expr_id = source_map - .node_expr(InFile { file_id: editioned_file_id.into(), value: &marker.into() }) - .unwrap() - .as_expr() + let (body, source_map) = Body::with_source_map(&db, function.into()); + + let expr_id = marker + .and_then(|marker| { + source_map + .node_expr(InFile { file_id: editioned_file_id.into(), value: &marker.into() }) + .and_then(|expr| expr.as_expr()) + }) + .or_else(|| { + body.exprs().find_map(|(expr, value)| { + let crate::hir::Expr::Path(path) = value else { return None }; + path.mod_path() + .and_then(|path| path.as_ident()) + .is_some_and(|name| name.as_str() == "marker") + .then_some(expr) + }) + }) .unwrap(); let scope = scopes.scope_for(expr_id); @@ -533,6 +543,58 @@ fn f(param: usize) { ); } + #[test] + fn pattern_type_expr_scope() { + do_check( + r#" +fn f(param: usize) { + let local = 0; + let _: builtin#pattern_type (usize is 0..=$0) = 0; +} +"#, + &["param"], + ); + } + + #[test] + fn closure_pattern_type_expr_scope() { + do_check( + r#" +fn f(param: usize) { + let local = 0; + let _ = |_: builtin#pattern_type (usize is 0..=$0)| {}; +} +"#, + &["param"], + ); + } + + #[test] + fn array_repeat_expr_scope() { + do_check( + r#" +fn f(param: usize) { + let local = 0; + let _ = [(); $0]; +} +"#, + &["param"], + ); + } + + #[test] + fn inline_const_expr_scope() { + do_check( + r#" +fn f(param: usize) { + let local = 0; + let _ = const { $0 }; +} +"#, + &["param"], + ); + } + #[test] fn test_lambda_scope() { do_check( diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs index 1443d3ea4be4c..2cc96d5db2c0a 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs @@ -161,7 +161,7 @@ pub struct ItemScope { /// Module scoped macros will be inserted into `items` instead of here. // FIXME: Macro shadowing in one module is not properly handled. Non-item place macros will // be all resolved to the last one defined if shadowing happens. - legacy_macros: FxHashMap>, + legacy_macros: FxHashMap>, /// The attribute macro invocations in this scope. attr_macros: FxHashMap, MacroCallId>, /// The macro invocations in this scope. From 388818c5ea2381e2dc58ee561de473929c29dab9 Mon Sep 17 00:00:00 2001 From: Wilfred Hughes Date: Mon, 22 Jun 2026 13:42:58 +0100 Subject: [PATCH 05/63] internal: Make stdout/stderr explicit in JsonLinesParser Currently JsonLinesParser::from_line is called for both stdout and stderr, so trait implementers cannot distinguish stdout and stderr. Define a separate JsonLinesParser::from_stderr_line to make the stdout/stderr distinction explicit, and update use sites. This is not a behaviour change. AI disclosure: Partially written by Codex and GPT-5.5. --- .../crates/rust-analyzer/src/command.rs | 4 +++- .../crates/rust-analyzer/src/discover.rs | 4 ++++ .../crates/rust-analyzer/src/flycheck.rs | 14 ++++++++++++-- .../crates/rust-analyzer/src/test_runner.rs | 4 ++++ 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/command.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/command.rs index ff2e21c865e9b..bc3fa21c6658f 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/command.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/command.rs @@ -23,6 +23,7 @@ use stdx::process::streaming_output; /// well as custom discover commands. pub(crate) trait JsonLinesParser: Send + 'static { fn from_line(&self, line: &str, error: &mut String) -> Option; + fn from_stderr_line(&self, line: &str, error: &mut String) -> Option; fn from_eof(&self) -> Option; } @@ -95,7 +96,8 @@ impl CommandActor { _ = stderr.write_all(line.as_bytes()); _ = stderr.write_all(b"\n"); } - if process_line(line, &mut stderr_errors) { + if let Some(t) = self.parser.from_stderr_line(line, &mut stderr_errors) { + self.sender.send(t).unwrap(); read_at_least_one_stderr_message = true; } }, diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/discover.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/discover.rs index 098b6a4d986d7..459a7993201b8 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/discover.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/discover.rs @@ -136,6 +136,10 @@ impl JsonLinesParser for DiscoverProjectParser { fn from_eof(&self) -> Option { None } + + fn from_stderr_line(&self, line: &str, error: &mut String) -> Option { + self.from_line(line, error) + } } #[test] 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 f73ffb24eea32..b927a11604158 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs @@ -1011,8 +1011,8 @@ enum CheckMessage { struct CheckParser; -impl JsonLinesParser for CheckParser { - fn from_line(&self, line: &str, error: &mut String) -> Option { +impl CheckParser { + fn parse_line(&self, line: &str, error: &mut String) -> Option { let mut deserializer = serde_json::Deserializer::from_str(line); deserializer.disable_recursion_limit(); if let Ok(message) = JsonMessage::deserialize(&mut deserializer) { @@ -1042,6 +1042,16 @@ impl JsonLinesParser for CheckParser { error.push('\n'); None } +} + +impl JsonLinesParser for CheckParser { + fn from_line(&self, line: &str, error: &mut String) -> Option { + self.parse_line(line, error) + } + + fn from_stderr_line(&self, line: &str, error: &mut String) -> Option { + self.parse_line(line, error) + } fn from_eof(&self) -> Option { None diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/test_runner.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/test_runner.rs index 31f35df5c796d..4f5c00192dcd5 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/test_runner.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/test_runner.rs @@ -72,6 +72,10 @@ impl JsonLinesParser for CargoTestOutputParser { }) } + fn from_stderr_line(&self, line: &str, error: &mut String) -> Option { + self.from_line(line, error) + } + fn from_eof(&self) -> Option { Some(CargoTestMessage { target: self.target.clone(), output: CargoTestOutput::Finished }) } From 047fb62a8e885436f94dbc3f95bac7f62d7fa6f8 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 24 Jul 2026 16:53:16 +0530 Subject: [PATCH 06/63] restrict visibility of mapping methods --- .../crates/syntax/src/syntax_editor/mapping.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 180c2e69fa3e9..464682223cd0e 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 @@ -21,7 +21,7 @@ pub struct SyntaxMapping { impl SyntaxMapping { /// Like [`SyntaxMapping::upmap_child`] but for syntax elements. - pub fn upmap_child_element( + pub(super) fn upmap_child_element( &self, child: &SyntaxElement, input_ancestor: &SyntaxNode, @@ -48,7 +48,7 @@ impl SyntaxMapping { /// Maps a child node of the input ancestor to the corresponding node in /// the output ancestor. - pub fn upmap_child( + pub(super) fn upmap_child( &self, child: &SyntaxNode, input_ancestor: &SyntaxNode, @@ -257,7 +257,7 @@ impl SyntaxMappingBuilder { } #[derive(Debug)] -pub struct MissingMapping(pub SyntaxNode); +pub(super) struct MissingMapping(pub SyntaxNode); #[derive(Debug, Clone, Copy)] struct MappingEntry { From 95048dca9c8e6da54264194cab70540c78eba770 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 24 Jul 2026 16:53:42 +0530 Subject: [PATCH 07/63] remove clone_for_updates entirely, --- .../src/ast/syntax_factory/constructors.rs | 330 ++++++++---------- 1 file changed, 142 insertions(+), 188 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 22c8c842d890c..23fb0e2e02782 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 @@ -14,49 +14,45 @@ use super::SyntaxFactory; impl SyntaxFactory { pub fn name(&self, name: &str) -> ast::Name { - make::name(name).clone_for_update() + make::name(name) } pub fn name_ref(&self, name: &str) -> ast::NameRef { - make::name_ref(name).clone_for_update() + make::name_ref(name) } pub fn name_ref_self_ty(&self) -> ast::NameRef { - make::name_ref_self_ty().clone_for_update() + make::name_ref_self_ty() } pub fn expr_todo(&self) -> ast::Expr { - make::ext::expr_todo().clone_for_update() + make::ext::expr_todo() } pub fn expr_self(&self) -> ast::Expr { - make::ext::expr_self().clone_for_update() + make::ext::expr_self() } pub fn expr_const_value(&self, text: &str) -> ast::ConstArg { - make::expr_const_value(text).clone_for_update() + make::expr_const_value(text) } pub fn lifetime(&self, text: &str) -> ast::Lifetime { - make::lifetime(text).clone_for_update() + make::lifetime(text) } pub fn ty(&self, text: &str) -> ast::Type { - make::ty(text).clone_for_update() + make::ty(text) } pub fn ty_infer(&self) -> ast::InferType { - let ast::Type::InferType(ast) = make::ty_placeholder().clone_for_update() else { - unreachable!() - }; + let ast::Type::InferType(ast) = make::ty_placeholder() else { unreachable!() }; ast } pub fn ty_path(&self, path: ast::Path) -> ast::PathType { - let ast::Type::PathType(ast) = make::ty_path(path.clone()).clone_for_update() else { - unreachable!() - }; + let ast::Type::PathType(ast) = make::ty_path(path.clone()) else { unreachable!() }; if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -76,11 +72,11 @@ impl SyntaxFactory { } pub fn type_bound(&self, bound: ast::Type) -> ast::TypeBound { - make::type_bound(bound).clone_for_update() + make::type_bound(bound) } pub fn type_bound_text(&self, bound: &str) -> ast::TypeBound { - make::type_bound_text(bound).clone_for_update() + make::type_bound_text(bound) } pub fn use_tree_list( @@ -88,7 +84,7 @@ impl SyntaxFactory { use_trees: impl IntoIterator, ) -> ast::UseTreeList { let (use_trees, input) = iterator_input(use_trees); - let ast = make::use_tree_list(use_trees).clone_for_update(); + let ast = make::use_tree_list(use_trees); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -104,7 +100,7 @@ impl SyntaxFactory { bounds: impl IntoIterator, ) -> Option { let (bounds, input) = iterator_input(bounds); - let ast = make::type_bound_list(bounds)?.clone_for_update(); + let ast = make::type_bound_list(bounds)?; if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -122,7 +118,7 @@ impl SyntaxFactory { name: ast::Name, bounds: Option, ) -> ast::TypeParam { - let ast = make::type_param(name.clone(), bounds.clone()).clone_for_update(); + let ast = make::type_param(name.clone(), bounds.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -140,23 +136,23 @@ impl SyntaxFactory { } pub fn path_from_text(&self, text: &str) -> ast::Path { - make::path_from_text(text).clone_for_update() + make::path_from_text(text) } pub fn path_from_text_with_edition(&self, text: &str, edition: Edition) -> ast::Path { - make::path_from_text_with_edition(text, edition).clone_for_update() + make::path_from_text_with_edition(text, edition) } pub fn path_concat(&self, first: ast::Path, second: ast::Path) -> ast::Path { - make::path_concat(first, second).clone_for_update() + make::path_concat(first, second) } pub fn visibility_pub_crate(&self) -> ast::Visibility { - make::visibility_pub_crate().clone_for_update() + make::visibility_pub_crate() } pub fn visibility_pub(&self) -> ast::Visibility { - make::visibility_pub().clone_for_update() + make::visibility_pub() } pub fn struct_( @@ -171,8 +167,7 @@ impl SyntaxFactory { strukt_name.clone(), generic_param_list.clone(), field_list.clone(), - ) - .clone_for_update(); + ); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -198,7 +193,7 @@ impl SyntaxFactory { } pub fn unnamed_param(&self, ty: ast::Type) -> ast::Param { - let ast = make::unnamed_param(ty.clone()).clone_for_update(); + let ast = make::unnamed_param(ty.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -210,7 +205,7 @@ impl SyntaxFactory { } pub fn untyped_param(&self, pat: ast::Pat) -> ast::Param { - let ast = make::untyped_param(pat.clone()).clone_for_update(); + let ast = make::untyped_param(pat.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -229,8 +224,7 @@ impl SyntaxFactory { ret_type: Option, ) -> ast::FnPtrType { let (params, params_input) = iterator_input(params); - let ast = make::ty_fn_ptr(is_unsafe, abi.clone(), params.into_iter(), ret_type.clone()) - .clone_for_update(); + let ast = make::ty_fn_ptr(is_unsafe, abi.clone(), params.into_iter(), ret_type.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -257,7 +251,7 @@ impl SyntaxFactory { bounds: impl IntoIterator, ) -> ast::WherePred { let (bounds, bounds_input) = iterator_input(bounds); - let ast = make::where_pred(path.clone(), bounds).clone_for_update(); + let ast = make::where_pred(path.clone(), bounds); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -293,7 +287,7 @@ impl SyntaxFactory { predicates: impl IntoIterator, ) -> ast::WhereClause { let (predicates, input) = iterator_input(predicates); - let ast = make::where_clause(predicates).clone_for_update(); + let ast = make::where_clause(predicates); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -305,7 +299,7 @@ impl SyntaxFactory { } pub fn impl_trait_type(&self, bounds: ast::TypeBoundList) -> ast::ImplTraitType { - let ast = make::impl_trait_type(bounds.clone()).clone_for_update(); + let ast = make::impl_trait_type(bounds.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -318,9 +312,7 @@ impl SyntaxFactory { } pub fn expr_field(&self, receiver: ast::Expr, field: &str) -> ast::FieldExpr { - let ast::Expr::FieldExpr(ast) = - make::expr_field(receiver.clone(), field).clone_for_update() - else { + let ast::Expr::FieldExpr(ast) = make::expr_field(receiver.clone(), field) else { unreachable!() }; @@ -362,8 +354,7 @@ impl SyntaxFactory { trait_where_clause.clone(), ty_where_clause.clone(), body.clone(), - ) - .clone_for_update(); + ); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -411,8 +402,7 @@ impl SyntaxFactory { type_param_bounds.clone(), where_clause.clone(), assignment.clone(), - ) - .clone_for_update(); + ); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -450,7 +440,7 @@ impl SyntaxFactory { params: impl IntoIterator, ) -> ast::ParamList { let (params, input) = iterator_input(params); - let ast = make::param_list(self_param.clone(), params).clone_for_update(); + let ast = make::param_list(self_param.clone(), params); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -467,7 +457,7 @@ impl SyntaxFactory { } pub fn const_param(&self, name: ast::Name, ty: ast::Type) -> ast::ConstParam { - let ast = make::const_param(name.clone(), ty.clone()).clone_for_update(); + let ast = make::const_param(name.clone(), ty.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -480,7 +470,7 @@ impl SyntaxFactory { } pub fn lifetime_param(&self, lifetime: ast::Lifetime) -> ast::LifetimeParam { - let ast = make::lifetime_param(lifetime.clone()).clone_for_update(); + let ast = make::lifetime_param(lifetime.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -496,7 +486,7 @@ impl SyntaxFactory { params: impl IntoIterator, ) -> ast::GenericParamList { let (params, input) = iterator_input(params); - let ast = make::generic_param_list(params).clone_for_update(); + let ast = make::generic_param_list(params); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -508,7 +498,7 @@ impl SyntaxFactory { } pub fn path_segment(&self, name_ref: ast::NameRef) -> ast::PathSegment { - let ast = make::path_segment(name_ref.clone()).clone_for_update(); + let ast = make::path_segment(name_ref.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -520,15 +510,15 @@ impl SyntaxFactory { } pub fn path_segment_self(&self) -> ast::PathSegment { - make::path_segment_self().clone_for_update() + make::path_segment_self() } pub fn path_segment_super(&self) -> ast::PathSegment { - make::path_segment_super().clone_for_update() + make::path_segment_super() } pub fn path_segment_crate(&self) -> ast::PathSegment { - make::path_segment_crate().clone_for_update() + make::path_segment_crate() } pub fn generic_ty_path_segment( @@ -537,7 +527,7 @@ impl SyntaxFactory { generic_args: impl IntoIterator, ) -> ast::PathSegment { let (generic_args, input) = iterator_input(generic_args); - let ast = make::generic_ty_path_segment(name_ref.clone(), generic_args).clone_for_update(); + let ast = make::generic_ty_path_segment(name_ref.clone(), generic_args); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -555,7 +545,7 @@ impl SyntaxFactory { } pub fn tail_only_block_expr(&self, tail_expr: ast::Expr) -> ast::BlockExpr { - let ast = make::tail_only_block_expr(tail_expr.clone()).clone_for_update(); + let ast = make::tail_only_block_expr(tail_expr.clone()); if let Some(mut mapping) = self.mappings() { let stmt_list = ast.stmt_list().unwrap(); @@ -571,9 +561,7 @@ impl SyntaxFactory { } pub fn expr_bin_op(&self, lhs: ast::Expr, op: ast::BinaryOp, rhs: ast::Expr) -> ast::Expr { - let ast::Expr::BinExpr(ast) = - make::expr_bin_op(lhs.clone(), op, rhs.clone()).clone_for_update() - else { + let ast::Expr::BinExpr(ast) = make::expr_bin_op(lhs.clone(), op, rhs.clone()) else { unreachable!() }; @@ -588,16 +576,16 @@ impl SyntaxFactory { } pub fn ty_placeholder(&self) -> ast::Type { - make::ty_placeholder().clone_for_update() + make::ty_placeholder() } pub fn ty_unit(&self) -> ast::Type { - make::ty_unit().clone_for_update() + make::ty_unit() } pub fn ty_tuple(&self, types: impl IntoIterator) -> ast::Type { let (types, input) = iterator_input(types); - let ast = make::ty_tuple(types).clone_for_update(); + let ast = make::ty_tuple(types); if let Some(mut mapping) = self.mappings() && let ast::Type::TupleType(tuple_ty) = &ast @@ -631,7 +619,7 @@ impl SyntaxFactory { unreachable!(); }; - let ast = path.path().unwrap().segment().unwrap().clone_for_update(); + let ast = path.path().unwrap().segment().unwrap(); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -653,7 +641,7 @@ impl SyntaxFactory { use_tree: ast::UseTree, ) -> ast::Use { let (attrs, attrs_input) = iterator_input(attrs); - let ast = make::use_(attrs, visibility.clone(), use_tree.clone()).clone_for_update(); + let ast = make::use_(attrs, visibility.clone(), use_tree.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -678,8 +666,7 @@ impl SyntaxFactory { alias: Option, add_star: bool, ) -> ast::UseTree { - let ast = make::use_tree(path.clone(), use_tree_list.clone(), alias.clone(), add_star) - .clone_for_update(); + let ast = make::use_tree(path.clone(), use_tree_list.clone(), alias.clone(), add_star); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -700,11 +687,11 @@ impl SyntaxFactory { } pub fn use_tree_glob(&self) -> ast::UseTree { - make::use_tree_glob().clone_for_update() + make::use_tree_glob() } pub fn path_unqualified(&self, segment: ast::PathSegment) -> ast::Path { - let ast = make::path_unqualified(segment.clone()).clone_for_update(); + let ast = make::path_unqualified(segment.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -716,7 +703,7 @@ impl SyntaxFactory { } pub fn path_qualified(&self, qual: ast::Path, segment: ast::PathSegment) -> ast::Path { - let ast = make::path_qualified(qual.clone(), segment.clone()).clone_for_update(); + let ast = make::path_qualified(qual.clone(), segment.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -738,7 +725,7 @@ impl SyntaxFactory { is_abs: bool, ) -> ast::Path { let (segments, input) = iterator_input(segments); - let ast = make::path_from_segments(segments, is_abs).clone_for_update(); + let ast = make::path_from_segments(segments, is_abs); if let Some(mut mapping) = self.mappings() { let mut current_path = Some(ast.clone()); @@ -757,7 +744,7 @@ impl SyntaxFactory { } pub fn ident_pat(&self, ref_: bool, mut_: bool, name: ast::Name) -> ast::IdentPat { - let ast = make::ident_pat(ref_, mut_, name.clone()).clone_for_update(); + let ast = make::ident_pat(ref_, mut_, name.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -769,7 +756,7 @@ impl SyntaxFactory { } pub fn simple_ident_pat(&self, name: ast::Name) -> ast::IdentPat { - let ast = make::ext::simple_ident_pat(name.clone()).clone_for_update(); + let ast = make::ext::simple_ident_pat(name.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -781,16 +768,16 @@ impl SyntaxFactory { } pub fn wildcard_pat(&self) -> ast::WildcardPat { - make::wildcard_pat().clone_for_update() + make::wildcard_pat() } pub fn literal_pat(&self, text: &str) -> ast::LiteralPat { - make::literal_pat(text).clone_for_update() + make::literal_pat(text) } pub fn slice_pat(&self, pats: impl IntoIterator) -> ast::SlicePat { let (pats, input) = iterator_input(pats); - let ast = make::slice_pat(pats).clone_for_update(); + let ast = make::slice_pat(pats); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -803,7 +790,7 @@ impl SyntaxFactory { pub fn tuple_pat(&self, pats: impl IntoIterator) -> ast::TuplePat { let (pats, input) = iterator_input(pats); - let ast = make::tuple_pat(pats).clone_for_update(); + let ast = make::tuple_pat(pats); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -820,7 +807,7 @@ impl SyntaxFactory { fields: impl IntoIterator, ) -> ast::TupleStructPat { let (fields, input) = iterator_input(fields); - let ast = make::tuple_struct_pat(path.clone(), fields).clone_for_update(); + let ast = make::tuple_struct_pat(path.clone(), fields); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -837,7 +824,7 @@ impl SyntaxFactory { path: ast::Path, fields: ast::RecordPatFieldList, ) -> ast::RecordPat { - let ast = make::record_pat_with_fields(path.clone(), fields.clone()).clone_for_update(); + let ast = make::record_pat_with_fields(path.clone(), fields.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -858,7 +845,7 @@ impl SyntaxFactory { rest_pat: Option, ) -> ast::RecordPatFieldList { let (fields, input) = iterator_input(fields); - let ast = make::record_pat_field_list(fields, rest_pat.clone()).clone_for_update(); + let ast = make::record_pat_field_list(fields, rest_pat.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -874,7 +861,7 @@ impl SyntaxFactory { } pub fn record_pat_field(&self, name_ref: ast::NameRef, pat: ast::Pat) -> ast::RecordPatField { - let ast = make::record_pat_field(name_ref.clone(), pat.clone()).clone_for_update(); + let ast = make::record_pat_field(name_ref.clone(), pat.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -887,7 +874,7 @@ impl SyntaxFactory { } pub fn record_pat_field_shorthand(&self, pat: ast::Pat) -> ast::RecordPatField { - let ast = make::record_pat_field_shorthand(pat.clone()).clone_for_update(); + let ast = make::record_pat_field_shorthand(pat.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -899,7 +886,7 @@ impl SyntaxFactory { } pub fn path_pat(&self, path: ast::Path) -> ast::Pat { - let ast = make::path_pat(path.clone()).clone_for_update(); + let ast = make::path_pat(path.clone()); match &ast { ast::Pat::PathPat(ast) => { @@ -923,7 +910,7 @@ impl SyntaxFactory { } pub fn rest_pat(&self) -> ast::RestPat { - make::rest_pat().clone_for_update() + make::rest_pat() } pub fn or_pat( @@ -932,7 +919,7 @@ impl SyntaxFactory { leading_pipe: bool, ) -> ast::OrPat { let (pats, input) = iterator_input(pats); - let ast = make::or_pat(pats, leading_pipe).clone_for_update(); + let ast = make::or_pat(pats, leading_pipe); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -944,7 +931,7 @@ impl SyntaxFactory { } pub fn box_pat(&self, pat: ast::Pat) -> ast::BoxPat { - let ast = make::box_pat(pat.clone()).clone_for_update(); + let ast = make::box_pat(pat.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -956,11 +943,11 @@ impl SyntaxFactory { } pub fn deref_pat(&self, pat: ast::Pat) -> ast::Pat { - make::deref_pat(pat.clone()).clone_for_update() + make::deref_pat(pat.clone()) } pub fn paren_pat(&self, pat: ast::Pat) -> ast::ParenPat { - let ast = make::paren_pat(pat.clone()).clone_for_update(); + let ast = make::paren_pat(pat.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -972,7 +959,7 @@ impl SyntaxFactory { } pub fn range_pat(&self, start: Option, end: Option) -> ast::RangePat { - let ast = make::range_pat(start.clone(), end.clone()).clone_for_update(); + let ast = make::range_pat(start.clone(), end.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -989,7 +976,7 @@ impl SyntaxFactory { } pub fn ref_pat(&self, pat: ast::Pat) -> ast::RefPat { - let ast = make::ref_pat(pat.clone()).clone_for_update(); + let ast = make::ref_pat(pat.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -1007,7 +994,7 @@ impl SyntaxFactory { ) -> ast::BlockExpr { let (statements, mut input) = iterator_input(statements); - let ast = make::block_expr(statements, tail_expr.clone()).clone_for_update(); + let ast = make::block_expr(statements, tail_expr.clone()); if let Some(mut mapping) = self.mappings() { let stmt_list = ast.stmt_list().unwrap(); @@ -1040,7 +1027,7 @@ impl SyntaxFactory { ) -> ast::BlockExpr { let (statements, mut input) = iterator_input(statements); - let ast = make::async_move_block_expr(statements, tail_expr.clone()).clone_for_update(); + let ast = make::async_move_block_expr(statements, tail_expr.clone()); if let Some(mut mapping) = self.mappings() { let stmt_list = ast.stmt_list().unwrap(); @@ -1065,11 +1052,11 @@ impl SyntaxFactory { } pub fn expr_empty_block(&self) -> ast::BlockExpr { - make::expr_empty_block().clone_for_update() + make::expr_empty_block() } pub fn expr_paren(&self, expr: ast::Expr) -> ast::ParenExpr { - let ast = make::expr_paren(expr.clone()).clone_for_update(); + let ast = make::expr_paren(expr.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -1082,7 +1069,7 @@ impl SyntaxFactory { pub fn expr_tuple(&self, fields: impl IntoIterator) -> ast::TupleExpr { let (fields, input) = iterator_input(fields); - let ast = make::expr_tuple(fields).clone_for_update(); + let ast = make::expr_tuple(fields); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -1094,7 +1081,7 @@ impl SyntaxFactory { } pub fn expr_assignment(&self, lhs: ast::Expr, rhs: ast::Expr) -> ast::BinExpr { - let ast = make::expr_assignment(lhs.clone(), rhs.clone()).clone_for_update(); + let ast = make::expr_assignment(lhs.clone(), rhs.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -1107,9 +1094,7 @@ impl SyntaxFactory { } pub fn expr_bin(&self, lhs: ast::Expr, op: ast::BinaryOp, rhs: ast::Expr) -> ast::BinExpr { - let ast::Expr::BinExpr(ast) = - make::expr_bin_op(lhs.clone(), op, rhs.clone()).clone_for_update() - else { + let ast::Expr::BinExpr(ast) = make::expr_bin_op(lhs.clone(), op, rhs.clone()) else { unreachable!() }; @@ -1124,13 +1109,11 @@ impl SyntaxFactory { } pub fn expr_literal(&self, text: &str) -> ast::Literal { - make::expr_literal(text).clone_for_update() + make::expr_literal(text) } pub fn expr_path(&self, path: ast::Path) -> ast::Expr { - let ast::Expr::PathExpr(ast) = make::expr_path(path.clone()).clone_for_update() else { - unreachable!() - }; + let ast::Expr::PathExpr(ast) = make::expr_path(path.clone()) else { unreachable!() }; if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -1142,7 +1125,7 @@ impl SyntaxFactory { } pub fn expr_prefix(&self, op: SyntaxKind, expr: ast::Expr) -> ast::PrefixExpr { - let ast = make::expr_prefix(op, expr.clone()).clone_for_update(); + let ast = make::expr_prefix(op, expr.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -1154,7 +1137,7 @@ impl SyntaxFactory { } pub fn expr_call(&self, expr: ast::Expr, arg_list: ast::ArgList) -> ast::CallExpr { - let ast = make::expr_call(expr.clone(), arg_list.clone()).clone_for_update(); + let ast = make::expr_call(expr.clone(), arg_list.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -1172,8 +1155,7 @@ impl SyntaxFactory { method: ast::NameRef, arg_list: ast::ArgList, ) -> ast::MethodCallExpr { - let ast = make::expr_method_call(receiver.clone(), method.clone(), arg_list.clone()) - .clone_for_update(); + let ast = make::expr_method_call(receiver.clone(), method.clone(), arg_list.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -1188,7 +1170,7 @@ impl SyntaxFactory { pub fn arg_list(&self, args: impl IntoIterator) -> ast::ArgList { let (args, input) = iterator_input(args); - let ast = make::arg_list(args).clone_for_update(); + let ast = make::arg_list(args); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax.clone()); @@ -1200,8 +1182,7 @@ impl SyntaxFactory { } pub fn expr_ref(&self, expr: ast::Expr, exclusive: bool) -> ast::Expr { - let ast::Expr::RefExpr(ast) = make::expr_ref(expr.clone(), exclusive).clone_for_update() - else { + let ast::Expr::RefExpr(ast) = make::expr_ref(expr.clone(), exclusive) else { unreachable!() }; @@ -1215,9 +1196,7 @@ impl SyntaxFactory { } pub fn expr_reborrow(&self, expr: ast::Expr) -> ast::Expr { - let ast::Expr::RefExpr(ast) = make::expr_reborrow(expr.clone()).clone_for_update() else { - unreachable!() - }; + let ast::Expr::RefExpr(ast) = make::expr_reborrow(expr.clone()) else { unreachable!() }; if let Some(mut mapping) = self.mappings() { // Layout: RefExpr(&mut, PrefixExpr(*, expr)). Map `expr` to the @@ -1236,9 +1215,7 @@ impl SyntaxFactory { } pub fn expr_raw_ref(&self, expr: ast::Expr, exclusive: bool) -> ast::Expr { - let ast::Expr::RefExpr(ast) = - make::expr_raw_ref(expr.clone(), exclusive).clone_for_update() - else { + let ast::Expr::RefExpr(ast) = make::expr_raw_ref(expr.clone(), exclusive) else { unreachable!() }; @@ -1257,7 +1234,7 @@ impl SyntaxFactory { expr: ast::Expr, ) -> ast::ClosureExpr { let (args, input) = iterator_input(pats); - let ast = make::expr_closure(args, expr.clone()).clone_for_update(); + let ast = make::expr_closure(args, expr.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -1275,9 +1252,7 @@ impl SyntaxFactory { } pub fn expr_return(&self, expr: Option) -> ast::ReturnExpr { - let ast::Expr::ReturnExpr(ast) = make::expr_return(expr.clone()).clone_for_update() else { - unreachable!() - }; + let ast::Expr::ReturnExpr(ast) = make::expr_return(expr.clone()) else { unreachable!() }; if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -1291,9 +1266,7 @@ impl SyntaxFactory { } pub fn expr_underscore(&self) -> ast::UnderscoreExpr { - let ast::Expr::UnderscoreExpr(ast) = make::ext::expr_underscore().clone_for_update() else { - unreachable!() - }; + let ast::Expr::UnderscoreExpr(ast) = make::ext::expr_underscore() else { unreachable!() }; ast } @@ -1304,8 +1277,7 @@ impl SyntaxFactory { then_branch: ast::BlockExpr, else_branch: Option, ) -> ast::IfExpr { - let ast = make::expr_if(condition.clone(), then_branch.clone(), else_branch.clone()) - .clone_for_update(); + let ast = make::expr_if(condition.clone(), then_branch.clone(), else_branch.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -1328,9 +1300,7 @@ impl SyntaxFactory { } pub fn expr_loop(&self, body: ast::BlockExpr) -> ast::LoopExpr { - let ast::Expr::LoopExpr(ast) = make::expr_loop(body.clone()).clone_for_update() else { - unreachable!() - }; + let ast::Expr::LoopExpr(ast) = make::expr_loop(body.clone()) else { unreachable!() }; if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -1342,7 +1312,7 @@ impl SyntaxFactory { } pub fn expr_while_loop(&self, condition: ast::Expr, body: ast::BlockExpr) -> ast::WhileExpr { - let ast = make::expr_while_loop(condition.clone(), body.clone()).clone_for_update(); + let ast = make::expr_while_loop(condition.clone(), body.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -1360,8 +1330,7 @@ impl SyntaxFactory { iterable: ast::Expr, body: ast::BlockExpr, ) -> ast::ForExpr { - let ast = - make::expr_for_loop(pat.clone(), iterable.clone(), body.clone()).clone_for_update(); + let ast = make::expr_for_loop(pat.clone(), iterable.clone(), body.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -1375,7 +1344,7 @@ impl SyntaxFactory { } pub fn expr_let(&self, pattern: ast::Pat, expr: ast::Expr) -> ast::LetExpr { - let ast = make::expr_let(pattern.clone(), expr.clone()).clone_for_update(); + let ast = make::expr_let(pattern.clone(), expr.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -1388,7 +1357,7 @@ impl SyntaxFactory { } pub fn expr_stmt(&self, expr: ast::Expr) -> ast::ExprStmt { - let ast = make::expr_stmt(expr.clone()).clone_for_update(); + let ast = make::expr_stmt(expr.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -1400,7 +1369,7 @@ impl SyntaxFactory { } pub fn expr_match(&self, expr: ast::Expr, match_arm_list: ast::MatchArmList) -> ast::MatchExpr { - let ast = make::expr_match(expr.clone(), match_arm_list.clone()).clone_for_update(); + let ast = make::expr_match(expr.clone(), match_arm_list.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -1416,7 +1385,7 @@ impl SyntaxFactory { } pub fn expr_macro(&self, path: ast::Path, tt: ast::TokenTree) -> ast::MacroExpr { - let ast = make::expr_macro(path.clone(), tt.clone()).clone_for_update(); + let ast = make::expr_macro(path.clone(), tt.clone()); if let Some(mut mapping) = self.mappings() { let macro_call = ast.macro_call().unwrap(); @@ -1436,7 +1405,7 @@ impl SyntaxFactory { guard: Option, expr: ast::Expr, ) -> ast::MatchArm { - let ast = make::match_arm(pat.clone(), guard.clone(), expr.clone()).clone_for_update(); + let ast = make::match_arm(pat.clone(), guard.clone(), expr.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -1452,7 +1421,7 @@ impl SyntaxFactory { } pub fn match_guard(&self, condition: ast::Expr) -> ast::MatchGuard { - let ast = make::match_guard(condition.clone()).clone_for_update(); + let ast = make::match_guard(condition.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -1468,7 +1437,7 @@ impl SyntaxFactory { match_arms: impl IntoIterator, ) -> ast::MatchArmList { let (match_arms, input) = iterator_input(match_arms); - let ast = make::match_arm_list(match_arms).clone_for_update(); + let ast = make::match_arm_list(match_arms); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -1485,8 +1454,7 @@ impl SyntaxFactory { ty: Option, initializer: Option, ) -> ast::LetStmt { - let ast = - make::let_stmt(pattern.clone(), ty.clone(), initializer.clone()).clone_for_update(); + let ast = make::let_stmt(pattern.clone(), ty.clone(), initializer.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -1516,8 +1484,7 @@ impl SyntaxFactory { ty.clone(), initializer.clone(), diverging.clone(), - ) - .clone_for_update(); + ); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -1537,7 +1504,7 @@ impl SyntaxFactory { } pub fn type_arg(&self, ty: ast::Type) -> ast::TypeArg { - let ast = make::type_arg(ty.clone()).clone_for_update(); + let ast = make::type_arg(ty.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -1549,7 +1516,7 @@ impl SyntaxFactory { } pub fn lifetime_arg(&self, lifetime: ast::Lifetime) -> ast::LifetimeArg { - let ast = make::lifetime_arg(lifetime.clone()).clone_for_update(); + let ast = make::lifetime_arg(lifetime.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -1570,8 +1537,7 @@ impl SyntaxFactory { ) -> ast::Const { let (attrs, attrs_input) = iterator_input(attrs); let ast = - make::item_const(attrs, visibility.clone(), name.clone(), ty.clone(), expr.clone()) - .clone_for_update(); + make::item_const(attrs, visibility.clone(), name.clone(), ty.clone(), expr.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -1607,8 +1573,7 @@ impl SyntaxFactory { name.clone(), ty.clone(), expr.clone(), - ) - .clone_for_update(); + ); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -1651,9 +1616,9 @@ impl SyntaxFactory { ) -> ast::GenericArgList { let (generic_args, input) = iterator_input(generic_args); let ast = if is_turbo { - make::turbofish_generic_arg_list(generic_args).clone_for_update() + make::turbofish_generic_arg_list(generic_args) } else { - make::generic_arg_list(generic_args).clone_for_update() + make::generic_arg_list(generic_args) }; if let Some(mut mapping) = self.mappings() { @@ -1670,7 +1635,7 @@ impl SyntaxFactory { path: ast::Path, fields: ast::RecordExprFieldList, ) -> ast::RecordExpr { - let ast = make::record_expr(path.clone(), fields.clone()).clone_for_update(); + let ast = make::record_expr(path.clone(), fields.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); builder.map_node(path.syntax().clone(), ast.path().unwrap().syntax().clone()); @@ -1688,7 +1653,7 @@ impl SyntaxFactory { fields: impl IntoIterator, ) -> ast::RecordExprFieldList { let (fields, input) = iterator_input(fields); - let ast = make::record_expr_field_list(fields).clone_for_update(); + let ast = make::record_expr_field_list(fields); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -1704,7 +1669,7 @@ impl SyntaxFactory { name: ast::NameRef, expr: Option, ) -> ast::RecordExprField { - let ast = make::record_expr_field(name.clone(), expr.clone()).clone_for_update(); + let ast = make::record_expr_field(name.clone(), expr.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -1738,7 +1703,7 @@ impl SyntaxFactory { fields: impl IntoIterator, ) -> ast::RecordFieldList { let (fields, input) = iterator_input(fields); - let ast = make::record_field_list(fields).clone_for_update(); + let ast = make::record_field_list(fields); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -1757,8 +1722,7 @@ impl SyntaxFactory { name: ast::Name, ty: ast::Type, ) -> ast::RecordField { - let ast = - make::record_field(visibility.clone(), name.clone(), ty.clone()).clone_for_update(); + let ast = make::record_field(visibility.clone(), name.clone(), ty.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -1783,7 +1747,7 @@ impl SyntaxFactory { fields: impl IntoIterator, ) -> ast::TupleFieldList { let (fields, input) = iterator_input(fields); - let ast = make::tuple_field_list(fields).clone_for_update(); + let ast = make::tuple_field_list(fields); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -1801,7 +1765,7 @@ impl SyntaxFactory { visibility: Option, ty: ast::Type, ) -> ast::TupleField { - let ast = make::tuple_field(visibility.clone(), ty.clone()).clone_for_update(); + let ast = make::tuple_field(visibility.clone(), ty.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -1836,8 +1800,7 @@ impl SyntaxFactory { generic_param_list.clone(), where_clause.clone(), variant_list.clone(), - ) - .clone_for_update(); + ); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -1880,7 +1843,7 @@ impl SyntaxFactory { variants: impl IntoIterator, ) -> ast::VariantList { let (variants, input) = iterator_input(variants); - let ast = make::variant_list(variants).clone_for_update(); + let ast = make::variant_list(variants); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -1905,8 +1868,7 @@ impl SyntaxFactory { name.clone(), field_list.clone(), discriminant.clone(), - ) - .clone_for_update(); + ); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -2012,7 +1974,7 @@ impl SyntaxFactory { ) -> ast::AssocItemList { let (items, input) = iterator_input(items); let items_vec: Vec<_> = items.into_iter().collect(); - let ast = make::assoc_item_list(Some(items_vec)).clone_for_update(); + let ast = make::assoc_item_list(Some(items_vec)); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -2024,13 +1986,13 @@ impl SyntaxFactory { } pub fn assoc_item_list_empty(&self) -> ast::AssocItemList { - make::assoc_item_list(None).clone_for_update() + make::assoc_item_list(None) } pub fn item_list(&self, items: impl IntoIterator) -> ast::ItemList { let (items, input) = iterator_input(items); let items_vec: Vec<_> = items.into_iter().collect(); - let ast = make::item_list(Some(items_vec)).clone_for_update(); + let ast = make::item_list(Some(items_vec)); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -2042,7 +2004,7 @@ impl SyntaxFactory { } pub fn mod_(&self, name: ast::Name, body: Option) -> ast::Module { - let ast = make::mod_(name.clone(), body.clone()).clone_for_update(); + let ast = make::mod_(name.clone(), body.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -2057,7 +2019,7 @@ impl SyntaxFactory { } pub fn attr_outer(&self, meta: ast::Meta) -> ast::Attr { - let ast = make::attr_outer(meta.clone()).clone_for_update(); + let ast = make::attr_outer(meta.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -2069,7 +2031,7 @@ impl SyntaxFactory { } pub fn attr_inner(&self, meta: ast::Meta) -> ast::Attr { - let ast = make::attr_inner(meta.clone()).clone_for_update(); + let ast = make::attr_inner(meta.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -2081,7 +2043,7 @@ impl SyntaxFactory { } pub fn meta_token_tree(&self, path: ast::Path, tt: ast::TokenTree) -> ast::Meta { - let ast = make::meta_token_tree(path.clone(), tt.clone()).clone_for_update(); + let ast = make::meta_token_tree(path.clone(), tt.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -2095,7 +2057,7 @@ impl SyntaxFactory { } pub fn cfg_flag(&self, flag: &str) -> ast::CfgPredicate { - make::cfg_flag(flag).clone_for_update() + make::cfg_flag(flag) } pub fn cfg_attr_meta( @@ -2104,7 +2066,7 @@ impl SyntaxFactory { inner: impl IntoIterator, ) -> ast::CfgAttrMeta { let inner = Vec::from_iter(inner); - let ast = make::cfg_attr_meta(predicate.clone(), inner.iter().cloned()).clone_for_update(); + let ast = make::cfg_attr_meta(predicate.clone(), inner.iter().cloned()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -2130,7 +2092,7 @@ impl SyntaxFactory { let tt: Vec<_> = tt.into_iter().collect(); let input: Vec<_> = tt.iter().cloned().filter_map(only_nodes).collect(); - let ast = make::token_tree(delimiter, tt).clone_for_update(); + let ast = make::token_tree(delimiter, tt); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -2158,7 +2120,7 @@ impl SyntaxFactory { } pub fn mut_self_param(&self) -> ast::SelfParam { - let ast = make::mut_self_param().clone_for_update(); + let ast = make::mut_self_param(); if let Some(mut mapping) = self.mappings() { let builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -2169,7 +2131,7 @@ impl SyntaxFactory { } pub fn self_param(&self) -> ast::SelfParam { - let ast = make::self_param().clone_for_update(); + let ast = make::self_param(); if let Some(mut mapping) = self.mappings() { let builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -2196,8 +2158,7 @@ impl SyntaxFactory { path_type.clone(), where_clause.clone(), body.clone(), - ) - .clone_for_update(); + ); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -2241,8 +2202,7 @@ impl SyntaxFactory { 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()); @@ -2269,7 +2229,7 @@ impl SyntaxFactory { } pub fn ret_type(&self, ty: ast::Type) -> ast::RetType { - let ast = make::ret_type(ty.clone()).clone_for_update(); + let ast = make::ret_type(ty.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -2280,7 +2240,7 @@ impl SyntaxFactory { } pub fn ty_ref(&self, ty: ast::Type, is_mut: bool) -> ast::Type { - let ast = make::ty_ref(ty.clone(), is_mut).clone_for_update(); + let ast = make::ty_ref(ty.clone(), is_mut); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -2303,7 +2263,7 @@ impl SyntaxFactory { } pub fn ty_name(&self, name: ast::Name) -> ast::Type { - let ast = make::ext::ty_name(name.clone()).clone_for_update(); + let ast = make::ext::ty_name(name.clone()); if let Some(mut mapping) = self.mappings() && let ast::Type::PathType(path_ty) = &ast @@ -2318,9 +2278,7 @@ impl SyntaxFactory { } pub fn expr_await(&self, expr: ast::Expr) -> ast::AwaitExpr { - let ast::Expr::AwaitExpr(ast) = make::expr_await(expr.clone()).clone_for_update() else { - unreachable!() - }; + let ast::Expr::AwaitExpr(ast) = make::expr_await(expr.clone()) else { unreachable!() }; if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -2332,7 +2290,7 @@ impl SyntaxFactory { } pub fn expr_try(&self, expr: ast::Expr) -> ast::Expr { - let ast = make::expr_try(expr.clone()).clone_for_update(); + let ast = make::expr_try(expr.clone()); if let Some(mut mapping) = self.mappings() { let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); @@ -2353,8 +2311,7 @@ impl SyntaxFactory { tail_expr: Option, ) -> ast::BlockExpr { let elements = elements.into_iter().collect::>(); - let ast = - make::hacky_block_expr(elements.iter().cloned(), tail_expr.clone()).clone_for_update(); + let ast = make::hacky_block_expr(elements.iter().cloned(), tail_expr.clone()); if let Some(mut mapping) = self.mappings() && let Some(stmt_list) = ast.stmt_list() @@ -2379,9 +2336,7 @@ impl SyntaxFactory { } pub fn expr_break(&self, label: Option, expr: Option) -> ast::BreakExpr { - let ast::Expr::BreakExpr(ast) = - make::expr_break(label.clone(), expr.clone()).clone_for_update() - else { + let ast::Expr::BreakExpr(ast) = make::expr_break(label.clone(), expr.clone()) else { unreachable!() }; @@ -2400,8 +2355,7 @@ impl SyntaxFactory { } pub fn expr_continue(&self, label: Option) -> ast::ContinueExpr { - let ast::Expr::ContinueExpr(ast) = make::expr_continue(label.clone()).clone_for_update() - else { + let ast::Expr::ContinueExpr(ast) = make::expr_continue(label.clone()) else { unreachable!() }; @@ -2427,11 +2381,11 @@ impl SyntaxFactory { &self, parts: impl IntoIterator, ) -> Option { - make::ext::path_from_idents(parts).map(|path| path.clone_for_update()) + make::ext::path_from_idents(parts) } pub fn token_tree_from_node(&self, node: &SyntaxNode) -> ast::TokenTree { - make::ext::token_tree_from_node(node).clone_for_update() + make::ext::token_tree_from_node(node) } pub fn expr_unit(&self) -> ast::Expr { From df6ded56740465f255e7fd33b99abdc8171362bb Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 24 Jul 2026 16:54:03 +0530 Subject: [PATCH 08/63] remove clone_for_update from AstNode --- src/tools/rust-analyzer/crates/syntax/src/ast.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast.rs b/src/tools/rust-analyzer/crates/syntax/src/ast.rs index d8c7e1583031d..855b5a80a5f6d 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast.rs @@ -61,12 +61,6 @@ pub trait AstNode { Self: Sized; fn syntax(&self) -> &SyntaxNode; - fn clone_for_update(&self) -> Self - where - Self: Sized, - { - Self::cast(self.syntax().clone_for_update()).unwrap() - } fn clone_subtree(&self) -> Self where Self: Sized, From 507ea9ed2b5755196bba20dd367e8a281cef3a4b Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 24 Jul 2026 17:07:27 +0530 Subject: [PATCH 09/63] make syntax editor void of any mutable API --- .../crates/syntax/src/syntax_editor.rs | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 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 7d15195c6f1f7..3ddc7914760eb 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/syntax_editor.rs @@ -1,6 +1,6 @@ //! Syntax Tree editor //! -//! Inspired by Roslyn's [`SyntaxEditor`], but is temporarily built upon mutable syntax tree editing. +//! Inspired by Roslyn's [`SyntaxEditor`]. //! //! [`SyntaxEditor`]: https://github.com/dotnet/roslyn/blob/43b0b05cc4f492fd5de00f6f6717409091df8daa/src/Workspaces/Core/Portable/Editing/SyntaxEditor.cs @@ -39,13 +39,12 @@ impl SyntaxEditor { /// Creates a syntax editor from `root`. /// /// 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. + /// If the provided node is not a root (i.e., has a parent), it is cloned + /// into a fresh subtree to satisfy syntax editor invariants. pub fn new(root: SyntaxNode) -> (Self, SyntaxNode) { let mut root = root; - if root.parent().is_some() || root.is_mutable() { + if root.parent().is_some() { root = root.clone_subtree() }; @@ -603,7 +602,7 @@ mod tests { let to_replace = root.syntax().descendants().find_map(ast::BinExpr::cast).unwrap(); let name = make::name("var_name"); - let name_ref = make::name_ref("var_name").clone_for_update(); + let name_ref = make::name_ref("var_name"); let placeholder_snippet = SyntaxAnnotation::default(); editor.add_annotation(name.syntax(), placeholder_snippet); @@ -884,7 +883,7 @@ mod tests { } #[test] - fn test_more_times_replace_node_to_mutable_token() { + fn test_more_times_replace_node_to_same_token() { let arg_list = make::arg_list([make::expr_literal("1").into(), make::expr_literal("2").into()]); @@ -903,13 +902,13 @@ mod tests { } #[test] - fn test_more_times_replace_node_to_mutable() { + fn test_more_times_replace_node_to_same_node() { let arg_list = make::arg_list([make::expr_literal("1").into(), make::expr_literal("2").into()]); let (editor, arg_list) = SyntaxEditor::with_ast_node(&arg_list); - let target_expr = make::expr_literal("3").clone_for_update(); + let target_expr = make::expr_literal("3"); for arg in arg_list.args() { editor.replace(arg.syntax(), target_expr.syntax()); @@ -922,13 +921,13 @@ mod tests { } #[test] - fn test_more_times_insert_node_to_mutable() { + fn test_more_times_insert_node_to_same_node() { let arg_list = make::arg_list([make::expr_literal("1").into(), make::expr_literal("2").into()]); let (editor, arg_list) = SyntaxEditor::with_ast_node(&arg_list); - let target_expr = make::ext::expr_unit().clone_for_update(); + let target_expr = make::ext::expr_unit(); for arg in arg_list.args() { editor.insert(Position::before(arg.syntax()), target_expr.syntax()); From 01c1b9aeb5fd9bb8679a614950b2284eb6aac0a1 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 24 Jul 2026 17:10:41 +0530 Subject: [PATCH 10/63] update mapping for element and remove for node --- .../syntax/src/syntax_editor/mapping.rs | 64 +++++-------------- 1 file changed, 16 insertions(+), 48 deletions(-) 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 464682223cd0e..d6498a5ec93fb 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 @@ -127,55 +127,23 @@ impl SyntaxMapping { Err(MissingMapping(current)) } - pub fn upmap_element( - &self, - input: &SyntaxElement, - output_root: &SyntaxNode, - ) -> Option> { - match input { - SyntaxElement::Node(node) => { - Some(self.upmap_node(node, output_root)?.map(SyntaxElement::Node)) - } - SyntaxElement::Token(token) => { - let upmap_parent = match self.upmap_node(&token.parent().unwrap(), output_root)? { - Ok(it) => it, - Err(err) => return Some(Err(err)), - }; + pub(super) fn upmap_element(&self, input: &SyntaxElement) -> SyntaxElement { + let mut current = input.clone(); - let element = upmap_parent.children_with_tokens().nth(token.index()).unwrap(); - debug_assert!( - element.as_token().is_some_and(|it| it.kind() == token.kind()), - "token upmapping mapped to the wrong node ({token:?} -> {element:?})" - ); - - Some(Ok(element)) - } - } - } - - pub fn upmap_node( - &self, - input: &SyntaxNode, - output_root: &SyntaxNode, - ) -> Option> { - // Try to follow the mapping tree, if it exists - let input_mapping = self.upmap_node_single(input); - let input_ancestor = - input.ancestors().find(|ancestor| self.upmap_node_single(ancestor).is_some()); - - match (input_mapping, input_ancestor) { - (Some(input_mapping), _) => { - // A mapping exists at the input, follow along the tree - Some(self.upmap_child(&input_mapping, &input_mapping, output_root)) - } - (None, Some(input_ancestor)) => { - // A mapping exists at an ancestor, follow along the tree - Some(self.upmap_child(input, &input_ancestor, output_root)) - } - (None, None) => { - // No mapping exists at all, is the same position in the final tree - None - } + loop { + let node = match ¤t { + SyntaxElement::Node(node) => node.clone(), + SyntaxElement::Token(token) => token.parent().unwrap(), + }; + let Some(input_ancestor) = + node.ancestors().find(|ancestor| self.upmap_node_single(ancestor).is_some()) + else { + return current; + }; + let output_ancestor = self.upmap_node_single(&input_ancestor).unwrap(); + current = self + .upmap_child_element(¤t, &input_ancestor, &output_ancestor.parent().unwrap()) + .expect("the nearest mapped ancestor must map its descendants"); } } From 70c3ea65c629fd6a95be63e8d0f180fce6787531 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 24 Jul 2026 17:11:01 +0530 Subject: [PATCH 11/63] update edits with new annotation semantics --- .../src/handlers/generate_blanket_trait_impl.rs | 2 +- .../src/handlers/replace_derive_with_manual_impl.rs | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs index 1e3f2ac677254..acd98aed00cee 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs @@ -141,7 +141,7 @@ pub(crate) fn generate_blanket_trait_impl( if let Some(cap) = ctx.config.snippet_cap && let Some(self_ty) = impl_.self_ty() { - builder.add_tabstop_before(cap, self_ty); + editor.add_annotation(self_ty.syntax(), builder.make_tabstop_before(cap)); } builder.add_file_edits(ctx.vfs_file_id(), editor); }, 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 4e85b30b58188..707b3321c338d 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 @@ -187,15 +187,18 @@ fn add_assist( && m.syntax().text() == "todo!()" { // Make the `todo!()` a placeholder - builder.add_placeholder_snippet(cap, m); + editor.add_annotation(m.syntax(), builder.make_placeholder_snippet(cap)); } else { // If we haven't already added a snippet, add a tabstop before the generated function - builder.add_tabstop_before(cap, first_assoc_item); + editor.add_annotation( + first_assoc_item.syntax(), + builder.make_tabstop_before(cap), + ); } } else if let Some(l_curly) = impl_def.assoc_item_list().and_then(|it| it.l_curly_token()) { - builder.add_tabstop_after_token(cap, l_curly); + editor.add_annotation(l_curly, builder.make_tabstop_after(cap)); } } From 6877440aca121b8fc6ec64b9c04d07163d2ac1c1 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 24 Jul 2026 19:00:53 +0530 Subject: [PATCH 12/63] Refactor edit_algo around immutable syntax tree with better semantics --- .../syntax/src/syntax_editor/edit_algo.rs | 1052 ++++++++++------- 1 file changed, 653 insertions(+), 399 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 36f50e39186c6..71b03784e981e 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 @@ -1,415 +1,697 @@ //! Implementation of applying changes to a syntax tree. -use std::{ - cmp::Ordering, - collections::VecDeque, - ops::{Range, RangeInclusive}, -}; +use std::{cmp::Ordering, ops::Range}; use rowan::TextRange; use rustc_hash::FxHashMap; use stdx::format_to; -use crate::{ - SyntaxElement, SyntaxNode, SyntaxNodePtr, - syntax_editor::{Change, ChangeKind, PositionRepr, mapping::MissingMapping}, -}; - -use super::{SyntaxEdit, SyntaxEditor}; +use crate::{NodeOrToken, SyntaxElement, SyntaxNode}; -pub(super) fn apply_edits(editor: SyntaxEditor) -> SyntaxEdit { - // Algorithm overview: - // - // - Sort changes by (range, type) - // - Ensures that parent edits are before child edits - // - Ensures that inserts will be guaranteed to be inserted at the right range - // - Validate changes - // - Checking for invalid changes is easy since the changes will be sorted by range - // - Fixup change targets - // - standalone change? map to original syntax tree - // - dependent change? - // - try to map to parent change (either independent or another dependent) - // - note: need to keep track of a parent change stack, since a change can be a parent of multiple changes - // - Apply changes - // - find changes to apply to real tree by applying nested changes first - // - changed nodes become part of the changed node set (useful for the formatter to only change those parts) - // - Propagate annotations +use super::{ + Change, ChangeKind, PositionRepr, SyntaxAnnotation, SyntaxEdit, SyntaxEditor, SyntaxMapping, + mapping::MissingMapping, +}; - let SyntaxEditor { root, changes, annotations, make } = editor; - let mut changes = changes.into_inner(); - let annotations = annotations.into_inner(); - let mappings = make.take(); +/// A validated batch of changes in the exact order in which it must execute. +/// +/// Planning is deliberately separate from tree mutation. Once an `EditPlan` +/// exists, execution does not need to reason about overlaps, dependencies, or +/// source ordering. +struct EditPlan { + changes: Vec, +} - let mut node_depths = FxHashMap::::default(); - let mut get_node_depth = |node: SyntaxNode| { - *node_depths.entry(node).or_insert_with_key(|node| node.ancestors().count()) - }; +/// A change whose target tree and output tracking are fully known. +struct PlannedChange { + tree: SyntaxNode, + change: Change, + record_as_changed: bool, +} - // Sort changes by range, then depth, then change kind, so that we can: - // - ensure that parent edits are ordered before child edits - // - ensure that inserts will be guaranteed to be inserted at the right range - // - easily check for disjoint replace ranges - changes.sort_by(|a, b| { - a.target_range() - .start() - .cmp(&b.target_range().start()) - .then_with(|| { - let a_target = a.target_parent(); - let b_target = b.target_parent(); +impl PlannedChange { + /// Returns the immutable source elements that this change will slice in. + fn replacement_elements(&self) -> &[SyntaxElement] { + match &self.change { + Change::Insert(_, element) | Change::Replace(_, Some(element)) => { + std::slice::from_ref(element) + } + Change::InsertAll(_, elements) + | Change::ReplaceWithMany(_, elements) + | Change::ReplaceAll(_, elements) => elements, + Change::Replace(_, None) => &[], + } + } +} - if a_target == b_target { - return Ordering::Equal; - } +/// The dependency info accumulated from one source ordered changes. +/// +/// `parent` is an edge to the nearest containing node replacement. A discarded +/// entry has no executable graph node because an ancestor deletion or ambiguous +/// range replacement has mde its target unavailable. +#[derive(Clone, Copy, Default)] +struct PlanEntry { + parent: Option, + discarded: bool, +} - get_node_depth(a_target).cmp(&get_node_depth(b_target)) - }) - .then(a.change_kind().cmp(&b.change_kind())) - }); +/// Planning failure containing the source-ordered changes use for diag. +struct InvalidEditPlan { + changes: Vec, +} - let disjoint_replaces_ranges = changes - .iter() - .zip(changes.iter().skip(1)) - .filter(|(l, r)| { - // We only care about checking for disjoint replace ranges - matches!( - (l.change_kind(), r.change_kind()), - ( - ChangeKind::Replace | ChangeKind::ReplaceRange, - ChangeKind::Replace | ChangeKind::ReplaceRange - ) - ) - }) - .all(|(l, r)| { - get_node_depth(l.target_parent()) != get_node_depth(r.target_parent()) - || (l.target_range().end() <= r.target_range().start()) +impl EditPlan { + /// Validates raw editor changes and turns them into an execution schedule. + /// + /// The input is first sorted in source order, dependent targets are then + /// rewritten from their input trees into ancestor replacement trees. Finally, + /// discarded changes are removed and the dependency forest is traversed in + /// postorder. + /// Independent roots and sibling changes are prioritized right to left. + fn build( + mut changes: Vec, + mappings: &SyntaxMapping, + mut node_depth: impl FnMut(SyntaxNode) -> usize, + ) -> Result { + changes.sort_by(|left, right| { + left.target_range() + .start() + .cmp(&right.target_range().start()) + .then_with(|| { + let left_target = left.target_parent(); + let right_target = right.target_parent(); + if left_target == right_target { + Ordering::Equal + } else { + node_depth(left_target).cmp(&node_depth(right_target)) + } + }) + .then(left.change_kind().cmp(&right.change_kind())) }); - if !disjoint_replaces_ranges { - report_intersecting_changes(&changes, get_node_depth, &root); + if !Self::replacements_are_disjoint(&changes, &mut node_depth) { + return Err(InvalidEditPlan { changes }); + } - return SyntaxEdit { - old_root: root.clone(), - new_root: root, - annotations: Default::default(), - changed_elements: vec![], - }; - } + let mut entries = vec![PlanEntry::default(); changes.len()]; + let mut regions_by_tree = FxHashMap::>::default(); + + for (index, change) in changes.iter().enumerate() { + let target_tree = change.target_parent().tree_top(); + let regions = regions_by_tree.entry(target_tree).or_default(); + if let Some(region_index) = regions + .iter() + .rposition(|region| region.range.contains_range(change.target_range())) + { + regions.truncate(region_index + 1); + match regions[region_index].nested_changes { + NestedChanges::Remap => { + entries[index].parent = Some(regions[region_index].change_index); + } + NestedChanges::Discard => entries[index].discarded = true, + } + } else { + regions.clear(); + } - #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] - struct DependentChange { - parent: u32, - child: u32, - } + if let Some(region) = ChangedRegion::for_change(change, index, entries[index].discarded) + { + regions.push(region); + } + } - // Build change tree - let mut changed_ancestors: VecDeque = VecDeque::new(); - let mut dependent_changes = vec![]; - let mut independent_changes = vec![]; - let mut outdated_changes = vec![]; + // Work from the innermost dependency towards the outermost one. This + // lets a chain A -> B -> C rewrite C into B before B itself is mapped + // into A's replacement tree. + for (child, entry) in entries.iter().enumerate().rev() { + if let Some(parent) = entry.parent { + Self::rewrite_dependent_target(&mut changes, parent, child, mappings); + } + } - for (change_index, change) in changes.iter().enumerate() { - // Check if this change is dependent on another change (i.e. it's contained within another range) - if let Some(index) = changed_ancestors + let mut children = vec![Vec::new(); changes.len()]; + for (child, entry) in entries.iter().enumerate() { + if let Some(parent) = entry.parent { + children[parent].push(child); + } + } + for siblings in &mut children { + siblings.sort_by(|&left, &right| { + Self::execution_priority(&changes[left], &changes[right], &mut node_depth) + }); + } + + let mut roots = entries .iter() - .rposition(|ancestor| ancestor.affected_range().contains_range(change.target_range())) - { - // Pop off any ancestors that aren't applicable - changed_ancestors.drain((index + 1)..); + .enumerate() + .filter_map(|(index, entry)| { + (!entry.discarded && entry.parent.is_none()).then_some(index) + }) + .collect::>(); + roots.sort_by(|&left, &right| { + Self::execution_priority(&changes[left], &changes[right], &mut node_depth) + }); - // FIXME: Resolve changes that depend on a range of elements - let ancestor = &changed_ancestors[index]; + let mut planned = changes + .into_iter() + .zip(entries) + .map(|(change, entry)| { + (!entry.discarded).then_some(PlannedChange { + tree: change.target_parent().tree_top(), + change, + record_as_changed: entry.parent.is_none(), + }) + }) + .collect::>(); - if let Change::Replace(_, None) = changes[ancestor.change_index] { - outdated_changes.push(change_index as u32); - } else { - dependent_changes.push(DependentChange { - parent: ancestor.change_index as u32, - child: change_index as u32, - }); - } - } else { - // This change is independent of any other change + let mut ordered = Vec::new(); + for root in roots { + Self::append_postorder(root, &children, &mut planned, &mut ordered); + } + + Ok(Self { changes: ordered }) + } - // Drain the changed ancestors since we're no longer in a set of dependent changes - changed_ancestors.drain(..); + /// Orders disjoint changes from right to left, with deeper ties first. + fn execution_priority( + left: &Change, + right: &Change, + node_depth: &mut impl FnMut(SyntaxNode) -> usize, + ) -> Ordering { + right + .target_range() + .start() + .cmp(&left.target_range().start()) + .then_with(|| node_depth(right.target_parent()).cmp(&node_depth(left.target_parent()))) + .then(right.change_kind().cmp(&left.change_kind())) + } - independent_changes.push(change_index as u32); + /// Appends a dependency subtree in post order fashion. + fn append_postorder( + index: usize, + children: &[Vec], + planned: &mut [Option], + ordered: &mut Vec, + ) { + for &child in &children[index] { + Self::append_postorder(child, children, planned, ordered); } + ordered.push(planned[index].take().expect("reachable plan nodes are not discarded")); + } - // Add to changed ancestors, if applicable - match change { - Change::Replace(SyntaxElement::Node(target), _) - | Change::ReplaceWithMany(SyntaxElement::Node(target), _) => { - changed_ancestors.push_back(ChangedAncestor::single(target, change_index)) + /// Checks that replacement at the same tree depth do not overlap + fn replacements_are_disjoint( + changes: &[Change], + mut node_depth: impl FnMut(SyntaxNode) -> usize, + ) -> bool { + let mut previous = FxHashMap::<(SyntaxNode, usize), TextRange>::default(); + for change in changes { + if !matches!(change.change_kind(), ChangeKind::Replace | ChangeKind::ReplaceRange) { + continue; } - Change::ReplaceAll(range, _) => { - changed_ancestors.push_back(ChangedAncestor::multiple(range, change_index)) + + let parent = change.target_parent(); + let key = (parent.tree_top(), node_depth(parent)); + if previous + .insert(key, change.target_range()) + .is_some_and(|range| range.end() > change.target_range().start()) + { + return false; } - _ => (), } + true } - // Map change targets to the correct syntax nodes - let tree_mutator = TreeMutator::new(&root); - let mut changed_elements = vec![]; - let mut changed_elements_set = rustc_hash::FxHashSet::default(); - let mut deduplicate_node = |node_or_token: &mut SyntaxElement| { - let node; - let node = match node_or_token { - SyntaxElement::Token(token) => match token.parent() { - None => return, - Some(parent) => { - node = parent; - &node - } - }, - SyntaxElement::Node(node) => node, + /// Maps one dependent change into its ancestor replacement tree. + fn rewrite_dependent_target( + changes: &mut [Change], + parent: usize, + child: usize, + mappings: &SyntaxMapping, + ) { + let (input_ancestor, output_ancestor) = match &changes[parent] { + Change::Replace( + SyntaxElement::Node(target), + Some(SyntaxElement::Node(replacement)), + ) => (target.clone(), replacement.clone()), + _ => unreachable!("only node replacements can own dependent changes"), }; - if changed_elements_set.contains(node) { - let new_node = node.clone_subtree().clone_for_update(); - match node_or_token { - SyntaxElement::Node(node) => *node = new_node, - SyntaxElement::Token(token) => { - *token = new_node - .children_with_tokens() - .filter_map(SyntaxElement::into_token) - .find(|it| it.kind() == token.kind() && it.text() == token.text()) - .unwrap(); - } - } - } else { - changed_elements_set.insert(node.clone()); - } - }; - for index in independent_changes { - match &mut changes[index as usize] { - Change::Insert(target, _) | Change::InsertAll(target, _) => { - match &mut target.repr { - PositionRepr::FirstChild(parent) => { - *parent = tree_mutator.make_syntax_mut(parent); - } - PositionRepr::After(child) => { - *child = tree_mutator.make_element_mut(child); - } - }; - } - Change::Replace(SyntaxElement::Node(target), Some(SyntaxElement::Node(_))) => { - *target = tree_mutator.make_syntax_mut(target); + let upmap_node = |target: &SyntaxNode| { + mappings.upmap_child(target, &input_ancestor, &output_ancestor).unwrap_or_else( + |MissingMapping(current)| { + panic!( + "no mappings exist between {current:?} (ancestor of {input_ancestor:?}) and {output_ancestor:?}" + ) + }, + ) + }; + let upmap_element = |target: &SyntaxElement| { + mappings.upmap_child_element(target, &input_ancestor, &output_ancestor).unwrap_or_else( + |MissingMapping(current)| { + panic!( + "no mappings exist between {current:?} (ancestor of {input_ancestor:?}) and {output_ancestor:?}" + ) + }, + ) + }; + + match &mut changes[child] { + Change::Insert(position, _) | Change::InsertAll(position, _) => { + match &mut position.repr { + PositionRepr::FirstChild(parent) => *parent = upmap_node(parent), + PositionRepr::After(child) => *child = upmap_element(child), + } } Change::Replace(target, _) | Change::ReplaceWithMany(target, _) => { - *target = tree_mutator.make_element_mut(target); + *target = upmap_element(target); } Change::ReplaceAll(range, _) => { - let start = tree_mutator.make_element_mut(range.start()); - let end = tree_mutator.make_element_mut(range.end()); - - *range = start..=end; + *range = upmap_element(range.start())..=upmap_element(range.end()); } } + } +} - 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.is_mutable() { - *node = node.clone_for_update(); - } - } - Change::Insert(_, SyntaxElement::Token(token)) - | Change::Replace(_, Some(SyntaxElement::Token(token))) => { - if let Some(parent) = token.parent() { - let idx = token.index(); - let new_parent = parent.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 { - 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 let Some(parent) = token.parent() { - let idx = token.index(); - let new_parent = parent.clone_subtree().clone_for_update(); - *token = new_parent - .children_with_tokens() - .nth(idx) - .and_then(SyntaxElement::into_token) - .unwrap(); - } - } - } - } - } - _ => {} - } +/// A stable structural address expressed as `children_with_token` indices. +#[derive(Clone)] +struct SyntaxPath { + child_indices: Vec, +} - match &mut changes[index as usize] { - Change::Insert(_, element) | Change::Replace(_, Some(element)) => { - deduplicate_node(element); - } - Change::InsertAll(_, elements) - | Change::ReplaceWithMany(_, elements) - | Change::ReplaceAll(_, elements) => { - elements.iter_mut().for_each(&mut deduplicate_node); +impl SyntaxPath { + /// Builds the root-relative path of element in its current tree. + fn new(element: &SyntaxElement) -> Self { + let mut child_indices = Vec::new(); + let mut node = match element { + SyntaxElement::Node(node) => node.clone(), + SyntaxElement::Token(token) => { + child_indices.push(token.index()); + token.parent().unwrap() } - Change::Replace(_, None) => (), + }; + + while let Some(parent) = node.parent() { + child_indices.push(node.index()); + node = parent; } + child_indices.reverse(); + Self { child_indices } + } - // Collect changed elements - match &changes[index as usize] { - Change::Insert(_, element) => changed_elements.push(element.clone()), - Change::InsertAll(_, elements) => changed_elements.extend(elements.iter().cloned()), - Change::Replace(_, Some(element)) => changed_elements.push(element.clone()), - Change::Replace(_, None) => {} - Change::ReplaceWithMany(_, elements) => { - changed_elements.extend(elements.iter().cloned()) - } - Change::ReplaceAll(_, elements) => changed_elements.extend(elements.iter().cloned()), + /// Follows this path from root, returning None if the structure differs. + fn resolve(&self, root: &SyntaxNode) -> Option { + let mut current = SyntaxElement::Node(root.clone()); + for &index in &self.child_indices { + current = current.into_node()?.children_with_tokens().nth(index)?; } + Some(current) } - for DependentChange { parent, child } in dependent_changes.into_iter().rev() { - let (input_ancestor, output_ancestor) = match &changes[parent as usize] { - // No change will depend on an insert since changes can only depend on nodes in the root tree - Change::Insert(_, _) | Change::InsertAll(_, _) => unreachable!(), - Change::Replace(target, Some(new_target)) => { - (to_owning_node(target), to_owning_node(new_target)) - } - Change::Replace(_, None) => { - unreachable!("deletions should not generate dependent changes") - } - Change::ReplaceAll(_, _) | Change::ReplaceWithMany(_, _) => { - unimplemented!("cannot resolve changes that depend on replacing many elements") - } - }; + /// Removes `ancestor`'s prefix, yielding this path within that subtree. + /// + /// Could have used LCA? + fn relative_to(&self, ancestor: &SyntaxPath) -> Option { + self.child_indices + .strip_prefix(ancestor.child_indices.as_slice()) + .map(|relative| SyntaxPath { child_indices: relative.to_vec() }) + } - let upmap_target_node = |target: &SyntaxNode| match mappings.upmap_child( - target, - &input_ancestor, - &output_ancestor, - ) { - Ok(it) => it, - Err(MissingMapping(current)) => unreachable!( - "no mappings exist between {current:?} (ancestor of {input_ancestor:?}) and {output_ancestor:?}" - ), - }; + /// Appends an inserted child slot and a path relative to that child. + fn in_child(&self, index: usize, relative: &SyntaxPath) -> SyntaxPath { + let mut child_indices = + Vec::with_capacity(self.child_indices.len() + relative.child_indices.len() + 1); + child_indices.extend_from_slice(&self.child_indices); + child_indices.push(index); + child_indices.extend_from_slice(&relative.child_indices); + SyntaxPath { child_indices } + } - let upmap_target = |target: &SyntaxElement| match mappings.upmap_child_element( - target, - &input_ancestor, - &output_ancestor, - ) { - Ok(it) => it, - Err(MissingMapping(current)) => unreachable!( - "no mappings exist between {current:?} (ancestor of {input_ancestor:?}) and {output_ancestor:?}" - ), + /// Updates this path for a splice and reports whether its element survives. + fn adjust_for_splice( + &mut self, + parent: &SyntaxPath, + deleted: &Range, + inserted: usize, + ) -> bool { + let Some(relative) = self.child_indices.strip_prefix(parent.child_indices.as_slice()) + else { + return true; }; + let Some((&child, _)) = relative.split_first() else { return true }; - match &mut changes[child as usize] { - Change::Insert(target, _) | Change::InsertAll(target, _) => match &mut target.repr { - PositionRepr::FirstChild(parent) => { - *parent = upmap_target_node(parent); - } - PositionRepr::After(child) => { - *child = upmap_target(child); + if deleted.contains(&child) { + return false; + } + if child >= deleted.end { + let new_child = child + inserted; + self.child_indices[parent.child_indices.len()] = + new_child - (deleted.end - deleted.start); + } + true + } +} + +/// An annotation paired with its structural location and registration order. +#[derive(Clone)] +struct TrackedAnnotation { + path: SyntaxPath, + annotation: SyntaxAnnotation, + order: usize, +} + +/// A structural edit used to translate original paths into a current tree. +enum PathEdit { + /// A child-list splice with all coordinates relative to the pre-edit tree. + Splice { parent: SyntaxPath, deleted: Range, inserted: usize }, + /// A root replacement, after which no path into the old root survives. + ReplaceRoot, +} + +/// The evolving immutable root and location metadata for one source tree. +/// +/// A syntax edit can involve the editor root plus several detached factory +/// trees. Each receives an independent state so dependent edits can be applied +/// before a generated tree is inserted elsewhere. +struct TreeState { + root: SyntaxNode, + edits: Vec, + changed: Vec, + original_annotations: Vec, + annotations: Vec, +} + +impl TreeState { + /// Starts tracking an unmodified immutable root. + fn new(root: SyntaxNode) -> Self { + Self { + root, + edits: Vec::new(), + changed: Vec::new(), + original_annotations: Vec::new(), + annotations: Vec::new(), + } + } + + /// Replay structural edits to translate an original path into this state. + fn map_original_path(&self, mut path: SyntaxPath) -> Option { + for edit in &self.edits { + match edit { + PathEdit::Splice { parent, deleted, inserted } => { + if !path.adjust_for_splice(parent, deleted, *inserted) { + return None; + } } - }, - Change::Replace(target, _) | Change::ReplaceWithMany(target, _) => { - *target = upmap_target(target); - } - Change::ReplaceAll(range, _) => { - *range = upmap_target(range.start())..=upmap_target(range.end()); + PathEdit::ReplaceRoot => return None, } } + Some(path) } - // We reverse here since we pushed to this in ascending order, - // and we want to remove elements in descending order - for idx in outdated_changes.into_iter().rev() { - changes.remove(idx as usize); + /// Finds a change target in the current root. + fn map_original_element(&self, element: &SyntaxElement) -> SyntaxElement { + self.map_original_path(SyntaxPath::new(element)) + .and_then(|path| path.resolve(&self.root)) + .expect("an edit target must still be present") } - // Apply changes - let mut root = tree_mutator.mutable_clone; + /// Applies one child-list splice and updates tracked structural path. + fn splice( + &mut self, + parent_path: SyntaxPath, + deleted: Range, + inserted: Vec, + track_as_changed: bool, + ) { + let inserted_count = inserted.len(); + self.changed + .retain_mut(|path| path.adjust_for_splice(&parent_path, &deleted, inserted_count)); + self.annotations + .retain_mut(|it| it.path.adjust_for_splice(&parent_path, &deleted, inserted_count)); + + for (offset, element) in inserted.iter().enumerate() { + let index = deleted.start + offset; + if track_as_changed { + self.changed + .push(parent_path.in_child(index, &SyntaxPath { child_indices: Vec::new() })); + } + self.annotations.extend(element.annotations.iter().map(|annotation| { + TrackedAnnotation { + path: parent_path.in_child(index, &annotation.path), + annotation: annotation.annotation, + order: annotation.order, + } + })); + } + + let parent = parent_path.resolve(&self.root).and_then(SyntaxElement::into_node).unwrap(); + let green = rowan::GreenNodeData::splice_children( + parent.green().as_ref(), + deleted.clone(), + inserted.into_iter().map(PreparedElement::into_green), + ); + self.root = SyntaxNode::new_root(parent.replace_with(green)); + self.edits.push(PathEdit::Splice { + parent: parent_path, + deleted, + inserted: inserted_count, + }); + } + + /// Replaces the tree's root with a prepared node payload. + fn replace_root(&mut self, replacement: PreparedElement, track_as_changed: bool) { + let NodeOrToken::Node(node) = replacement.syntax else { + panic!("root node replacement should be a node") + }; + self.root = SyntaxNode::new_root(node.green().into_owned()); + self.changed.clear(); + if track_as_changed { + self.changed.push(SyntaxPath { child_indices: Vec::new() }); + } + self.annotations = replacement.annotations; + self.edits.push(PathEdit::ReplaceRoot); + } - for change in changes { + /// Applies a planned change to this tree using already prepared payloads. + fn apply( + &mut self, + change: &Change, + replacement: Vec, + record_as_changed: bool, + ) { match change { - Change::Insert(position, element) => { - let (parent, index) = position.place(); - parent.splice_children(index..index, vec![element]); - } - Change::InsertAll(position, elements) => { - let (parent, index) = position.place(); - parent.splice_children(index..index, elements); - } - Change::Replace(target, None) => { - target.detach(); - } - Change::Replace(SyntaxElement::Node(target), Some(new_target)) if target == root => { - root = new_target.into_node().expect("root node replacement should be a node"); + Change::Insert(position, _) | Change::InsertAll(position, _) => { + let (parent, index) = match &position.repr { + PositionRepr::FirstChild(parent) => { + let parent = self.map_original_element(&parent.clone().into()); + (parent.into_node().unwrap(), 0) + } + PositionRepr::After(child) => { + let child = self.map_original_element(child); + (child.parent().unwrap(), child.index() + 1) + } + }; + self.splice( + SyntaxPath::new(&parent.into()), + index..index, + replacement, + record_as_changed, + ); } - Change::Replace(target, Some(new_target)) => { - let parent = target.parent().unwrap(); - parent.splice_children(target.index()..target.index() + 1, vec![new_target]); + Change::Replace(SyntaxElement::Node(target), Some(_)) if target.parent().is_none() => { + self.replace_root(replacement.into_iter().next().unwrap(), record_as_changed); } - Change::ReplaceWithMany(target, elements) => { + Change::Replace(target, _) | Change::ReplaceWithMany(target, _) => { + let target = self.map_original_element(target); let parent = target.parent().unwrap(); - parent.splice_children(target.index()..target.index() + 1, elements); + let index = target.index(); + self.splice( + SyntaxPath::new(&parent.into()), + index..index + 1, + replacement, + record_as_changed, + ); } - Change::ReplaceAll(range, elements) => { - let start = range.start().index(); - let end = range.end().index(); - let parent = range.start().parent().unwrap(); - parent.splice_children(start..end + 1, elements); + Change::ReplaceAll(range, _) => { + let start = self.map_original_element(range.start()); + let end = self.map_original_element(range.end()); + let parent = start.parent().unwrap(); + self.splice( + SyntaxPath::new(&parent.into()), + start.index()..end.index() + 1, + replacement, + record_as_changed, + ); } } } +} + +/// A replacement payload paired with the annotation below it. +/// +/// The syntax element remains an immutable snapshot of its source tree. +/// Annotation paths are relative to the payload root and are rebased by +/// splice +struct PreparedElement { + syntax: SyntaxElement, + annotations: Vec, +} - // Propagate annotations - let annotations = annotations.into_iter().filter_map(|(element, annotation)| { - match mappings.upmap_element(&element, &root) { - // Needed to follow the new tree to find the resulting element - Some(Ok(mapped)) => Some((mapped, annotation)), - // Element did not need to be mapped - None => Some((element, annotation)), - // Element did not make it to the final tree - Some(Err(_)) => None, +impl PreparedElement { + fn into_green(self) -> rowan::NodeOrToken { + match self.syntax { + SyntaxElement::Node(node) => NodeOrToken::Node(node.green().into_owned()), + SyntaxElement::Token(token) => NodeOrToken::Token(token.green().to_owned()), } - }); + } +} - let mut annotation_groups = FxHashMap::default(); +/// Owns all evolving trees involved in executing an edit plan. +struct TreeStore { + states: FxHashMap, +} - for (element, annotation) in annotations { - annotation_groups.entry(annotation).or_insert(vec![]).push(element); +impl TreeStore { + /// Creates per tree state for annotations after following factory mapping. + fn with_annotations( + annotations: Vec<(SyntaxElement, SyntaxAnnotation)>, + mappings: &SyntaxMapping, + ) -> Self { + let mut states = FxHashMap::::default(); + for (order, (element, annotation)) in annotations.into_iter().enumerate() { + let element = mappings.upmap_element(&element); + let tree = element.tree_top(); + let tracked = TrackedAnnotation { path: SyntaxPath::new(&element), annotation, order }; + let state = states.entry(tree.clone()).or_insert_with(|| TreeState::new(tree)); + state.original_annotations.push(tracked.clone()); + state.annotations.push(tracked); + } + Self { states } + } + + /// Execute an already ordered plan without performing further analysis. + fn execute(&mut self, plan: EditPlan) { + for planned in plan.changes { + self.states + .entry(planned.tree.clone()) + .or_insert_with(|| TreeState::new(planned.tree.clone())); + let replacement = planned + .replacement_elements() + .iter() + .map(|element| self.prepare_element(element)) + .collect(); + self.states.get_mut(&planned.tree).unwrap().apply( + &planned.change, + replacement, + planned.record_as_changed, + ); + } } - SyntaxEdit { - old_root: tree_mutator.immutable, - new_root: root, - changed_elements, - annotations: annotation_groups, + /// Captures the source element and annotations used by a replacement. + fn prepare_element(&self, element: &SyntaxElement) -> PreparedElement { + let tree = element.tree_top(); + let original_path = SyntaxPath::new(element); + let (element, annotations) = match self.states.get(&tree) { + Some(state) => { + let annotations_below = + |annotations: &[TrackedAnnotation], ancestor: &SyntaxPath| { + annotations + .iter() + .filter_map(|annotation| { + annotation.path.relative_to(ancestor).map(|path| { + TrackedAnnotation { + path, + annotation: annotation.annotation, + order: annotation.order, + } + }) + }) + .collect() + }; + match state.map_original_path(original_path.clone()) { + Some(path) => { + let element = path.resolve(&state.root).unwrap(); + let annotations = annotations_below(&state.annotations, &path); + (element, annotations) + } + None => { + let annotations = + annotations_below(&state.original_annotations, &original_path); + (element.clone(), annotations) + } + } + } + None => (element.clone(), Vec::new()), + }; + PreparedElement { syntax: element, annotations } + } + + /// Resolves the editor roots tracked paths and constructs the public edit. + fn finish(mut self, old_root: SyntaxNode) -> SyntaxEdit { + let state = + self.states.remove(&old_root).unwrap_or_else(|| TreeState::new(old_root.clone())); + let new_root = state.root; + + let mut changed_elements = state + .changed + .into_iter() + .filter_map(|path| path.resolve(&new_root)) + .collect::>(); + changed_elements.sort_by_key(|element| element.text_range().start()); + + let mut annotations = FxHashMap::>::default(); + for annotation in state.annotations { + if let Some(element) = annotation.path.resolve(&new_root) { + annotations + .entry(annotation.annotation) + .or_default() + .push((annotation.order, element)); + } + } + let annotations = annotations + .into_iter() + .map(|(annotation, mut elements)| { + elements.sort_by_key(|(order, element)| (*order, element.text_range().start())); + (annotation, elements.into_iter().map(|(_, element)| element).collect()) + }) + .collect(); + + SyntaxEdit { old_root, new_root, changed_elements, annotations } } } +/// Plans and executes all changes recorded by a SyntaxEditor. +pub(super) fn apply_edits(editor: SyntaxEditor) -> SyntaxEdit { + let SyntaxEditor { root, changes, annotations, make } = editor; + let mappings = make.take(); + let mut node_depths = FxHashMap::::default(); + let mut node_depth = |node: SyntaxNode| { + *node_depths.entry(node).or_insert_with_key(|node| node.ancestors().count()) + }; + + let plan = match EditPlan::build(changes.into_inner(), &mappings, &mut node_depth) { + Ok(plan) => plan, + Err(InvalidEditPlan { changes }) => { + report_intersecting_changes(&changes, &mut node_depth, &root); + return SyntaxEdit { + old_root: root.clone(), + new_root: root, + annotations: FxHashMap::default(), + changed_elements: Vec::new(), + }; + } + }; + + let mut trees = TreeStore::with_annotations(annotations.into_inner(), &mappings); + trees.execute(plan); + trees.finish(root) +} + fn report_intersecting_changes( changes: &[Change], - mut get_node_depth: impl FnMut(rowan::SyntaxNode) -> usize, - root: &rowan::SyntaxNode, + mut get_node_depth: impl FnMut(SyntaxNode) -> usize, + root: &SyntaxNode, ) { let intersecting_changes = changes .iter() @@ -478,77 +760,49 @@ fn report_intersecting_changes( stdx::always!(false, "{}", error_msg); } -fn to_owning_node(element: &SyntaxElement) -> SyntaxNode { - match element { - SyntaxElement::Node(node) => node.clone(), - SyntaxElement::Token(token) => token.parent().unwrap(), - } -} - -struct ChangedAncestor { - kind: ChangedAncestorKind, +/// A replacement region that can contain later source ordered changeds +struct ChangedRegion { + range: TextRange, change_index: usize, + nested_changes: NestedChanges, } -enum ChangedAncestorKind { - Single { node: SyntaxNode }, - Range { _changed_elements: RangeInclusive, _in_parent: SyntaxNode }, -} - -impl ChangedAncestor { - fn single(node: &SyntaxNode, change_index: usize) -> Self { - let kind = ChangedAncestorKind::Single { node: node.clone() }; - - Self { kind, change_index } - } - - fn multiple(range: &RangeInclusive, change_index: usize) -> Self { - Self { - kind: ChangedAncestorKind::Range { - _changed_elements: range.clone(), - _in_parent: range.start().parent().unwrap(), - }, - change_index, - } - } - - fn affected_range(&self) -> TextRange { - match &self.kind { - ChangedAncestorKind::Single { node } => node.text_range(), - ChangedAncestorKind::Range { _changed_elements: changed_nodes, _in_parent: _ } => { - TextRange::new( - changed_nodes.start().text_range().start(), - changed_nodes.end().text_range().end(), - ) - } - } - } -} - -struct TreeMutator { - immutable: SyntaxNode, - mutable_clone: SyntaxNode, +/// How changes nested within a replacement region are handled. +enum NestedChanges { + /// Map nested targets into a one-to-one node replacement. + Remap, + /// Drop nested changes because the replacement has no unique counterpart. + Discard, } -impl TreeMutator { - fn new(immutable: &SyntaxNode) -> TreeMutator { - let immutable = immutable.clone(); - let mutable_clone = immutable.clone_for_update(); - TreeMutator { immutable, mutable_clone } - } - - fn make_element_mut(&self, element: &SyntaxElement) -> SyntaxElement { - match element { - SyntaxElement::Node(node) => SyntaxElement::Node(self.make_syntax_mut(node)), - SyntaxElement::Token(token) => { - let parent = self.make_syntax_mut(&token.parent().unwrap()); - parent.children_with_tokens().nth(token.index()).unwrap() - } +impl ChangedRegion { + /// Describes a region replaced by change, if it can contain changes. + fn for_change(change: &Change, change_index: usize, discarded: bool) -> Option { + match change { + Change::Replace(SyntaxElement::Node(target), replacement) => Some(Self { + range: target.text_range(), + change_index, + nested_changes: if !discarded && matches!(replacement, Some(SyntaxElement::Node(_))) + { + NestedChanges::Remap + } else { + NestedChanges::Discard + }, + }), + Change::ReplaceWithMany(SyntaxElement::Node(target), _) => Some(Self { + range: target.text_range(), + change_index, + nested_changes: NestedChanges::Discard, + }), + Change::ReplaceAll(elements, _) => Some(Self { + range: TextRange::new( + elements.start().text_range().start(), + elements.end().text_range().end(), + ), + change_index, + nested_changes: NestedChanges::Discard, + }), + _ => None, } } - - fn make_syntax_mut(&self, node: &SyntaxNode) -> SyntaxNode { - let ptr = SyntaxNodePtr::new(node); - ptr.to_node(&self.mutable_clone) - } } From 49d4204b082dad2bfac39cbb9a0ebaa677497397 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Sun, 17 May 2026 03:34:46 +0300 Subject: [PATCH 13/63] fix: resolve path on all namespaces and return resolution based on visibility --- .../crates/hir-def/src/per_ns.rs | 8 +- .../crates/hir-def/src/resolver.rs | 83 +++++++--- .../crates/hir-ty/src/lower/path.rs | 4 +- .../crates/hir/src/source_analyzer.rs | 156 ++++++++++++------ .../crates/ide-assists/src/tests.rs | 1 - .../highlight_module_macro_conflict.html | 50 ++++++ .../test_data/private_multi_namespace.html | 46 ++++++ .../ide/src/syntax_highlighting/tests.rs | 48 ++++++ 8 files changed, 309 insertions(+), 87 deletions(-) create mode 100644 src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_module_macro_conflict.html create mode 100644 src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/private_multi_namespace.html diff --git a/src/tools/rust-analyzer/crates/hir-def/src/per_ns.rs b/src/tools/rust-analyzer/crates/hir-def/src/per_ns.rs index 8721cd65dbac7..f7e5ac316a2ca 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/per_ns.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/per_ns.rs @@ -109,16 +109,16 @@ impl PerNs { self.values.map(|it| it.def) } - pub fn take_values_import(self) -> Option<(ModuleDefId, Option)> { - self.values.map(|it| (it.def, it.import)) + pub fn take_values_full(self) -> Option { + self.values } pub fn take_macros(self) -> Option { self.macros.map(|it| it.def) } - pub fn take_macros_import(self) -> Option<(MacroId, Option)> { - self.macros.map(|it| (it.def, it.import)) + pub fn take_macros_full(self) -> Option { + self.macros } pub fn filter_visibility(self, mut f: impl FnMut(Visibility) -> bool) -> PerNs { diff --git a/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs b/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs index 63ff384de021a..5b11f5ff8bbb6 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs @@ -33,7 +33,7 @@ use crate::{ item_scope::{BUILTIN_SCOPE, BuiltinShadowMode, ImportOrExternCrate, ItemScope}, lang_item::LangItemTarget, nameres::{DefMap, LocalDefMap, MacroSubNs, ResolvePathResultPrefixInfo, block_def_map}, - per_ns::PerNs, + per_ns::{MacrosItem, PerNs}, signatures::ImplSignature, src::HasSource, type_ref::LifetimeRef, @@ -174,7 +174,9 @@ impl<'db> Resolver<'db> { path: &Path, ) -> Option<(TypeNs, Option, Option)> { self.resolve_path_in_type_ns_with_prefix_info(db, path).map( - |(resolution, remaining_segments, import, _)| (resolution, remaining_segments, import), + |(resolution, remaining_segments, import, _, _)| { + (resolution, remaining_segments, import) + }, ) } @@ -182,8 +184,13 @@ impl<'db> Resolver<'db> { &self, db: &dyn SourceDatabase, path: &Path, - ) -> Option<(TypeNs, Option, Option, ResolvePathResultPrefixInfo)> - { + ) -> Option<( + TypeNs, + Option, + Option, + ResolvePathResultPrefixInfo, + Visibility, + )> { let path = match path { Path::BarePath(mod_path) => mod_path, Path::Normal(it) => &it.mod_path, @@ -206,6 +213,7 @@ impl<'db> Resolver<'db> { seg.as_ref().map(|_| 1), None, ResolvePathResultPrefixInfo::default(), + Visibility::Public, )); } }; @@ -230,6 +238,7 @@ impl<'db> Resolver<'db> { remaining_idx(), None, ResolvePathResultPrefixInfo::default(), + Visibility::Public, )); } } else if let &GenericDefId::AdtId(adt) = def @@ -240,6 +249,7 @@ impl<'db> Resolver<'db> { remaining_idx(), None, ResolvePathResultPrefixInfo::default(), + Visibility::Public, )); } if let Some(id) = params.find_type_by_name(first_name, *def) { @@ -248,6 +258,7 @@ impl<'db> Resolver<'db> { remaining_idx(), None, ResolvePathResultPrefixInfo::default(), + Visibility::Public, )); } } @@ -264,6 +275,7 @@ impl<'db> Resolver<'db> { remaining_idx(), None, ResolvePathResultPrefixInfo::default(), + Visibility::Public, ) } else { res @@ -323,7 +335,7 @@ impl<'db> Resolver<'db> { path: &Path, hygiene_id: HygieneId, ) -> Option { - self.resolve_path_in_value_ns_with_prefix_info(db, path, hygiene_id).map(|(it, _)| it) + self.resolve_path_in_value_ns_with_prefix_info(db, path, hygiene_id).map(|(it, _, _)| it) } fn skip_to_mod<'this, T>( @@ -343,7 +355,7 @@ impl<'db> Resolver<'db> { db: &dyn SourceDatabase, path: &Path, mut hygiene_id: HygieneId, - ) -> Option<(ResolveValueResult, ResolvePathResultPrefixInfo)> { + ) -> Option<(ResolveValueResult, ResolvePathResultPrefixInfo, Visibility)> { let path = match path { Path::BarePath(mod_path) => mod_path, Path::Normal(it) => &it.mod_path, @@ -363,6 +375,7 @@ impl<'db> Resolver<'db> { | LangItemTarget::MacroId(_) => return None, }), ResolvePathResultPrefixInfo::default(), + Visibility::Public, )); } Path::LangItem(l, Some(_)) => { @@ -383,6 +396,7 @@ impl<'db> Resolver<'db> { return Some(( ResolveValueResult::Partial(type_ns, 0), ResolvePathResultPrefixInfo::default(), + Visibility::Public, )); } }; @@ -408,6 +422,7 @@ impl<'db> Resolver<'db> { return Some(( ResolveValueResult::ValueNs(ValueNs::LocalBinding(e.binding())), ResolvePathResultPrefixInfo::default(), + Visibility::Public, )); } } @@ -421,6 +436,7 @@ impl<'db> Resolver<'db> { return Some(( ResolveValueResult::ValueNs(ValueNs::ImplSelf(impl_)), ResolvePathResultPrefixInfo::default(), + Visibility::Public, )); } if let Some(id) = params.find_const_by_name(first_name, *def) { @@ -428,6 +444,7 @@ impl<'db> Resolver<'db> { return Some(( ResolveValueResult::ValueNs(val), ResolvePathResultPrefixInfo::default(), + Visibility::Public, )); } } @@ -448,6 +465,7 @@ impl<'db> Resolver<'db> { return Some(( ResolveValueResult::Partial(TypeNs::SelfType(impl_), 1), ResolvePathResultPrefixInfo::default(), + Visibility::Public, )); } } else if let &GenericDefId::AdtId(adt) = def @@ -457,6 +475,7 @@ impl<'db> Resolver<'db> { return Some(( ResolveValueResult::Partial(ty, 1), ResolvePathResultPrefixInfo::default(), + Visibility::Public, )); } if let Some(id) = params.find_type_by_name(first_name, *def) { @@ -464,6 +483,7 @@ impl<'db> Resolver<'db> { return Some(( ResolveValueResult::Partial(ty, 1), ResolvePathResultPrefixInfo::default(), + Visibility::Public, )); } } @@ -490,6 +510,7 @@ impl<'db> Resolver<'db> { return Some(( ResolveValueResult::Partial(TypeNs::BuiltinType(builtin), 1), ResolvePathResultPrefixInfo::default(), + Visibility::Public, )); } @@ -513,7 +534,7 @@ impl<'db> Resolver<'db> { db: &dyn SourceDatabase, path: &ModPath, expected_macro_kind: Option, - ) -> Option<(MacroId, Option)> { + ) -> Option { let (item_map, item_local_map, module) = self.item_scope_(); item_map .resolve_path( @@ -525,7 +546,7 @@ impl<'db> Resolver<'db> { expected_macro_kind, ) .0 - .take_macros_import() + .take_macros_full() } pub fn resolve_path_as_macro_def( @@ -534,7 +555,7 @@ impl<'db> Resolver<'db> { path: &ModPath, expected_macro_kind: Option, ) -> Option { - self.resolve_path_as_macro(db, path, expected_macro_kind).map(|(it, _)| it.definition(db)) + self.resolve_path_as_macro(db, path, expected_macro_kind).map(|it| it.def.definition(db)) } pub fn resolve_lifetime(&self, lifetime: &LifetimeRef) -> Option { @@ -1166,7 +1187,7 @@ impl<'db> ModuleItemMap<'db> { &self, db: &'db dyn SourceDatabase, path: &ModPath, - ) -> Option<(ResolveValueResult, ResolvePathResultPrefixInfo)> { + ) -> Option<(ResolveValueResult, ResolvePathResultPrefixInfo, Visibility)> { let (module_def, unresolved_idx, prefix_info) = self.def_map.resolve_path_locally( self.local_def_map, db, @@ -1176,12 +1197,12 @@ impl<'db> ModuleItemMap<'db> { ); match unresolved_idx { None => { - let value = to_value_ns(module_def, self.def_map)?; - Some((ResolveValueResult::ValueNs(value), prefix_info)) + let (value, vis) = to_value_ns(module_def, self.def_map)?; + Some((ResolveValueResult::ValueNs(value), prefix_info, vis)) } Some(unresolved_idx) => { - let def = module_def.take_types()?; - let ty = match def { + let res = module_def.take_types_full()?; + let ty = match res.def { ModuleDefId::AdtId(it) => TypeNs::AdtId(it), ModuleDefId::TraitId(it) => TypeNs::TraitId(it), ModuleDefId::TypeAliasId(it) => TypeNs::TypeAliasId(it), @@ -1194,7 +1215,7 @@ impl<'db> ModuleItemMap<'db> { | ModuleDefId::MacroId(_) | ModuleDefId::StaticId(_) => return None, }; - Some((ResolveValueResult::Partial(ty, unresolved_idx), prefix_info)) + Some((ResolveValueResult::Partial(ty, unresolved_idx), prefix_info, res.vis)) } } } @@ -1203,8 +1224,13 @@ impl<'db> ModuleItemMap<'db> { &self, db: &dyn SourceDatabase, path: &ModPath, - ) -> Option<(TypeNs, Option, Option, ResolvePathResultPrefixInfo)> - { + ) -> Option<( + TypeNs, + Option, + Option, + ResolvePathResultPrefixInfo, + Visibility, + )> { let (module_def, idx, prefix_info) = self.def_map.resolve_path_locally( self.local_def_map, db, @@ -1212,17 +1238,22 @@ impl<'db> ModuleItemMap<'db> { path, BuiltinShadowMode::Other, ); - let (res, import) = to_type_ns(module_def)?; - Some((res, idx, import, prefix_info)) + let (res, import, vis) = to_type_ns(module_def)?; + Some((res, idx, import, prefix_info, vis)) } } -fn to_value_ns(per_ns: PerNs, def_map: &DefMap) -> Option { - let def = per_ns.take_values().or_else(|| { - let Some(MacroId::ProcMacroId(proc_macro)) = per_ns.take_macros() else { return None }; +fn to_value_ns(per_ns: PerNs, def_map: &DefMap) -> Option<(ValueNs, Visibility)> { + let (def, vis) = per_ns.take_values_full().map(|res| (res.def, res.vis)).or_else(|| { + let Some(MacrosItem { def: MacroId::ProcMacroId(proc_macro), vis, .. }) = + per_ns.take_macros_full() + else { + return None; + }; // If we cannot resolve to value ns, but we can resolve to a proc macro, and this is the crate // defining this proc macro - inside this crate, we should treat the macro as a function. - def_map.proc_macro_as_fn(proc_macro).map(ModuleDefId::FunctionId) + let def = ModuleDefId::FunctionId(def_map.proc_macro_as_fn(proc_macro)?); + Some((def, vis)) })?; let res = match def { ModuleDefId::FunctionId(it) => ValueNs::FunctionId(it), @@ -1238,10 +1269,10 @@ fn to_value_ns(per_ns: PerNs, def_map: &DefMap) -> Option { | ModuleDefId::MacroId(_) | ModuleDefId::ModuleId(_) => return None, }; - Some(res) + Some((res, vis)) } -fn to_type_ns(per_ns: PerNs) -> Option<(TypeNs, Option)> { +fn to_type_ns(per_ns: PerNs) -> Option<(TypeNs, Option, Visibility)> { let def = per_ns.take_types_full()?; let res = match def.def { ModuleDefId::AdtId(it) => TypeNs::AdtId(it), @@ -1259,7 +1290,7 @@ fn to_type_ns(per_ns: PerNs) -> Option<(TypeNs, Option)> { | ModuleDefId::MacroId(_) | ModuleDefId::StaticId(_) => return None, }; - Some((res, def.import)) + Some((res, def.import, def.vis)) } #[derive(Default)] diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lower/path.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lower/path.rs index 77037c5b12d13..554a191d9d0dd 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lower/path.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lower/path.rs @@ -334,7 +334,7 @@ impl<'a, 'b, 'db> PathLoweringContext<'a, 'b, 'db> { #[tracing::instrument(skip(self), ret)] pub(crate) fn resolve_path_in_type_ns(&mut self) -> Option<(TypeNs, Option)> { - let (resolution, remaining_index, _, prefix_info) = + let (resolution, remaining_index, _, prefix_info, _) = self.ctx.resolver.resolve_path_in_type_ns_with_prefix_info(self.ctx.db, self.path)?; let segments = self.segments; @@ -385,7 +385,7 @@ impl<'a, 'b, 'db> PathLoweringContext<'a, 'b, 'db> { &mut self, hygiene_id: HygieneId, ) -> Option { - let (res, prefix_info) = self.ctx.resolver.resolve_path_in_value_ns_with_prefix_info( + let (res, prefix_info, _) = self.ctx.resolver.resolve_path_in_value_ns_with_prefix_info( self.ctx.db, self.path, hygiene_id, diff --git a/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs b/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs index fb27f9dec45ac..e80567641baf3 100644 --- a/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs +++ b/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs @@ -24,8 +24,9 @@ use hir_def::{ hir::{BindingId, Expr, ExprId, ExprOrPatId, Pat, PatId, generics::GenericParams}, lang_item::LangItems, nameres::MacroSubNs, - resolver::{Resolver, TypeNs, ValueNs, resolver_for_scope}, + resolver::{ResolveValueResult, Resolver, TypeNs, ValueNs, resolver_for_scope}, type_ref::{Mutability, TypeRefId}, + visibility::Visibility, }; use hir_expand::{ HirFileId, InFile, @@ -1060,7 +1061,7 @@ impl<'db> SourceAnalyzer<'db> { }; let store_owner = self.resolver.expression_store_owner(); - let res = resolve_hir_value_path( + let (res, _) = resolve_hir_value_path( db, &self.resolver, store_owner, @@ -1643,7 +1644,8 @@ impl<'db> SourceAnalyzer<'db> { Some(name.clone()), )), hygiene, - ), + ) + .map(|(it, _)| it), ) }) } @@ -1683,7 +1685,8 @@ impl<'db> SourceAnalyzer<'db> { Some(name.clone()), )), hygiene, - ), + ) + .map(|(it, _)| it), ) })) } @@ -1865,7 +1868,7 @@ pub(crate) fn resolve_hir_path_as_attr_macro( ) -> Option { resolver .resolve_path_as_macro(db, path.mod_path()?, Some(MacroSubNs::Attr)) - .map(|(it, _)| it) + .map(|it| it.def) .map(Into::into) } @@ -1880,7 +1883,7 @@ fn resolve_hir_path_<'db>( resolve_per_ns: bool, ) -> PathResolutionPerNs<'db> { let types = || { - let (ty, unresolved) = match path.type_anchor() { + let (ty, unresolved, ty_is_visible) = match path.type_anchor() { Some(type_ref) => resolver.generic_def().and_then(|def| { let generics = OnceCell::new(); let (_, res) = TyLoweringContext::new( @@ -1894,19 +1897,20 @@ fn resolve_hir_path_<'db>( LifetimeLoweringMode::LateParam, ) .lower_ty_ext(type_ref); - res.map(|ty_ns| (ty_ns, path.segments().first())) + res.map(|ty_ns| (ty_ns, path.segments().first(), Visibility::Public)) }), None => { - let (ty, remaining_idx, _) = resolver.resolve_path_in_type_ns(db, path)?; + let (ty, remaining_idx, _, _, vis) = + resolver.resolve_path_in_type_ns_with_prefix_info(db, path)?; match remaining_idx { Some(remaining_idx) => { if remaining_idx + 1 == path.segments().len() { - Some((ty, path.segments().last())) + Some((ty, path.segments().last(), vis)) } else { None } } - None => Some((ty, None)), + None => Some((ty, None, vis)), } } }?; @@ -1917,7 +1921,10 @@ fn resolve_hir_path_<'db>( && let Some(type_alias_id) = trait_id.trait_items(db).associated_type_by_name(unresolved.name) { - return Some(PathResolution::Def(ModuleDefId::from(type_alias_id).into())); + return Some(( + PathResolution::Def(ModuleDefId::from(type_alias_id).into()), + ty_is_visible, + )); } let res = match ty { @@ -1945,8 +1952,8 @@ fn resolve_hir_path_<'db>( }) .map(TypeAlias::from) .map(Into::into) - .map(PathResolution::Def), - None => Some(res), + .map(|def| (PathResolution::Def(def), ty_is_visible)), + None => Some((res, ty_is_visible)), } }; @@ -1956,41 +1963,79 @@ fn resolve_hir_path_<'db>( let items = || { resolver .resolve_module_path_in_items(db, path.mod_path()?) - .take_types() - .map(|it| PathResolution::Def(it.into())) + .take_types_full() + .map(|it| (PathResolution::Def(it.def.into()), it.vis)) }; let macros = || { resolver .resolve_path_as_macro(db, path.mod_path()?, None) - .map(|(def, _)| PathResolution::Def(ModuleDef::Macro(def.into()))) + .map(|res| (PathResolution::Def(ModuleDef::Macro(res.def.into())), res.vis)) }; - if resolve_per_ns { - PathResolutionPerNs { - type_ns: types().or_else(items), - value_ns: values(), - macro_ns: macros(), - } - } else { - let res = if prefer_value_ns { - values() - .map(|value_ns| PathResolutionPerNs::new(None, Some(value_ns), None)) - .unwrap_or_else(|| PathResolutionPerNs::new(types(), None, None)) - } else { - types() - .map(|type_ns| PathResolutionPerNs::new(Some(type_ns), None, None)) - .unwrap_or_else(|| PathResolutionPerNs::new(None, values(), None)) - }; + let mut types_ns: Option> = None; + let mut values_ns: Option> = None; + + let mut types_is_visible: Option = None; + let mut values_is_visible: Option = None; - if res.any().is_some() { - res - } else if let Some(type_ns) = items() { - PathResolutionPerNs::new(Some(type_ns), None, None) + if !resolve_per_ns { + if prefer_value_ns { + values_ns = Some(values().inspect(|(_, vis)| { + values_is_visible = Some(resolver.is_visible(db, *vis)); + })); + + if let Some(Some((res, _))) = values_ns + && values_is_visible.unwrap_or_default() + { + return PathResolutionPerNs::new(None, Some(res), None); + } } else { - PathResolutionPerNs::new(None, None, macros()) + types_ns = Some(types().or_else(items).inspect(|(_, vis)| { + types_is_visible = Some(resolver.is_visible(db, *vis)); + })); + + if let Some(Some((res, _))) = types_ns + && types_is_visible.unwrap_or_default() + { + return PathResolutionPerNs::new(Some(res), None, None); + } + } + } + + let mut macros_is_visible = false; + + let mut types = types_ns.unwrap_or_else(|| types().or_else(items)).map(|(res, vis)| { + types_is_visible = Some(types_is_visible.unwrap_or_else(|| resolver.is_visible(db, vis))); + res + }); + let mut values = values_ns.unwrap_or_else(values).map(|(res, vis)| { + values_is_visible = Some(values_is_visible.unwrap_or_else(|| resolver.is_visible(db, vis))); + res + }); + let mut macros = macros().map(|(res, vis)| { + macros_is_visible = resolver.is_visible(db, vis); + res + }); + + let types_is_visible = types_is_visible.unwrap_or_default(); + let values_is_visible = values_is_visible.unwrap_or_default(); + + // If there is a visible resolution and an invisible one, we only want to include the visible one. But if all are + // invisible, we want to include them all. + if types_is_visible || values_is_visible || macros_is_visible { + if !types_is_visible { + types = None; + } + if !values_is_visible { + values = None; + } + if !macros_is_visible { + macros = None; } } + + PathResolutionPerNs { type_ns: types, value_ns: values, macro_ns: macros } } fn resolve_hir_value_path<'db>( @@ -2000,23 +2045,26 @@ fn resolve_hir_value_path<'db>( infer_body: Option>, path: &Path, hygiene: HygieneId, -) -> Option> { - resolver.resolve_path_in_value_ns_fully(db, path, hygiene).and_then(|val| { - let res = match val { - ValueNs::LocalBinding(binding_id) => { - let var = Local { parent: store_owner?, parent_infer: infer_body?, binding_id }; - PathResolution::Local(var) - } - ValueNs::FunctionId(it) => PathResolution::Def(Function::from(it).into()), - ValueNs::ConstId(it) => PathResolution::Def(Const::from(it).into()), - ValueNs::StaticId(it) => PathResolution::Def(Static::from(it).into()), - ValueNs::StructId(it) => PathResolution::Def(Struct::from(it).into()), - ValueNs::EnumVariantId(it) => PathResolution::Def(EnumVariant::from(it).into()), - ValueNs::ImplSelf(impl_id) => PathResolution::SelfType(impl_id.into()), - ValueNs::GenericParam(id) => PathResolution::ConstParam(id.into()), - }; - Some(res) - }) +) -> Option<(PathResolution<'db>, Visibility)> { + resolver.resolve_path_in_value_ns_with_prefix_info(db, path, hygiene).and_then( + |(val, _, vis)| { + let ResolveValueResult::ValueNs(val) = val else { return None }; + let res = match val { + ValueNs::LocalBinding(binding_id) => { + let var = Local { parent: store_owner?, parent_infer: infer_body?, binding_id }; + PathResolution::Local(var) + } + ValueNs::FunctionId(it) => PathResolution::Def(Function::from(it).into()), + ValueNs::ConstId(it) => PathResolution::Def(Const::from(it).into()), + ValueNs::StaticId(it) => PathResolution::Def(Static::from(it).into()), + ValueNs::StructId(it) => PathResolution::Def(Struct::from(it).into()), + ValueNs::EnumVariantId(it) => PathResolution::Def(EnumVariant::from(it).into()), + ValueNs::ImplSelf(impl_id) => PathResolution::SelfType(impl_id.into()), + ValueNs::GenericParam(id) => PathResolution::ConstParam(id.into()), + }; + Some((res, vis)) + }, + ) } /// Resolves a path where we know it is a qualifier of another path. 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 135e750ca066c..3624099b13bc6 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/tests.rs @@ -354,7 +354,6 @@ fn check_with_config( handler(&mut acc, &ctx); }); let mut res = acc.finish(); - let assist = match assist_label { Some(label) => res.into_iter().find(|resolved| resolved.label == label), None if res.is_empty() => None, diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_module_macro_conflict.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_module_macro_conflict.html new file mode 100644 index 0000000000000..b61f574f0e768 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_module_macro_conflict.html @@ -0,0 +1,50 @@ + + +
use foo::bar;
+
+fn main() {
+    bar!()
+}
+
+
\ No newline at end of file diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/private_multi_namespace.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/private_multi_namespace.html new file mode 100644 index 0000000000000..06fc3f0772dbe --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/private_multi_namespace.html @@ -0,0 +1,46 @@ + + +
use foo::foo;
+
+
\ No newline at end of file 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 f4b103902499c..6cb323b46a521 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 @@ -1601,3 +1601,51 @@ async fn get_double_async(num: u32) -> u32 { false, ); } + +#[test] +fn private_multi_namespace() { + check_highlighting( + r#" +//- /bar.rs crate:bar deps:foo +use foo::foo; + +//- /foo.rs crate:foo +struct foo; + +#[macro_export] +macro_rules! foo { + () => {}; +} + "#, + expect_file!["./test_data/private_multi_namespace.html"], + false, + ); +} + +#[test] +fn mod_and_macro_name_conflict() { + check_highlighting( + r#" +//- /main.rs crate:main deps:foo +use foo::bar; + +fn main() { + bar!() +} + +//- /foo.rs crate:foo +mod bar { + fn random() {} +} + +#[macro_export] +macro_rules! bar { + () => { + println!("Hello"); + }; +} +"#, + expect_file!["./test_data/highlight_module_macro_conflict.html"], + false, + ); +} From 31086e2cc87b450a01209b2fbd42ff71f7409617 Mon Sep 17 00:00:00 2001 From: Wilfred Hughes Date: Mon, 22 Jun 2026 13:43:54 +0100 Subject: [PATCH 14/63] fix: Only parse stdout in discover protocol Previously we read both stdout and stderr in the discover protocol. Depending on the tool generating rust-project JSON, this meant that a single stderr log message could break discovery. Instead, only look for JSON from the discover command's stdout, and forward stderr to the rust-analyzer logs. Update both the implementation and the discover protocol docs to reflect this behaviour. AI disclosure: Code partly written by GPT-5.5. --- .../rust-analyzer/crates/rust-analyzer/src/config.rs | 9 ++++++--- .../rust-analyzer/crates/rust-analyzer/src/discover.rs | 5 +++-- .../crates/rust-analyzer/src/test_runner.rs | 7 +++++-- .../docs/book/src/configuration_generated.md | 9 ++++++--- .../docs/book/src/non_cargo_based_projects.md | 4 ++-- src/tools/rust-analyzer/editors/code/package.json | 2 +- 6 files changed, 23 insertions(+), 13 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 fcb34b743adbd..8590a99f286ae 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs @@ -562,9 +562,9 @@ config_data! { /// /// **Warning**: This format is provisional and subject to change. /// - /// The discover command should output JSON objects, one per - /// line (JSONL format). These objects should correspond to - /// this Rust data type: + /// The discover command should output JSON objects to stdout, + /// one per line (JSONL format). These objects should correspond + /// to this Rust data type: /// /// ```norun /// #[derive(Debug, Clone, Deserialize, Serialize)] @@ -604,6 +604,9 @@ config_data! { /// Only the finished event is required, but the other /// variants are encouraged to give users more feedback about /// progress or errors. + /// + /// Stderr is not parsed as JSONL. It is treated as command log + /// output and forwarded to rust-analyzer's own logs. workspace_discoverConfig: Option = None, } } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/discover.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/discover.rs index 459a7993201b8..04d0cedb3eca2 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/discover.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/discover.rs @@ -137,8 +137,9 @@ impl JsonLinesParser for DiscoverProjectParser { None } - fn from_stderr_line(&self, line: &str, error: &mut String) -> Option { - self.from_line(line, error) + fn from_stderr_line(&self, line: &str, _error: &mut String) -> Option { + tracing::info!(%line, "discover command stderr"); + None } } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/test_runner.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/test_runner.rs index 4f5c00192dcd5..c6f8a7c799c21 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/test_runner.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/test_runner.rs @@ -72,8 +72,11 @@ impl JsonLinesParser for CargoTestOutputParser { }) } - fn from_stderr_line(&self, line: &str, error: &mut String) -> Option { - self.from_line(line, error) + fn from_stderr_line(&self, line: &str, _error: &mut String) -> Option { + Some(CargoTestMessage { + target: self.target.clone(), + output: CargoTestOutput::Custom { text: line.to_owned() }, + }) } fn from_eof(&self) -> Option { 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 fd377616d9566..4df17d77edfbf 100644 --- a/src/tools/rust-analyzer/docs/book/src/configuration_generated.md +++ b/src/tools/rust-analyzer/docs/book/src/configuration_generated.md @@ -1769,9 +1769,9 @@ will likely be useful: **Warning**: This format is provisional and subject to change. -The discover command should output JSON objects, one per -line (JSONL format). These objects should correspond to -this Rust data type: +The discover command should output JSON objects to stdout, +one per line (JSONL format). These objects should correspond +to this Rust data type: ```norun #[derive(Debug, Clone, Deserialize, Serialize)] @@ -1812,6 +1812,9 @@ Only the finished event is required, but the other variants are encouraged to give users more feedback about progress or errors. +Stderr is not parsed as JSONL. It is treated as command log +output and forwarded to rust-analyzer's own logs. + ## rust-analyzer.workspace.symbol.search.excludeImports {#workspace.symbol.search.excludeImports} diff --git a/src/tools/rust-analyzer/docs/book/src/non_cargo_based_projects.md b/src/tools/rust-analyzer/docs/book/src/non_cargo_based_projects.md index 9cc3292444980..75e7fc900f193 100644 --- a/src/tools/rust-analyzer/docs/book/src/non_cargo_based_projects.md +++ b/src/tools/rust-analyzer/docs/book/src/non_cargo_based_projects.md @@ -237,8 +237,8 @@ There are four ways to feed `rust-project.json` to rust-analyzer: - Use [`"rust-analyzer.workspace.discoverConfig": … }`](./configuration.md#workspace.discoverConfig) to specify a workspace discovery command to generate project descriptions - on-the-fly. Please note that the command output is message-oriented and must - output JSONL [as described in the configuration docs](./configuration.md#workspace.discoverConfig). + on-the-fly. Please note that the command's stdout is message-oriented and + must output JSONL [as described in the configuration docs](./configuration.md#workspace.discoverConfig). - Place `rust-project.json` file at the root of the project, and rust-analyzer will discover it. diff --git a/src/tools/rust-analyzer/editors/code/package.json b/src/tools/rust-analyzer/editors/code/package.json index 61bc4cb29dfb7..d152cfb5861ea 100644 --- a/src/tools/rust-analyzer/editors/code/package.json +++ b/src/tools/rust-analyzer/editors/code/package.json @@ -3258,7 +3258,7 @@ "title": "Workspace", "properties": { "rust-analyzer.workspace.discoverConfig": { - "markdownDescription": "Configure a command that rust-analyzer can invoke to\nobtain configuration.\n\nThis is an alternative to manually generating\n`rust-project.json`: it enables rust-analyzer to generate\nrust-project.json on the fly, and regenerate it when\nswitching or modifying projects.\n\nThis is an object with three fields:\n\n* `command`: the shell command to invoke\n\n* `filesToWatch`: which build system-specific files should\nbe watched to trigger regenerating the configuration\n\n* `progressLabel`: the name of the command, used in\nprogress indicators in the IDE\n\nHere's an example of a valid configuration:\n\n```json\n\"rust-analyzer.workspace.discoverConfig\": {\n \"command\": [\n \"rust-project\",\n \"develop-json\",\n \"{arg}\"\n ],\n \"progressLabel\": \"buck2/rust-project\",\n \"filesToWatch\": [\n \"BUCK\"\n ]\n}\n```\n\n## Argument Substitutions\n\nIf `command` includes the argument `{arg}`, that argument will be substituted\nwith the JSON-serialized form of the following enum:\n\n```norun\n#[derive(PartialEq, Clone, Debug, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub enum DiscoverArgument {\n Path(AbsPathBuf),\n Buildfile(AbsPathBuf),\n}\n```\n\nrust-analyzer will use the path invocation to find and\ngenerate a `rust-project.json` and therefore a\nworkspace. Example:\n\n\n```norun\nrust-project develop-json '{ \"path\": \"myproject/src/main.rs\" }'\n```\n\nrust-analyzer will use build file invocations to update an\nexisting workspace. Example:\n\nOr with a build file and the configuration above:\n\n```norun\nrust-project develop-json '{ \"buildfile\": \"myproject/BUCK\" }'\n```\n\nAs a reference for implementors, buck2's `rust-project`\nwill likely be useful:\n.\n\n## Discover Command Output\n\n**Warning**: This format is provisional and subject to change.\n\nThe discover command should output JSON objects, one per\nline (JSONL format). These objects should correspond to\nthis Rust data type:\n\n```norun\n#[derive(Debug, Clone, Deserialize, Serialize)]\n#[serde(tag = \"kind\")]\n#[serde(rename_all = \"snake_case\")]\nenum DiscoverProjectData {\n Finished { buildfile: Utf8PathBuf, project: ProjectJsonData },\n Error { error: String, source: Option },\n Progress { message: String },\n}\n```\n\nFor example, a progress event:\n\n```json\n{\"kind\":\"progress\",\"message\":\"generating rust-project.json\"}\n```\n\nA finished event can look like this (expanded and\ncommented for readability):\n\n```json\n{\n // the internally-tagged representation of the enum.\n \"kind\": \"finished\",\n // the file used by a non-Cargo build system to define\n // a package or target.\n \"buildfile\": \"rust-analyzer/BUCK\",\n // the contents of a rust-project.json, elided for brevity\n \"project\": {\n \"sysroot\": \"foo\",\n \"crates\": []\n }\n}\n```\n\nOnly the finished event is required, but the other\nvariants are encouraged to give users more feedback about\nprogress or errors.", + "markdownDescription": "Configure a command that rust-analyzer can invoke to\nobtain configuration.\n\nThis is an alternative to manually generating\n`rust-project.json`: it enables rust-analyzer to generate\nrust-project.json on the fly, and regenerate it when\nswitching or modifying projects.\n\nThis is an object with three fields:\n\n* `command`: the shell command to invoke\n\n* `filesToWatch`: which build system-specific files should\nbe watched to trigger regenerating the configuration\n\n* `progressLabel`: the name of the command, used in\nprogress indicators in the IDE\n\nHere's an example of a valid configuration:\n\n```json\n\"rust-analyzer.workspace.discoverConfig\": {\n \"command\": [\n \"rust-project\",\n \"develop-json\",\n \"{arg}\"\n ],\n \"progressLabel\": \"buck2/rust-project\",\n \"filesToWatch\": [\n \"BUCK\"\n ]\n}\n```\n\n## Argument Substitutions\n\nIf `command` includes the argument `{arg}`, that argument will be substituted\nwith the JSON-serialized form of the following enum:\n\n```norun\n#[derive(PartialEq, Clone, Debug, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub enum DiscoverArgument {\n Path(AbsPathBuf),\n Buildfile(AbsPathBuf),\n}\n```\n\nrust-analyzer will use the path invocation to find and\ngenerate a `rust-project.json` and therefore a\nworkspace. Example:\n\n\n```norun\nrust-project develop-json '{ \"path\": \"myproject/src/main.rs\" }'\n```\n\nrust-analyzer will use build file invocations to update an\nexisting workspace. Example:\n\nOr with a build file and the configuration above:\n\n```norun\nrust-project develop-json '{ \"buildfile\": \"myproject/BUCK\" }'\n```\n\nAs a reference for implementors, buck2's `rust-project`\nwill likely be useful:\n.\n\n## Discover Command Output\n\n**Warning**: This format is provisional and subject to change.\n\nThe discover command should output JSON objects to stdout,\none per line (JSONL format). These objects should correspond\nto this Rust data type:\n\n```norun\n#[derive(Debug, Clone, Deserialize, Serialize)]\n#[serde(tag = \"kind\")]\n#[serde(rename_all = \"snake_case\")]\nenum DiscoverProjectData {\n Finished { buildfile: Utf8PathBuf, project: ProjectJsonData },\n Error { error: String, source: Option },\n Progress { message: String },\n}\n```\n\nFor example, a progress event:\n\n```json\n{\"kind\":\"progress\",\"message\":\"generating rust-project.json\"}\n```\n\nA finished event can look like this (expanded and\ncommented for readability):\n\n```json\n{\n // the internally-tagged representation of the enum.\n \"kind\": \"finished\",\n // the file used by a non-Cargo build system to define\n // a package or target.\n \"buildfile\": \"rust-analyzer/BUCK\",\n // the contents of a rust-project.json, elided for brevity\n \"project\": {\n \"sysroot\": \"foo\",\n \"crates\": []\n }\n}\n```\n\nOnly the finished event is required, but the other\nvariants are encouraged to give users more feedback about\nprogress or errors.\n\nStderr is not parsed as JSONL. It is treated as command log\noutput and forwarded to rust-analyzer's own logs.", "default": null, "anyOf": [ { From de3c5b647b55472b8aba8817f83cea826e41b344 Mon Sep 17 00:00:00 2001 From: Wilfred Hughes Date: Mon, 27 Jul 2026 15:44:42 +0100 Subject: [PATCH 15/63] internal: Spelling and grammar fixes I noticed a few "its" versus "it's" grammatical issues, so I've done a pass at fixing obvious grammar issues. AI disclosure: I fixed the first few manually, then asked GPT-5.5 to look for additional cases and kept all the obviously reasonable fixes. --- src/tools/rust-analyzer/crates/hir-def/src/attrs/docs.rs | 4 ++-- .../crates/hir-def/src/nameres/path_resolution.rs | 8 ++++---- .../rust-analyzer/crates/hir-ty/src/infer/place_op.rs | 2 +- src/tools/rust-analyzer/crates/hir-ty/src/lib.rs | 2 +- .../crates/hir-ty/src/next_solver/inspect.rs | 2 +- src/tools/rust-analyzer/crates/hir/src/display.rs | 2 +- src/tools/rust-analyzer/crates/hir/src/semantics.rs | 4 ++-- .../crates/hir/src/semantics/source_to_def.rs | 6 +++--- .../ide-assists/src/handlers/replace_if_let_with_match.rs | 2 +- .../ide-completion/src/completions/attribute/derive.rs | 2 +- .../crates/ide-completion/src/completions/type.rs | 2 +- .../crates/ide-completion/src/context/analysis.rs | 2 +- src/tools/rust-analyzer/crates/ide-completion/src/item.rs | 2 +- .../rust-analyzer/crates/ide-db/src/source_change.rs | 4 ++-- .../crates/ide-db/src/syntax_helpers/tree_diff.rs | 2 +- src/tools/rust-analyzer/crates/ide-ssr/src/search.rs | 2 +- src/tools/rust-analyzer/crates/ide-ssr/src/tests.rs | 2 +- src/tools/rust-analyzer/crates/ide/src/typing.rs | 2 +- .../rust-analyzer/crates/mbe/src/expander/matcher.rs | 2 +- src/tools/rust-analyzer/crates/parser/src/output.rs | 2 +- .../crates/proc-macro-api/src/legacy_protocol/msg/flat.rs | 2 +- .../rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs | 4 ++-- .../rust-analyzer/crates/rust-analyzer/src/main_loop.rs | 2 +- src/tools/rust-analyzer/crates/span/src/ast_id.rs | 4 ++-- src/tools/rust-analyzer/crates/vfs/src/lib.rs | 6 +++--- src/tools/rust-analyzer/crates/vfs/src/loader.rs | 2 +- src/tools/rust-analyzer/crates/vfs/src/path_interner.rs | 4 ++-- 27 files changed, 40 insertions(+), 40 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 bdd8ea84a0910..55cb6f8f81aa7 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 @@ -44,13 +44,13 @@ pub struct Docs { 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 + /// If the item is an outlined module (`mod foo;`), `docs_source_map` stores 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. + /// The size of 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, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/path_resolution.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/path_resolution.rs index fde1db4734a78..150b4eeb60f2b 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/path_resolution.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/path_resolution.rs @@ -1,11 +1,11 @@ -//! This modules implements a function to resolve a path `foo::bar::baz` to a -//! def, which is used within the name resolution. +//! This module implements a function to resolve a path `foo::bar::baz` to a +//! def, which is used within name resolution. //! //! When name resolution is finished, the result of resolving a path is either -//! `Some(def)` or `None`. However, when we are in process of resolving imports +//! `Some(def)` or `None`. However, when we are in the process of resolving imports //! or macros, there's a third possibility: //! -//! I can't resolve this path right now, but I might be resolve this path +//! I can't resolve this path right now, but I might be able to resolve this path //! later, when more macros are expanded. //! //! `ReachedFixedPoint` signals about this. diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/place_op.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/place_op.rs index b226e5ca85de0..c2e39709e8949 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/place_op.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/place_op.rs @@ -235,7 +235,7 @@ impl<'db> InferenceContext<'db> { // We have to replace the operator with the mutable variant for the // program to compile, so we don't really have a choice here and want - // to just try using `DerefMut` even if its not in the item bounds + // to just try using `DerefMut` even if it's not in the item bounds // of the opaque. let treat_opaques = TreatNotYetDefinedOpaques::AsInfer; table.lookup_method_for_operator( diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs index c631c87de5160..0dd558828fd7f 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs @@ -216,7 +216,7 @@ impl<'db> MemoryMap<'db> { } } -/// Return an index of a parameter in the generic type parameter list by it's id. +/// Returns the index of a parameter in the generic type parameter list by its id. pub fn type_or_const_param_idx(db: &dyn HirDatabase, id: TypeOrConstParamId) -> u32 { generics::generics(db, id.parent).type_or_const_param_idx(id) } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/inspect.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/inspect.rs index 7e2dfb7112d3b..f251dcfdcfb6d 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/inspect.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/inspect.rs @@ -131,7 +131,7 @@ impl<'a, 'db> InspectCandidate<'a, 'db> { /// Certainty passed into `evaluate_added_goals_and_make_canonical_response`. /// /// If this certainty is `Yes`, then we must be confident that the candidate - /// must hold iff it's nested goals hold. This is not true if the certainty is + /// must hold iff its nested goals hold. This is not true if the certainty is /// `Maybe(..)`, which suggests we forced ambiguity instead. /// /// This is *not* the certainty of the candidate's full nested evaluation, which diff --git a/src/tools/rust-analyzer/crates/hir/src/display.rs b/src/tools/rust-analyzer/crates/hir/src/display.rs index dc35a5c57bec4..61eda80fb4877 100644 --- a/src/tools/rust-analyzer/crates/hir/src/display.rs +++ b/src/tools/rust-analyzer/crates/hir/src/display.rs @@ -357,7 +357,7 @@ impl<'db> HirDisplay<'db> for Adt { impl<'db> HirDisplay<'db> for Struct { fn hir_fmt(&self, f: &mut HirFormatter<'_, 'db>) -> Result { let module_id = self.module(f.db).id; - // FIXME: Render repr if its set explicitly? + // FIXME: Render repr if it's set explicitly? write_visibility(module_id, self.visibility(f.db), f)?; f.write_str("struct ")?; write!(f, "{}", self.name(f.db).display(f.db, f.edition()))?; diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs index fee6ae3d49527..f298e25489a59 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs @@ -1563,7 +1563,7 @@ impl<'db> SemanticsImpl<'db> { } /// Attempts to map the node out of macro expanded files. - /// This only work for attribute expansions, as other ones do not have nodes as input. + /// This only works for attribute expansions, as other ones do not have nodes as input. pub fn original_ast_node(&self, node: N) -> Option { self.wrap_node_infile(node).original_ast_node_rooted(self.db).map( |InRealFile { file_id, value }| { @@ -1574,7 +1574,7 @@ impl<'db> SemanticsImpl<'db> { } /// Attempts to map the node out of macro expanded files. - /// This only work for attribute expansions, as other ones do not have nodes as input. + /// This only works for attribute expansions, as other ones do not have nodes as input. pub fn original_syntax_node_rooted(&self, node: &SyntaxNode) -> Option { let InFile { file_id, .. } = self.find_file(node); InFile::new(file_id, node).original_syntax_node_rooted(self.db).map( 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 3b39a489141e1..81005f48ddf83 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 @@ -10,7 +10,7 @@ //! This problem is a part of more-or-less every IDE feature implemented. Every //! IDE functionality (like goto to definition), conceptually starts with a //! specific cursor position in a file. Starting with this text offset, we first -//! figure out what syntactic construct are we at: is this a pattern, an +//! figure out what syntactic construct we are at: is this a pattern, an //! expression, an item definition. //! //! Knowing only the syntax gives us relatively little info. For example, @@ -32,11 +32,11 @@ //! Specifically, the algorithm goes like this: //! //! 1. Find the syntactic container for the syntax. For example, field's -//! container is the struct, and structs container is a module. +//! container is the struct, and the struct's container is a module. //! 2. Recursively get the def corresponding to container. //! 3. Ask the container def for all child defs. These child defs contain //! the answer and answer's siblings. -//! 4. For each child def, ask for it's source. +//! 4. For each child def, ask for its source. //! 5. The child def whose source is the syntax node we've started with //! is the answer. //! 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 9a61163e87e71..50d6f6d62cb3e 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 @@ -102,7 +102,7 @@ pub(crate) fn replace_if_let_with_match( if !pat_seen && cond_bodies.len() != 1 { // Don't offer turning an if (chain) without patterns into a match, - // unless its a simple `if cond { .. } (else { .. })` + // unless it's a simple `if cond { .. } (else { .. })` return None; } diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/derive.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/derive.rs index 9e7dabbd01046..356c7e0087136 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/derive.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/derive.rs @@ -107,7 +107,7 @@ struct DeriveDependencies { } /// Standard Rust derives that have dependencies -/// (the dependencies are needed so that the main derive don't break the compilation when added) +/// (the dependencies are needed so that the main derive doesn't break the compilation when added) const DEFAULT_DERIVE_DEPENDENCIES: &[DeriveDependencies] = &[ DeriveDependencies { label: "Copy", dependencies: &["Clone"] }, DeriveDependencies { label: "Eq", dependencies: &["PartialEq"] }, 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 c07c02e28538f..391152f438b61 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 @@ -26,7 +26,7 @@ pub(crate) fn complete_type_path<'db>( ScopeDef::ModuleDef(Function(_) | EnumVariant(_) | Static(_)) | ScopeDef::Local(_) => { false } - // unless its a constant in a generic arg list position + // unless it's a constant in a generic arg list position ScopeDef::ModuleDef(Const(_)) | ScopeDef::GenericParam(ConstParam(_)) => { location.complete_consts() } 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 b06f52c113459..7280fd1ad5751 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 @@ -1663,7 +1663,7 @@ fn classify_name_ref<'db>( let res = sema.resolve_path(&qualifier); // For understanding how and why super_chain_len is calculated the way it - // is check the documentation at it's definition + // is check the documentation at its definition let mut segment_count = 0; let super_count = iter::successors(Some(qualifier.clone()), |p| p.qualifier()) .take_while(|p| { 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 2ff726c6d03e3..675ffac040293 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/item.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/item.rs @@ -320,7 +320,7 @@ impl CompletionRelevance { } if let Some(trait_) = trait_ { - // lower rank trait methods unless its notable + // lower rank trait methods unless it's notable if !trait_.notable_trait { score -= 5; } 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 a38154420af1a..540b0ee99dd21 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 @@ -1,5 +1,5 @@ -//! This modules defines type to represent changes to the source code, that flow -//! from the server to the client. +//! This module defines types that represent changes to source code flowing from +//! the server to the client. //! //! It can be viewed as a dual for [`Change`][vfs::Change]. diff --git a/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/tree_diff.rs b/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/tree_diff.rs index 7163c08e1e317..af893f1ea5f38 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/tree_diff.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/syntax_helpers/tree_diff.rs @@ -115,7 +115,7 @@ pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> TreeDiff { } (Some(ref lhs_ele), Some(ref rhs_ele)) if syntax_element_eq(lhs_ele, rhs_ele) => {} (Some(lhs_ele), Some(rhs_ele)) => { - // nodes differ, look for lhs_ele in rhs, if its found we can mark everything up + // nodes differ, look for lhs_ele in rhs, if it's found we can mark everything up // until that element as insertions. This is important to keep the diff minimal // in regards to insertions that have been actually done, this is important for // use insertions as we do not want to replace the entire module node. diff --git a/src/tools/rust-analyzer/crates/ide-ssr/src/search.rs b/src/tools/rust-analyzer/crates/ide-ssr/src/search.rs index c6a1d69672119..f76912b84085a 100644 --- a/src/tools/rust-analyzer/crates/ide-ssr/src/search.rs +++ b/src/tools/rust-analyzer/crates/ide-ssr/src/search.rs @@ -127,7 +127,7 @@ impl<'db> MatchFinder<'db> { usage_cache.find(&definition).unwrap() } - /// Returns the scope within which we want to search. We don't want un unrestricted search + /// Returns the scope within which we want to search. We don't want an unrestricted search /// scope, since we don't want to find references in external dependencies. fn search_scope(&self) -> SearchScope { // FIXME: We should ideally have a test that checks that we edit local roots and not library diff --git a/src/tools/rust-analyzer/crates/ide-ssr/src/tests.rs b/src/tools/rust-analyzer/crates/ide-ssr/src/tests.rs index b3d09cac42cf5..d8c15cabb2fb8 100644 --- a/src/tools/rust-analyzer/crates/ide-ssr/src/tests.rs +++ b/src/tools/rust-analyzer/crates/ide-ssr/src/tests.rs @@ -1070,7 +1070,7 @@ fn match_failure_reasons() { #[test] fn overlapping_possible_matches() { // There are three possible matches here, however the middle one, `foo(foo(foo(42)))` shouldn't - // match because it overlaps with the outer match. The inner match is permitted since it's is + // match because it overlaps with the outer match. The inner match is permitted since it is // contained entirely within the placeholder of the outer match. assert_matches( "foo(foo($a))", diff --git a/src/tools/rust-analyzer/crates/ide/src/typing.rs b/src/tools/rust-analyzer/crates/ide/src/typing.rs index b06079d8acd13..79919dac5b195 100644 --- a/src/tools/rust-analyzer/crates/ide/src/typing.rs +++ b/src/tools/rust-analyzer/crates/ide/src/typing.rs @@ -365,7 +365,7 @@ fn on_left_angle_typed( ) -> Option { let file_text = reparsed.syntax().text(); - // Find the next non-whitespace char in the line, check if its a `>` + // Find the next non-whitespace char in the line, check if it's a `>` let mut next_offset = offset; while file_text.char_at(next_offset) == Some(' ') { next_offset += TextSize::of(' ') diff --git a/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs b/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs index fe01fb1f10637..c95e5a3ef8341 100644 --- a/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs +++ b/src/tools/rust-analyzer/crates/mbe/src/expander/matcher.rs @@ -347,7 +347,7 @@ struct MatchState<'t> { /// Process the matcher positions of `cur_items` until it is empty. In the process, this will /// produce more items in `next_items`, `eof_items`, and `bb_items`. /// -/// For more info about the how this happens, see the module-level doc comments and the inline +/// For more info about how this happens, see the module-level doc comments and the inline /// comments of this function. /// /// # Parameters diff --git a/src/tools/rust-analyzer/crates/parser/src/output.rs b/src/tools/rust-analyzer/crates/parser/src/output.rs index ce64db8adae90..4f4728a8c5a58 100644 --- a/src/tools/rust-analyzer/crates/parser/src/output.rs +++ b/src/tools/rust-analyzer/crates/parser/src/output.rs @@ -14,7 +14,7 @@ use crate::SyntaxKind; #[derive(Default)] pub struct Output { /// 32-bit encoding of events. If LSB is zero, then that's an index into the - /// error vector. Otherwise, it's one of the thee other variants, with data encoded as + /// error vector. Otherwise, it's one of the three other variants, with data encoded as /// /// ```text /// |16 bit kind|8 bit n_input_tokens|4 bit tag|4 bit leftover| diff --git a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/msg/flat.rs b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/msg/flat.rs index 3015bd0c0eccc..ae03be9aa7a26 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/msg/flat.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-api/src/legacy_protocol/msg/flat.rs @@ -31,7 +31,7 @@ //! ``` //! //! We probably should replace most of the code here with bincode someday, but, -//! as we don't have bincode in Cargo.toml yet, lets stick with serde_json for +//! as we don't have bincode in Cargo.toml yet, let's stick with serde_json for //! the time being. #[cfg(feature = "in-rust-tree")] diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs index 7b00aebbfc4a5..ad92ebea7adb1 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs @@ -48,8 +48,8 @@ impl flags::Ssr { impl flags::Search { /// Searches for `patterns`, printing debug information for any nodes whose text exactly matches - /// `debug_snippet`. This is intended for debugging and probably isn't in it's current form useful - /// for much else. + /// `debug_snippet`. This is intended for debugging and probably isn't useful in its current + /// form for much else. pub fn run(self) -> anyhow::Result<()> { use ide_db::base_db::SourceDatabase; let cargo_config = 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 56490061a74f6..bd5c5ec87d968 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 @@ -998,7 +998,7 @@ impl GlobalState { let path = VfsPath::from(path); // If the file is in mem docs, it's managed by the client via - // notifications so only set it if its not in there. Library files are + // notifications so only set it if it's not in there. Library files are // exempt from that authority as they are considered immutable, for // them disk is always the source of truth. let is_library = self.source_root_config.path_is_library(&path); diff --git a/src/tools/rust-analyzer/crates/span/src/ast_id.rs b/src/tools/rust-analyzer/crates/span/src/ast_id.rs index f6500a9b4dbea..83a6748c01eaf 100644 --- a/src/tools/rust-analyzer/crates/span/src/ast_id.rs +++ b/src/tools/rust-analyzer/crates/span/src/ast_id.rs @@ -1,8 +1,8 @@ //! `AstIdMap` allows to create stable IDs for "large" syntax nodes like items //! and macro calls. //! -//! Specifically, it enumerates all items in a file and uses position of a an -//! item as an ID. That way, id's don't change unless the set of items itself +//! Specifically, it enumerates all items in a file and uses the position of an +//! item as an ID. That way, IDs don't change unless the set of items itself //! changes. //! //! These IDs are tricky. If one of them invalidates, its interned ID invalidates, diff --git a/src/tools/rust-analyzer/crates/vfs/src/lib.rs b/src/tools/rust-analyzer/crates/vfs/src/lib.rs index d48b984407e65..8d2c65f79163a 100644 --- a/src/tools/rust-analyzer/crates/vfs/src/lib.rs +++ b/src/tools/rust-analyzer/crates/vfs/src/lib.rs @@ -211,7 +211,7 @@ impl Vfs { /// /// Returns `true` if the file was modified, and saves the [change](ChangedFile). /// - /// If the path does not currently exists in the `Vfs`, allocates a new + /// If the path does not currently exist in the `Vfs`, allocates a new /// [`FileId`] for it. pub fn set_file_contents(&mut self, path: VfsPath, contents: Option>) -> bool { let _p = span!(Level::INFO, "Vfs::set_file_contents").entered(); @@ -280,7 +280,7 @@ impl Vfs { true } - /// Drain and returns all the changes in the `Vfs`. + /// Drains and returns all the changes in the `Vfs`. pub fn take_changes(&mut self) -> IndexMap> { mem::take(&mut self.changes) } @@ -292,7 +292,7 @@ impl Vfs { /// Returns the id associated with `path` /// - /// - If `path` does not exists in the `Vfs`, allocate a new id for it, associated with a + /// - If `path` does not exist in the `Vfs`, allocates a new id for it, associated with a /// deleted file; /// - Else, returns `path`'s id. /// diff --git a/src/tools/rust-analyzer/crates/vfs/src/loader.rs b/src/tools/rust-analyzer/crates/vfs/src/loader.rs index c49e4c4322d43..97e46ae894294 100644 --- a/src/tools/rust-analyzer/crates/vfs/src/loader.rs +++ b/src/tools/rust-analyzer/crates/vfs/src/loader.rs @@ -60,7 +60,7 @@ pub enum Message { n_total: usize, /// The files that have been loaded successfully. n_done: LoadingProgress, - /// The dir being loaded, `None` if its for a file. + /// The dir being loaded, `None` if it's for a file. dir: Option, /// The [`Config`] version. config_version: u32, diff --git a/src/tools/rust-analyzer/crates/vfs/src/path_interner.rs b/src/tools/rust-analyzer/crates/vfs/src/path_interner.rs index 225bfc7218b44..fbd1a624e3ce8 100644 --- a/src/tools/rust-analyzer/crates/vfs/src/path_interner.rs +++ b/src/tools/rust-analyzer/crates/vfs/src/path_interner.rs @@ -17,7 +17,7 @@ pub(crate) struct PathInterner { impl PathInterner { /// Get the id corresponding to `path`. /// - /// If `path` does not exists in `self`, returns [`None`]. + /// If `path` does not exist in `self`, returns [`None`]. pub(crate) fn get(&self, path: &VfsPath) -> Option { self.map.get_index_of(path).map(|i| FileId(i as u32)) } @@ -36,7 +36,7 @@ impl PathInterner { /// /// # Panics /// - /// Panics if `id` does not exists in `self`. + /// Panics if `id` does not exist in `self`. pub(crate) fn lookup(&self, id: FileId) -> &VfsPath { self.map.get_index(id.0 as usize).unwrap() } From 9c13c1f728455d10ae5ae23d8cdfca94391db523 Mon Sep 17 00:00:00 2001 From: shulaoda <165626830+shulaoda@users.noreply.github.com> Date: Mon, 27 Jul 2026 23:03:38 +0800 Subject: [PATCH 16/63] fix: don't pick a discriminant type larger than typeck's --- src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs | 4 ++-- src/tools/rust-analyzer/crates/hir-ty/src/layout/tests.rs | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs b/src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs index 1f321af79493f..777d0803fc799 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs @@ -142,8 +142,8 @@ fn repr_discr( Integer::I8 }; - // If there are no negative values, we can use the unsigned fit. - Ok(if min >= 0 { + // `min` and `max` are the ends of a wrapping range, so their sign is not a usable test. + Ok(if unsigned_fit <= signed_fit { (cmp::max(unsigned_fit, at_least), false) } else { (cmp::max(signed_fit, at_least), true) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests.rs b/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests.rs index b9ee38c44fe41..b5db24e98bc70 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests.rs @@ -610,6 +610,13 @@ fn enums_with_discriminants() { A = 1, // This one is (perhaps surprisingly) zero sized. } } + size_and_align! { + #[allow(overflowing_literals, clippy::enum_clike_unportable_variant)] + enum Goal { + A = 0, + B = 0x8000_0000_0000_0001, // Wraps around to a negative discriminant. + } + } } #[test] From 1da028bb07e2dc99ec6e05e6caef4f918ac1bee3 Mon Sep 17 00:00:00 2001 From: AayushMainali-Github Date: Mon, 27 Jul 2026 18:54:46 +0000 Subject: [PATCH 17/63] fix: use char counts in progress bar --- .../rust-analyzer/src/cli/progress_report.rs | 105 +++++++++++++----- 1 file changed, 78 insertions(+), 27 deletions(-) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/progress_report.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/progress_report.rs index 028311388c561..aff2c0d6ff52a 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/progress_report.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/progress_report.rs @@ -65,31 +65,7 @@ impl<'a> ProgressReport<'a> { } fn update_text(&mut self, text: &str) { - // Get length of common portion - let mut common_prefix_length = 0; - let common_length = usize::min(self.text.len(), text.len()); - - while common_prefix_length < common_length - && text.chars().nth(common_prefix_length).unwrap() - == self.text.chars().nth(common_prefix_length).unwrap() - { - common_prefix_length += 1; - } - - // Backtrack to the first differing character - let mut output = String::new(); - output += &'\x08'.to_string().repeat(self.text.len() - common_prefix_length); - // Output new suffix, using chars() iter to ensure unicode compatibility - output.extend(text.chars().skip(common_prefix_length)); - - // If the new text is shorter than the old one: delete overlapping characters - if let Some(overlap_count) = self.text.len().checked_sub(text.len()) - && overlap_count > 0 - { - output += &" ".repeat(overlap_count); - output += &"\x08".repeat(overlap_count); - } - + let output = render_text_update(&self.text, text); let _ = io::stdout().write(output.as_bytes()); let _ = io::stdout().flush(); text.clone_into(&mut self.text); @@ -105,11 +81,86 @@ impl<'a> ProgressReport<'a> { } // Fill all last text to space and return the cursor - let spaces = " ".repeat(self.text.len()); - let backspaces = "\x08".repeat(self.text.len()); + let len = self.text.chars().count(); + let spaces = " ".repeat(len); + let backspaces = "\x08".repeat(len); print!("{backspaces}{spaces}{backspaces}"); let _ = io::stdout().flush(); self.text = String::new(); } } + +fn render_text_update(old: &str, new: &str) -> String { + let old_len = old.chars().count(); + let new_len = new.chars().count(); + + // Get length of common portion + let mut common_prefix_length = 0; + let common_length = usize::min(old_len, new_len); + + while common_prefix_length < common_length + && new.chars().nth(common_prefix_length).unwrap() + == old.chars().nth(common_prefix_length).unwrap() + { + common_prefix_length += 1; + } + + // Backtrack to the first differing character + let mut output = String::new(); + output += &'\x08'.to_string().repeat(old_len - common_prefix_length); + // Output new suffix, using chars() iter to ensure unicode compatibility + output.extend(new.chars().skip(common_prefix_length)); + + // If the new text is shorter than the old one: delete overlapping characters + if let Some(overlap_count) = old_len.checked_sub(new_len) + && overlap_count > 0 + { + output += &" ".repeat(overlap_count); + output += &"\x08".repeat(overlap_count); + } + + output +} + +#[cfg(test)] +mod tests { + use super::render_text_update; + + #[test] + fn ascii_prefix_reuse() { + let old = "1/7 14% processing: foo"; + let new = "1/7 28% processing: bar"; + let update = render_text_update(old, new); + + let common = "1/7 "; + let backspaces = old.chars().count() - common.chars().count(); + let expected = format!("{}{}", "\x08".repeat(backspaces), "28% processing: bar"); + assert_eq!(update, expected); + } + + #[test] + fn unicode_identifiers_do_not_panic() { + // Regression test for rust-lang/rust-analyzer#22844: previous code + // compared byte lengths with char indices, so `chars().nth(...).unwrap()` + // panicked on non-ASCII. + let old = "1/7 14% processing: f::消息"; + let new = "2/7 28% processing: f::消息内容"; + let update = render_text_update(old, new); + + let backspaces = old.chars().count(); + let expected = format!("{}{new}", "\x08".repeat(backspaces)); + assert_eq!(update, expected); + } + + #[test] + fn shorter_unicode_message_clears_overlap() { + let old = "processing: 消息内容"; + let new = "processing: 消息"; + let update = render_text_update(old, new); + + // Drop the last two chars, then blank/backspace the leftover width. + let expected = "\x08\x08 \x08\x08"; + assert_eq!(update, expected); + } +} From e86cdb59eae82e18b6e8d8151695809ede272ca4 Mon Sep 17 00:00:00 2001 From: shulaoda <165626830+shulaoda@users.noreply.github.com> Date: Tue, 28 Jul 2026 08:20:27 +0800 Subject: [PATCH 18/63] fix: don't panic on a qualified path whose trait is not a trait --- .../rust-analyzer/crates/hir-ty/src/lower/path.rs | 6 ++++++ .../crates/hir-ty/src/tests/regression.rs | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lower/path.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lower/path.rs index 554a191d9d0dd..c67c69520db10 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lower/path.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lower/path.rs @@ -1234,6 +1234,12 @@ pub(crate) fn substs_from_args_and_bindings<'db>( }; params.next(); substs.push(self_ty); + } else if has_self_arg { + // A qualified path `::Assoc` where `Trait` resolved to something without a + // `Self` parameter, e.g. a struct. `check_generic_args_len()` skips the self type + // unconditionally, so drop it here too instead of matching it against a real parameter. + // FIXME: Report a diagnostic here, rustc emits `E0404: expected trait, found struct`. + args.next(); } loop { 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 c580841244f1b..2836f977a9ac5 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 @@ -3016,3 +3016,15 @@ fn f(s: S) { s.m(); } "#, ); } + +#[test] +fn regression_22799() { + check_no_mismatches( + r#" +struct S; +fn f() { + ::S; +} + "#, + ); +} From e42a814b26084408f164c5a0aed7686a461c99cc Mon Sep 17 00:00:00 2001 From: Wilfred Hughes Date: Wed, 22 Jul 2026 18:22:37 +0100 Subject: [PATCH 19/63] fix: Failed to lookup MACRO_CALL@... in this Semantics due to include! SemanticsImpl::find_file assumes that its caches always contain the file that has the current SyntaxNode. For macros `foo!()` we only have two files to worry about: the macro call site and the macro definition site. Hoewver, for include!("foo.rs") we also need to consider the included file. Ensure that the file cache is consistently populated for include!() invocations macro expansion, and add a test. AI disclosure: GPT-5.5 used to minimise a repro from a real project and write the initial implementation. Comments and commit message are entirely mine. --- .../crates/hir/src/semantics/source_to_def.rs | 12 ++++++++++++ .../ide-assists/src/handlers/inline_macro.rs | 17 +++++++++++++++++ 2 files changed, 29 insertions(+) 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 81005f48ddf83..caa7b39885fec 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 @@ -165,9 +165,21 @@ impl<'db> SourceToDefCache<'db> { self.expansion_info_cache.entry(macro_file).or_insert_with(|| { let exp_info = macro_file.expansion_info(db); + // Ensure that the cache contains syntax nodes from expanded macros, + // whose root may be in another file. let InMacroFile { file_id, value } = exp_info.expanded(); Self::cache(&mut self.root_to_file_cache, value, file_id.into()); + // include!("foo.rs") invocations are awkward: in addition to the + // expansion site there's the included file (foo.rs), so we need to + // ensure that it exists in the cache too. + if macro_file.is_include_macro(db) { + let arg = exp_info.arg(); + if let Some(arg_node) = arg.value { + Self::cache(&mut self.root_to_file_cache, arg_node.tree_top(), arg.file_id); + } + } + exp_info }) } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_macro.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_macro.rs index 5a185637df4ef..d934fae9253f6 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_macro.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_macro.rs @@ -175,6 +175,23 @@ macro_rules! num { ); } + #[test] + fn inline_macro_in_included_file() { + // Regression test for climbing from the included file into an uncached includer root. + check_assist_not_applicable( + inline_macro, + r#" +//- minicore:include +//- /main.rs +include!("a.rs"); +//- /a.rs +fn foo() { + let x = 1$0; +} +"#, + ); + } + #[test] fn inline_macro_simple_not_applicable_broken_macro() { // FIXME: This is a bug. The macro should not expand, but it's From db60d94665b36d16a63ebbb48055a759646e53f0 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Tue, 28 Jul 2026 15:34:33 +0200 Subject: [PATCH 20/63] internal: update next-solver to 0.166 --- src/tools/rust-analyzer/Cargo.lock | 44 +++++++++--------- src/tools/rust-analyzer/Cargo.toml | 16 +++---- .../crates/hir-ty/src/method_resolution.rs | 45 ++++++++++++------- .../crates/hir-ty/src/next_solver/interner.rs | 37 +++++++++------ .../crates/hir-ty/src/next_solver/util.rs | 12 +++++ 5 files changed, 95 insertions(+), 59 deletions(-) diff --git a/src/tools/rust-analyzer/Cargo.lock b/src/tools/rust-analyzer/Cargo.lock index a096be2f2ad82..7a2e2d493b59a 100644 --- a/src/tools/rust-analyzer/Cargo.lock +++ b/src/tools/rust-analyzer/Cargo.lock @@ -2062,9 +2062,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "ra-ap-rustc_abi" -version = "0.165.0" +version = "0.166.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f25a779e21ca3bba6795193b16508c8ab159f96ee4b07349893fd272065b525" +checksum = "e2cf1b1ffe31b6226c00b40cddfda65002b7729f9f4ed2d547b5856cdab0011c" dependencies = [ "bitflags", "ra-ap-rustc_hashes", @@ -2074,33 +2074,33 @@ dependencies = [ [[package]] name = "ra-ap-rustc_ast_ir" -version = "0.165.0" +version = "0.166.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0218ca6c7b096466e85a497e6150c39be5b7bc36637fe62c1cd20370a9d9aac7" +checksum = "2ef42605e36e1305e815ccfc8830eb870f74d78534bca19a61629149536d8e98" [[package]] name = "ra-ap-rustc_hashes" -version = "0.165.0" +version = "0.166.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b410bacf1a7c8038f376fa6283003784d568ac012e35fc0aeefa9a5ab11a2e" +checksum = "b9f5542968215c17275920791b2fa13a43014287506ed0450777c79845102e86" dependencies = [ "rustc-stable-hash", ] [[package]] name = "ra-ap-rustc_index" -version = "0.165.0" +version = "0.166.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2271b55e4a5d0cc0cbe9bdf8056c07ac69e32919a48ce66722ed0526d62588c3" +checksum = "1d9e47b9ca7d92cfb0d6653503adbabd41938b84474317397a664326b208d6c6" dependencies = [ "ra-ap-rustc_index_macros", ] [[package]] name = "ra-ap-rustc_index_macros" -version = "0.165.0" +version = "0.166.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6a89e743fb881a1e13544e3395a5ad9ad9280d56384256a121066119abd7af2" +checksum = "4d744a7a2852a22f06210bcff9e4667ed0cacbfbe94894cc294044d25e876341" dependencies = [ "proc-macro2", "quote", @@ -2109,9 +2109,9 @@ dependencies = [ [[package]] name = "ra-ap-rustc_lexer" -version = "0.165.0" +version = "0.166.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6d7c9cc05e0e6b72a214a455a106d9b22b0494164d50a657b17bd319534c218" +checksum = "527c12b3731b7d0692498012810b85b2b8dfdb8b514321ed6afc434bd1c70191" dependencies = [ "memchr", "unicode-ident", @@ -2120,9 +2120,9 @@ dependencies = [ [[package]] name = "ra-ap-rustc_next_trait_solver" -version = "0.165.0" +version = "0.166.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb3017c2f0ace80b8e6068b9c613aa56ed50e0374bf44a891447511f1264e40d" +checksum = "a7a9663a8d7c369e934aac2b74a638537ad7eb4be75b4530d765384dc071c936" dependencies = [ "derive-where", "ra-ap-rustc_index", @@ -2133,9 +2133,9 @@ dependencies = [ [[package]] name = "ra-ap-rustc_parse_format" -version = "0.165.0" +version = "0.166.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a737f844bdef8ac5ab54dadf2f34704b4d06beef9236d71080bb34db697220b" +checksum = "2c038b7a8b0f784d4e441ad8ab991fbbdaa5e0be482e59c639a846a1c8126951" dependencies = [ "ra-ap-rustc_lexer", "rustc-literal-escaper", @@ -2143,9 +2143,9 @@ dependencies = [ [[package]] name = "ra-ap-rustc_pattern_analysis" -version = "0.165.0" +version = "0.166.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6de3d4c7d6078cce3c40c55717b8b15002a80b9fa8849faea496a365324861b4" +checksum = "42ca286f90e99bb97cd9274c088f3c874a05d1ee90cabf40a3928afedabe99fd" dependencies = [ "ra-ap-rustc_index", "rustc-hash 2.1.2", @@ -2156,9 +2156,9 @@ dependencies = [ [[package]] name = "ra-ap-rustc_type_ir" -version = "0.165.0" +version = "0.166.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c5d9a4d3e7bee7313599bc6d794037247ac0165f03857379cf4fc3097199e05" +checksum = "26d6efb6008f665a9485e0afecf9f4950a6c4bedd8ddd330a9df8986a6c0160b" dependencies = [ "arrayvec", "bitflags", @@ -2177,9 +2177,9 @@ dependencies = [ [[package]] name = "ra-ap-rustc_type_ir_macros" -version = "0.165.0" +version = "0.166.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "024598d1f54272acd83d28c121f8a2e82e216dd7be1e40158b66b2d12fa214c0" +checksum = "5f4fd2355e2bbf1f343c730f623596efc6e465b5e3685b606a437567ebb75bf8" dependencies = [ "proc-macro2", "quote", diff --git a/src/tools/rust-analyzer/Cargo.toml b/src/tools/rust-analyzer/Cargo.toml index 2a219c3ea485c..4ef92c5bd2ca3 100644 --- a/src/tools/rust-analyzer/Cargo.toml +++ b/src/tools/rust-analyzer/Cargo.toml @@ -86,14 +86,14 @@ vfs-notify = { path = "./crates/vfs-notify", version = "0.0.0" } vfs = { path = "./crates/vfs", version = "0.0.0" } edition = { path = "./crates/edition", version = "0.0.0" } -ra-ap-rustc_lexer = { version = "0.165", default-features = false } -ra-ap-rustc_parse_format = { version = "0.165", default-features = false } -ra-ap-rustc_index = { version = "0.165", default-features = false } -ra-ap-rustc_abi = { version = "0.165", default-features = false } -ra-ap-rustc_pattern_analysis = { version = "0.165", default-features = false } -ra-ap-rustc_ast_ir = { version = "0.165", default-features = false } -ra-ap-rustc_type_ir = { version = "0.165", default-features = false } -ra-ap-rustc_next_trait_solver = { version = "0.165", default-features = false } +ra-ap-rustc_lexer = { version = "0.166", default-features = false } +ra-ap-rustc_parse_format = { version = "0.166", default-features = false } +ra-ap-rustc_index = { version = "0.166", default-features = false } +ra-ap-rustc_abi = { version = "0.166", default-features = false } +ra-ap-rustc_pattern_analysis = { version = "0.166", default-features = false } +ra-ap-rustc_ast_ir = { version = "0.166", default-features = false } +ra-ap-rustc_type_ir = { version = "0.166", default-features = false } +ra-ap-rustc_next_trait_solver = { version = "0.166", default-features = false } # local crates that aren't published to crates.io. These should not have versions. 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 e8702bf2c99de..97e9d8bae1907 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 @@ -31,7 +31,7 @@ use hir_def::{ }; use rustc_hash::{FxHashMap, FxHashSet}; use rustc_type_ir::{ - TypeVisitableExt, + TypeVisitableExt, VisitorResult, fast_reject::{TreatParams, simplify_type}, inherent::{BoundExistentialPredicates, IntoKind}, }; @@ -54,6 +54,7 @@ use crate::{ obligation_ctxt::ObligationCtxt, util::clauses_as_obligations, }, + ret, traits::ParamEnvAndCrate, }; @@ -835,27 +836,34 @@ impl<'db> TraitImpls<'db> { } } - pub fn for_each_crate_and_block( + pub fn for_each_crate_and_block( db: &'db dyn HirDatabase, krate: Crate, block: Option>, - for_each: &mut dyn FnMut(&TraitImpls<'db>), - ) { + for_each: &mut dyn FnMut(&TraitImpls<'db>) -> R, + ) -> R { let blocks = std::iter::successors(block, |block| block.module(db).block(db)); - blocks.filter_map(|block| Self::for_block(db, block)).for_each(&mut *for_each); - Self::for_crate_and_deps(db, krate).iter().map(|it| &**it).for_each(for_each); + for impl_ in blocks.filter_map(|block| Self::for_block(db, block)) { + ret!(for_each(impl_)); + } + for impl_ in Self::for_crate_and_deps(db, krate) { + ret!(for_each(impl_)); + } + R::output() } /// Like [`Self::for_each_crate_and_block()`], but takes in account two blocks, one for a trait and one for a self type. - pub fn for_each_crate_and_block_trait_and_type( + pub fn for_each_crate_and_block_trait_and_type( db: &'db dyn HirDatabase, krate: Crate, type_block: Option>, trait_block: Option>, - for_each: &mut dyn FnMut(&TraitImpls<'db>), - ) { + for_each: &mut dyn FnMut(&TraitImpls<'db>) -> R, + ) -> R { let in_self_and_deps = TraitImpls::for_crate_and_deps(db, krate); - in_self_and_deps.iter().for_each(|impls| for_each(impls)); + for impl_ in in_self_and_deps { + ret!(for_each(impl_)); + } // We must not provide duplicate impls to the solver. Therefore we work with the following strategy: // start from each block, and walk ancestors until you meet the other block. If they never meet, @@ -874,13 +882,20 @@ impl<'db> TraitImpls<'db> { .filter_map(move |block| TraitImpls::for_block(db, block)) }; if trait_block == type_block { - blocks_iter(trait_block) - .filter_map(|block| TraitImpls::for_block(db, block)) - .for_each(for_each); + for impl_ in + blocks_iter(trait_block).filter_map(|block| TraitImpls::for_block(db, block)) + { + ret!(for_each(impl_)); + } } else { - for_each_block(trait_block, type_block).for_each(&mut *for_each); - for_each_block(type_block, trait_block).for_each(for_each); + for impl_ in for_each_block(trait_block, type_block) { + ret!(for_each(impl_)); + } + for impl_ in for_each_block(type_block, trait_block) { + ret!(for_each(impl_)); + } } + R::output() } } 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 a7216a034cc56..dc30c1e582eff 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 @@ -29,7 +29,7 @@ use rustc_index::bit_set::DenseBitSet; use rustc_type_ir::{ AliasTy, BoundVar, CoroutineWitnessTypes, DebruijnIndex, EarlyBinder, FlagComputation, Flags, FnSigKind, GenericArgKind, GenericTypeVisitable, ImplPolarity, InferTy, Interner, TraitRef, - TypeFlags, TypeVisitableExt, Upcast, Variance, + TypeFlags, TypeVisitableExt, Upcast, Variance, VisitorResult, elaborate::elaborate, error::TypeError, fast_reject, @@ -54,6 +54,7 @@ use crate::{ TraitAssocTyId, TraitIdWrapper, TypeAliasIdWrapper, UnevaluatedConst, Unnormalized, util::{explicit_item_bounds, explicit_item_self_bounds}, }, + ret, }; use super::{ @@ -1601,12 +1602,12 @@ impl<'db> Interner for DbInterner<'db> { def_id.0.trait_items(self.db()).associated_types().map(|id| id.into()) } - fn for_each_relevant_impl( + fn for_each_relevant_impl( self, trait_def_id: Self::TraitId, self_ty: Self::Ty, - mut f: impl FnMut(Self::ImplId), - ) { + mut f: impl FnMut(Self::ImplId) -> R, + ) -> R { let krate = self.krate.expect("trait solving requires setting `DbInterner::krate`"); let trait_block = trait_def_id.0.loc(self.db).container.block(self.db); let mut consider_impls_for_simplified_type = |simp: SimplifiedType<'_>| { @@ -1641,13 +1642,14 @@ impl<'db> Interner for DbInterner<'db> { let (regular_impls, builtin_derive_impls) = impls.for_trait_and_self_ty(trait_def_id.0, &simp); for &impl_ in regular_impls { - f(impl_.into()); + ret!(f(impl_.into())); } for &impl_ in builtin_derive_impls { - f(impl_.into()); + ret!(f(impl_.into())); } + R::output() }, - ); + ) }; match self_ty.kind() { @@ -1676,7 +1678,7 @@ impl<'db> Interner for DbInterner<'db> { let simp = fast_reject::simplify_type(self, self_ty, fast_reject::TreatParams::AsRigid) .unwrap(); - consider_impls_for_simplified_type(simp); + ret!(consider_impls_for_simplified_type(simp)); } // HACK: For integer and float variables we have to manually look at all impls @@ -1704,7 +1706,7 @@ impl<'db> Interner for DbInterner<'db> { SimplifiedType::Uint(Usize), ]; for simp in possible_integers { - consider_impls_for_simplified_type(simp); + ret!(consider_impls_for_simplified_type(simp)); } } @@ -1719,7 +1721,7 @@ impl<'db> Interner for DbInterner<'db> { ]; for simp in possible_floats { - consider_impls_for_simplified_type(simp); + ret!(consider_impls_for_simplified_type(simp)); } } @@ -1748,15 +1750,22 @@ impl<'db> Interner for DbInterner<'db> { self.for_each_blanket_impl(trait_def_id, f) } - fn for_each_blanket_impl(self, trait_def_id: Self::TraitId, mut f: impl FnMut(Self::ImplId)) { - let Some(krate) = self.krate else { return }; + fn for_each_blanket_impl( + self, + trait_def_id: Self::TraitId, + mut f: impl FnMut(Self::ImplId) -> R, + ) -> R { + let Some(krate) = self.krate else { + return R::output(); + }; let block = trait_def_id.0.loc(self.db).container.block(self.db); TraitImpls::for_each_crate_and_block(self.db, krate, block, &mut |impls| { for &impl_ in impls.blanket_impls(trait_def_id.0) { - f(impl_.into()); + ret!(f(impl_.into())); } - }); + R::output() + }) } fn has_item_definition(self, _def_id: Self::ImplOrTraitAssocTermId) -> bool { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/util.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/util.rs index 7e40e3c17d517..e384ac455213d 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/util.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/util.rs @@ -723,3 +723,15 @@ pub(crate) fn clauses_as_obligations<'db>( recursion_depth: 0, }) } + +/// Copied from +/// +#[macro_export] +macro_rules! ret { + ($e: expr) => { + match $e.branch() { + ::std::ops::ControlFlow::Break(b) => return R::from_residual(b), + ::std::ops::ControlFlow::Continue(()) => {} + } + }; +} From 2a82c4bcb68d67261be1c5ca9439f7d26b8beb16 Mon Sep 17 00:00:00 2001 From: cuishuang Date: Tue, 28 Jul 2026 23:48:01 +0800 Subject: [PATCH 21/63] fix(vfs): use component-based path prefix matching for virtual paths --- src/tools/rust-analyzer/crates/vfs/src/vfs_path.rs | 4 ++-- src/tools/rust-analyzer/crates/vfs/src/vfs_path/tests.rs | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/vfs/src/vfs_path.rs b/src/tools/rust-analyzer/crates/vfs/src/vfs_path.rs index 7e2c787afc738..eb55081c2038e 100644 --- a/src/tools/rust-analyzer/crates/vfs/src/vfs_path.rs +++ b/src/tools/rust-analyzer/crates/vfs/src/vfs_path.rs @@ -337,9 +337,9 @@ impl PartialEq for AbsPath { struct VirtualPath(String); impl VirtualPath { - /// Returns `true` if `other` is a prefix of `self` (as strings). + /// Returns `true` if `other` is a prefix of `self`. fn starts_with(&self, other: &VirtualPath) -> bool { - self.0.starts_with(&other.0) + <_ as AsRef>::as_ref(&self.0).starts_with(&other.0) } fn strip_prefix(&self, base: &VirtualPath) -> Option<&RelPath> { diff --git a/src/tools/rust-analyzer/crates/vfs/src/vfs_path/tests.rs b/src/tools/rust-analyzer/crates/vfs/src/vfs_path/tests.rs index 2d89362ee0691..c21fabfbf0460 100644 --- a/src/tools/rust-analyzer/crates/vfs/src/vfs_path/tests.rs +++ b/src/tools/rust-analyzer/crates/vfs/src/vfs_path/tests.rs @@ -1,5 +1,13 @@ use super::*; +#[test] +fn virtual_path_starts_with_is_component_based() { + let path = |path: &str| VfsPath::new_virtual_path(path.to_owned()); + + assert!(!path("/foobar").starts_with(&path("/foo"))); + assert!(path("/foo/bar").starts_with(&path("/foo"))); +} + #[test] fn virtual_path_extensions() { assert_eq!(VirtualPath("/".to_owned()).name_and_extension(), None); From 0248a75893dfbf84174591b45e09fea8c3758fd6 Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Tue, 28 Jul 2026 10:15:20 -0700 Subject: [PATCH 22/63] Reformat snippet docs so that they appear in the book --- .../rust-analyzer/crates/ide-completion/src/snippet.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/snippet.rs b/src/tools/rust-analyzer/crates/ide-completion/src/snippet.rs index ee47c84708b46..20981433ae7ac 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/snippet.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/snippet.rs @@ -38,13 +38,12 @@ // * `description` is an optional description of the snippet, if unset the snippet name will be used. // // * `requires` is an optional list of item paths that have to be resolvable in the current crate where the completion is rendered. - // On failure of resolution the snippet won't be applicable, otherwise the snippet will insert an import for the items on insertion if // the items aren't yet in scope. // // * `scope` is an optional filter for when the snippet should be applicable. Possible values are: -// ** for Snippet-Scopes: `expr`, `item` (default: `item`) -// ** for Postfix-Snippet-Scopes: `expr`, `type` (default: `expr`) +// * for Snippet-Scopes: `expr`, `item` (default: `item`) +// * for Postfix-Snippet-Scopes: `expr`, `type` (default: `expr`) // // The `body` field also has access to placeholders as visible in the example as `$0`. // These placeholders take the form of `$number` or `${number:placeholder_text}` which can be traversed as tabstop in ascending order starting from 1, @@ -98,7 +97,7 @@ // "scope": "expr" // } // } -// ```` +// ``` use hir::{ModPath, Name, Symbol}; use ide_db::imports::import_assets::LocatedImport; From 02f2944526cea5777442a544fcc9c574f9d7387f Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Tue, 28 Jul 2026 21:05:57 +0300 Subject: [PATCH 23/63] Mark auto traits as coinductive Coinductive traits are traits that when proving a predicate for them, `Type: Trait`, inside the predicate we can rely on itself to hold. For example, in `struct Foo(Foo)`, coinductive trait will succeed `Foo: Trait` and non-coinductive trait will fail unless there's an `impl Trait for Foo`. In Rust, only auto traits and `#[rustc_coinductive]` traits are coinductive, but previously we haven't considered auto traits coinductive. --- .../crates/hir-def/src/signatures.rs | 2 +- .../crates/hir-ty/src/tests/traits.rs | 34 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/signatures.rs b/src/tools/rust-analyzer/crates/hir-def/src/signatures.rs index 10a38ec71e372..45dab8859d270 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/signatures.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/signatures.rs @@ -545,7 +545,7 @@ impl TraitSignature { let attrs = AttrFlags::query(db, id.into()); let source = loc.source(db); if source.value.auto_token().is_some() { - flags.insert(TraitFlags::AUTO); + flags.insert(TraitFlags::AUTO | TraitFlags::COINDUCTIVE); } if source.value.unsafe_token().is_some() { flags.insert(TraitFlags::UNSAFE); 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 8233a009816fc..fad944589dab6 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 @@ -5377,3 +5377,37 @@ fn run_dyn<'b>(val: &dyn for<'a> Trait<'a, 'b>) {} "#]], ); } + +#[test] +fn recursive_auto_trait() { + check_types( + r#" +auto trait Send {} +impl !Send for *const T {} + +struct Vec(*const T); +impl Send for Vec {} + +struct Node { + children: Vec, +} + +struct Holder(T); + +trait Lock { + fn get(&self) -> &T; +} + +impl Lock for Holder { + fn get(&self) -> &T { + &self.0 + } +} + +fn probe(h: &Holder) { + h.get(); + // ^^^^^^^ &'? Node +} + "#, + ); +} From 13901c01ad3d7bbcb384e346230de145f5552aaa Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Wed, 29 Jul 2026 07:32:47 +0300 Subject: [PATCH 24/63] Store liberated closure sigs in InferenceResult We (will) need them in later stages, e.g. MIR building. --- .../rust-analyzer/crates/hir-ty/src/infer.rs | 44 +++++++++++++++++-- .../crates/hir-ty/src/infer/closure.rs | 16 +++++-- .../hir-ty/src/infer/closure/analysis.rs | 5 +-- .../crates/hir-ty/src/next_solver/binder.rs | 34 +++++++++----- .../hir-ty/src/next_solver/generic_arg.rs | 42 ++---------------- .../crates/hir-ty/src/next_solver/interner.rs | 29 ++++++++++++ .../crates/hir-ty/src/next_solver/ty.rs | 26 ++--------- 7 files changed, 115 insertions(+), 81 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 bd00da84cafad..c838fcc3a7640 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs @@ -99,7 +99,7 @@ use crate::{ }, method_resolution::CandidateId, next_solver::{ - AliasTy, Const, ConstKind, DbInterner, ErrorGuaranteed, GenericArgs, Region, + AliasTy, Const, ConstKind, DbInterner, ErrorGuaranteed, GenericArgs, Region, StoredFnSig, StoredGenericArg, StoredGenericArgs, StoredTy, StoredTys, Term, Ty, TyKind, Tys, abi::Safety, infer::{InferCtxt, ObligationInspector, traits::ObligationCause}, @@ -820,7 +820,7 @@ pub struct InferenceResult<'db> { defined_anon_consts: ThinVec>, } -#[derive(Clone, PartialEq, Eq, Debug, Default)] +#[derive(Clone, PartialEq, Eq, Debug)] pub struct ClosureData { /// Tracks the minimum captures required for a closure; /// see `MinCaptureInformationMap` for more details. @@ -849,6 +849,42 @@ pub struct ClosureData { /// information on `t` in order to create place `t.0` and `t.1`. We can solve this /// issue by fake reading `t`. pub fake_reads: Box<[(Place, FakeReadCause, SmallVec<[CaptureSourceStack; 2]>)]>, + + /// For each fn, records the "liberated" types of its arguments + /// and return type. Liberated means that all bound regions + /// (including late-bound regions) are replaced with free + /// equivalents. This table is not used in codegen (since regions + /// are erased there) and hence is not serialized to metadata. + /// + /// This table also contains the "revealed" values for any `impl Trait` + /// that appear in the signature and whose values are being inferred + /// by this function. + /// + /// # Example + /// + /// ```rust + /// # use std::fmt::Debug; + /// fn foo(x: &u32) -> impl Debug { *x } + /// ``` + /// + /// The function signature here would be: + /// + /// ```ignore (illustrative) + /// for<'a> fn(&'a u32) -> Foo + /// ``` + /// + /// where `Foo` is an opaque type created for this function. + /// + /// + /// The *liberated* form of this would be + /// + /// ```ignore (illustrative) + /// fn(&'a u32) -> u32 + /// ``` + /// + /// Note that `'a` is not bound (it would be an `ReLateParam`) and + /// that the `Foo` opaque type is replaced by its hidden type. + pub liberated_sig: StoredFnSig, } /// Part of `MinCaptureInformationMap`; Maps a root variable to the list of `CapturedPlace`. @@ -1677,7 +1713,7 @@ impl<'db> InferenceContext<'db> { } pat_adjustments.shrink_to_fit(); for closure_data in closures_data.values_mut() { - let ClosureData { min_captures, fake_reads } = closure_data; + let ClosureData { min_captures, fake_reads, liberated_sig } = closure_data; let dummy_place = || Place { base_ty: types.types.error.store(), base: closure::analysis::expr_use_visitor::PlaceBase::Rvalue, @@ -1706,6 +1742,8 @@ impl<'db> InferenceContext<'db> { min_capture.shrink_to_fit(); } min_captures.shrink_to_fit(); + + resolver.resolve_completely(liberated_sig); } closures_data.shrink_to_fit(); *tuple_field_access_types = tuple_field_accesses_rev diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs index e2948a81ac7d9..9af182fc49288 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs @@ -9,6 +9,7 @@ use hir_def::{ hir::{ClosureKind, CoroutineKind, CoroutineSource, ExprId, PatId}, type_ref::TypeRefId, }; +use indexmap::IndexMap; use rustc_abi::ExternAbi; use rustc_type_ir::{ AliasTyKind, ClosureArgs, ClosureArgsParts, CoroutineArgs, CoroutineArgsParts, @@ -21,11 +22,11 @@ use tracing::{debug, instrument}; use crate::{ Span, db::{InternedClosure, InternedClosureId, InternedCoroutineClosureId, InternedCoroutineId}, - infer::{BreakableKind, Diverges, coerce::CoerceMany, pat::PatOrigin}, + infer::{BreakableKind, ClosureData, Diverges, coerce::CoerceMany, pat::PatOrigin}, next_solver::{ AliasTy, Binder, ClauseKind, DbInterner, ErrorGuaranteed, FnSig, GenericArg, PolyFnSig, - PolyProjectionPredicate, Predicate, PredicateKind, SolverDefId, TermId, Ty, TyKind, - Unnormalized, + PolyProjectionPredicate, Predicate, PredicateKind, SolverDefId, StoredFnSig, TermId, Ty, + TyKind, Unnormalized, abi::Safety, infer::{ BoundRegionConversionTime, InferOk, InferResult, @@ -303,6 +304,15 @@ impl<'db> InferenceContext<'db> { } }; + self.result.closures_data.insert( + closure_expr, + ClosureData { + liberated_sig: StoredFnSig::new(liberated_sig), + fake_reads: Box::default(), + min_captures: IndexMap::default(), + }, + ); + // Now go through the argument patterns for (arg_pat, arg_ty) in args.iter().zip(liberated_sig.inputs()) { self.infer_top_pat(*arg_pat, *arg_ty, PatOrigin::Param); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure/analysis.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure/analysis.rs index 38e634eb7db49..0c24b82d1bde0 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure/analysis.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure/analysis.rs @@ -515,7 +515,7 @@ impl<'db> InferenceContext<'db> { let fake_reads = delegate.fake_reads; - self.result.closures_data.entry(closure_expr_id).or_default().fake_reads = + self.result.closures_data.get_mut(&closure_expr_id).unwrap().fake_reads = fake_reads.into_boxed_slice(); // If we are also inferred the closure kind here, @@ -730,8 +730,7 @@ impl<'db> InferenceContext<'db> { return; } - let mut closure_data = - self.result.closures_data.remove(&closure_def_id).unwrap_or_default(); + let mut closure_data = self.result.closures_data.remove(&closure_def_id).unwrap(); let root_var_min_capture_list = &mut closure_data.min_captures; let mut dedup_sources_scratch = FxHashMap::default(); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/binder.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/binder.rs index 9585cced6b114..351d0c4cda47e 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/binder.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/binder.rs @@ -71,17 +71,33 @@ impl StoredEarlyBinder { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct StoredPolyFnSig { bound_vars: StoredBoundVarKinds, - inputs_and_output: StoredTys, - fn_sig_kind: FnSigKind<'static>, + sig: StoredFnSig, } impl StoredPolyFnSig { #[inline] pub fn new(sig: PolyFnSig<'_>) -> Self { let bound_vars = sig.bound_vars().store(); - let sig = sig.skip_binder(); + Self { bound_vars, sig: StoredFnSig::new(sig.skip_binder()) } + } + + #[inline] + pub fn get(&self) -> PolyFnSig<'_> { + Binder::bind_with_vars(self.sig.get(), self.bound_vars.as_ref()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, TypeVisitable, TypeFoldable)] +pub struct StoredFnSig { + inputs_and_output: StoredTys, + #[type_visitable(ignore)] + fn_sig_kind: FnSigKind<'static>, +} + +impl StoredFnSig { + #[inline] + pub fn new(sig: FnSig<'_>) -> Self { Self { - bound_vars, inputs_and_output: sig.inputs_and_output.store(), fn_sig_kind: FnSigKind::new( sig.fn_sig_kind.abi(), @@ -92,14 +108,8 @@ impl StoredPolyFnSig { } #[inline] - pub fn get(&self) -> PolyFnSig<'_> { - Binder::bind_with_vars( - FnSig { - inputs_and_output: self.inputs_and_output.as_ref(), - fn_sig_kind: self.fn_sig_kind, - }, - self.bound_vars.as_ref(), - ) + pub fn get(&self) -> FnSig<'_> { + FnSig { inputs_and_output: self.inputs_and_output.as_ref(), fn_sig_kind: self.fn_sig_kind } } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/generic_arg.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/generic_arg.rs index 22b34b379dd86..483811f9e6f0c 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/generic_arg.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/generic_arg.rs @@ -21,7 +21,8 @@ use rustc_type_ir::{ }; use crate::next_solver::{ - ConstInterned, RegionInterned, TyInterned, impl_foldable_for_interned_slice, interned_slice, + ConstInterned, RegionInterned, TyInterned, impl_foldable_for_interned_slice, + impl_foldable_for_stored_type, interned_slice, }; use super::{ @@ -194,24 +195,7 @@ impl std::fmt::Debug for StoredGenericArg { } } -impl<'db> TypeVisitable> for StoredGenericArg { - fn visit_with>>(&self, visitor: &mut V) -> V::Result { - self.as_ref().visit_with(visitor) - } -} - -impl<'db> TypeFoldable> for StoredGenericArg { - fn try_fold_with>>( - self, - folder: &mut F, - ) -> Result { - Ok(self.as_ref().try_fold_with(folder)?.store()) - } - - fn fold_with>>(self, folder: &mut F) -> Self { - self.as_ref().fold_with(folder).store() - } -} +impl_foldable_for_stored_type!(StoredGenericArg); #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct GenericArg<'db> { @@ -473,28 +457,10 @@ interned_slice!( GenericArg<'static>, ); impl_foldable_for_interned_slice!(GenericArgs); +impl_foldable_for_stored_type!(StoredGenericArgs); impl<'db> rustc_type_ir::inherent::GenericArg> for GenericArg<'db> {} -impl<'db> TypeVisitable> for StoredGenericArgs { - fn visit_with>>(&self, visitor: &mut V) -> V::Result { - self.as_ref().visit_with(visitor) - } -} - -impl<'db> TypeFoldable> for StoredGenericArgs { - fn try_fold_with>>( - self, - folder: &mut F, - ) -> Result { - Ok(self.as_ref().try_fold_with(folder)?.store()) - } - - fn fold_with>>(self, folder: &mut F) -> Self { - self.as_ref().fold_with(folder).store() - } -} - trait GenericArgsBuilder<'db>: AsRef<[GenericArg<'db>]> { fn push(&mut self, arg: GenericArg<'db>); } 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 dc30c1e582eff..7554ca6bcd025 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 @@ -265,6 +265,35 @@ macro_rules! impl_foldable_for_interned_slice { } pub(crate) use impl_foldable_for_interned_slice; +macro_rules! impl_foldable_for_stored_type { + ($name:ident) => { + impl<'db> ::rustc_type_ir::TypeVisitable> for $name { + fn visit_with>>( + &self, + visitor: &mut V, + ) -> V::Result { + self.as_ref().visit_with(visitor) + } + } + + impl<'db> rustc_type_ir::TypeFoldable> for $name { + fn try_fold_with>>( + self, + folder: &mut F, + ) -> Result { + Ok(self.as_ref().try_fold_with(folder)?.store()) + } + fn fold_with>>( + self, + folder: &mut F, + ) -> Self { + self.as_ref().fold_with(folder).store() + } + } + }; +} +pub(crate) use impl_foldable_for_stored_type; + macro_rules! impl_stored_interned { ( $storage:ident, $name:ident, $stored_name:ident $(,)? ) => { #[derive(Clone, PartialEq, Eq, Hash)] 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 05f559c0349e9..36c18ed772ca7 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 @@ -33,7 +33,8 @@ use crate::{ CoroutineClosureIdWrapper, CoroutineIdWrapper, FnSig, GenericArgKind, PolyFnSig, Predicate, Region, TraitRef, TypeAliasIdWrapper, Unnormalized, abi::Safety, - impl_foldable_for_interned_slice, impl_stored_interned, interned_slice, + impl_foldable_for_interned_slice, impl_foldable_for_stored_type, impl_stored_interned, + interned_slice, util::{CoroutineArgsExt, IntegerTypeExt}, }, }; @@ -61,6 +62,7 @@ pub(super) struct TyInterned(WithCachedTypeInfo>); impl_internable!(gc; TyInterned); impl_stored_interned!(TyInterned, Ty, StoredTy); +impl_foldable_for_stored_type!(StoredTy); const _: () = { const fn is_copy() {} @@ -894,15 +896,6 @@ impl<'db> TypeVisitable> for Ty<'db> { } } -impl<'db> TypeVisitable> for StoredTy { - fn visit_with>>( - &self, - visitor: &mut V, - ) -> V::Result { - self.as_ref().visit_with(visitor) - } -} - impl<'db> TypeSuperVisitable> for Ty<'db> { fn super_visit_with>>( &self, @@ -969,18 +962,6 @@ impl<'db> TypeFoldable> for Ty<'db> { } } -impl<'db> TypeFoldable> for StoredTy { - fn try_fold_with>>( - self, - folder: &mut F, - ) -> Result { - Ok(self.as_ref().try_fold_with(folder)?.store()) - } - fn fold_with>>(self, folder: &mut F) -> Self { - self.as_ref().fold_with(folder).store() - } -} - impl<'db> TypeSuperFoldable> for Ty<'db> { fn try_super_fold_with>>( self, @@ -1422,6 +1403,7 @@ impl<'db> rustc_type_ir::inherent::Ty> for Ty<'db> { interned_slice!(TysStorage, Tys, StoredTys, tys, Ty<'db>, Ty<'static>); impl_foldable_for_interned_slice!(Tys); +impl_foldable_for_stored_type!(StoredTys); impl<'db> Tys<'db> { #[inline] From a6a9f65c56f27188e39a001bd24dc991d777e5d7 Mon Sep 17 00:00:00 2001 From: Joshua Isika Date: Wed, 29 Jul 2026 10:34:59 +0300 Subject: [PATCH 25/63] Report a config error for postfix snippets with item scope Postfix custom snippets are never considered by completion when `scope` is `item` (only `Expr`-scoped postfix snippets are checked in completions::postfix), so this combination previously created a snippet that silently never fired. Validate the combination during deserialization of `SnippetDef` via `#[serde(try_from = "SnippetDefRepr")]`. Both the client JSON config and `rust-analyzer.toml` configs deserialize a `SnippetDef` per map entry, so this covers both config sources with one check. Trade-off: because the check now runs during `FxIndexMap` deserialization, one invalid entry fails deserialization of the whole custom-snippets map, falling back to the built-in default snippets rather than dropping only the bad entry. This matches how any other structurally invalid `SnippetDef` field already behaves. Fixes rust-lang/rust-analyzer#22894 --- .../crates/rust-analyzer/src/config.rs | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) 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 5b6215c41f384..3ba0e47f14bcb 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs @@ -2908,6 +2908,7 @@ enum SnippetScopeDef { #[derive(Serialize, Deserialize, Debug, Clone, Default)] #[serde(default)] +#[serde(try_from = "SnippetDefRepr")] pub(crate) struct SnippetDef { #[serde(with = "single_or_array")] #[serde(skip_serializing_if = "Vec::is_empty")] @@ -2931,6 +2932,46 @@ pub(crate) struct SnippetDef { scope: SnippetScopeDef, } +/// Plain deserialization target for [`SnippetDef`]. Both the client JSON +/// config and `rust-analyzer.toml` configs deserialize a `SnippetDef` per +/// map entry, so validating the field combination here (via `TryFrom`) +/// covers both config sources instead of only one. +#[derive(Deserialize, Default)] +#[serde(default)] +struct SnippetDefRepr { + #[serde(with = "single_or_array")] + prefix: Vec, + #[serde(with = "single_or_array")] + postfix: Vec, + #[serde(with = "single_or_array")] + body: Vec, + #[serde(with = "single_or_array")] + requires: Vec, + description: Option, + scope: SnippetScopeDef, +} + +impl TryFrom for SnippetDef { + type Error = String; + + fn try_from(repr: SnippetDefRepr) -> Result { + if repr.scope == SnippetScopeDef::Item && !repr.postfix.is_empty() { + return Err( + "'postfix' is not supported together with '\"scope\": \"item\"'; postfix snippets are not supported in item scope" + .to_owned(), + ); + } + Ok(SnippetDef { + prefix: repr.prefix, + postfix: repr.postfix, + body: repr.body, + requires: repr.requires, + description: repr.description, + scope: repr.scope, + }) + } +} + mod single_or_array { use serde::{Deserialize, Serialize}; @@ -4428,4 +4469,30 @@ mod tests { == Some(Utf8PathBuf::from("other_folder")) )); } + #[test] + fn postfix_snippet_item_scope_is_invalid() { + let mut config = + Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None); + let mut change = ConfigChange::default(); + change.change_client_config(serde_json::json!({ + "completion":{ + "snippets": { + "custom":{ + "foo": { + "postfix": "foo", + "body": "foo", + "scope": "item" + } + } + } + } + })); + let errors; + (config, errors, _) = config.apply_change(change); + assert!(!errors.0.is_empty(), "expected a config error for postfix+item scope"); + assert!( + config.snippets.iter().all(|s| s.postfix_triggers.iter().all(|t| &**t != "foo")), + "invalid snippet should not have been registered" + ); + } } From c5ccd5b3e5fdb32a4a44a76a5d6e053ab7ca962b Mon Sep 17 00:00:00 2001 From: Kivanc Gunalp Date: Wed, 29 Jul 2026 07:34:44 +0000 Subject: [PATCH 26/63] hir-ty, ide-diagnostics: use E0057 vs E0061 for arg-count mismatch The MismatchedArgCount diagnostic previously used code E0107, which is actually 'wrong number of generic arguments'. Split it based on how the call is made: - E0057 for calls through the Fn/FnMut/FnOnce traits (arguments bundled into a tuple via TupleArgumentsFlag::TupleArguments in the inference code) - E0061 for regular function calls This adds an is_fn_trait_call flag on InferenceDiagnostic::MismatchedArgCount and the hir-surface MismatchedArgCount struct, populated from the tuple_arguments flag already tracked by check_call_arguments. The downstream 'if !args_count_matches' push in infer/expr.rs already covers both paths, so the two FIXMEs at the top of the tuple branch are addressed by threading the kind through rather than by adding a new push site. The nightly-only fallback FIXME below (E0059-ish) is left alone per discussion on rust-lang/rust-analyzer#22140. Adds a test 'arg_count_multi_arg_closure' that exercises the multi-argument tuple case via a closure with signature |_a: u8, _b: u8|. This complements the existing 'arg_count_lambda' test (1-tuple case). Refs rust-lang/rust-analyzer#22140 --- .../rust-analyzer/crates/hir-ty/src/infer.rs | 5 ++++ .../crates/hir-ty/src/infer/expr.rs | 8 ++--- .../crates/hir/src/diagnostics.rs | 18 +++++++++-- .../src/handlers/mismatched_arg_count.rs | 30 ++++++++++++++++++- 4 files changed, 53 insertions(+), 8 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 bd00da84cafad..4036722bafb3a 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs @@ -410,6 +410,11 @@ pub enum InferenceDiagnostic { expected: usize, #[type_visitable(ignore)] found: usize, + /// True when the call goes through the `Fn`/`FnMut`/`FnOnce` trait + /// (i.e. arguments were bundled into a tuple). Determines whether the + /// diagnostic surface uses E0057 (Fn-trait call) or E0061 (regular call). + #[type_visitable(ignore)] + is_fn_trait_call: bool, }, MismatchedTupleStructPatArgCount { #[type_visitable(ignore)] 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 20cfc9008a9e1..570df6b871df1 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 @@ -2015,10 +2015,9 @@ impl<'db> InferenceContext<'db> { match tuple_type.kind() { // We expected a tuple and got a tuple TyKind::Tuple(arg_types) => { - // Argument length differs - if arg_types.len() != provided_args.len() { - // FIXME: Emit an error. - } + // Argument length differs. The mismatch is reported below by the + // shared `MismatchedArgCount` push (with `is_fn_trait_call = true`, + // which the diagnostic surface renders as E0057). let expected_input_tys = match expected_input_tys { Some(expected_input_tys) => match expected_input_tys.first() { Some(ty) => match ty.kind() { @@ -2068,6 +2067,7 @@ impl<'db> InferenceContext<'db> { call_expr, expected: expected_input_tys.len() + skip_indices.len(), found: provided_args.len(), + is_fn_trait_call: tuple_arguments == TupleArgumentsFlag::TupleArguments, }); } diff --git a/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs b/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs index d0801d8efd03e..0c4191ce2dd6f 100644 --- a/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs @@ -441,6 +441,9 @@ pub struct MismatchedArgCount { pub call_expr: InFile, pub expected: usize, pub found: usize, + /// True when the call is through a `Fn`/`FnMut`/`FnOnce` trait (E0057) + /// rather than a regular function call (E0061). + pub is_fn_trait_call: bool, } #[derive(Debug)] @@ -885,9 +888,18 @@ impl<'db> AnyDiagnostic<'db> { }; DuplicateField { field: expr_or_pat, variant: variant.into() }.into() } - &InferenceDiagnostic::MismatchedArgCount { call_expr, expected, found } => { - MismatchedArgCount { call_expr: expr_syntax(call_expr)?, expected, found }.into() - } + &InferenceDiagnostic::MismatchedArgCount { + call_expr, + expected, + found, + is_fn_trait_call, + } => MismatchedArgCount { + call_expr: expr_syntax(call_expr)?, + expected, + found, + is_fn_trait_call, + } + .into(), &InferenceDiagnostic::PrivateField { expr, field } => { let expr = expr_syntax(expr)?; let field = field.into(); diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs index 844431c1e5c46..fb9095e0f4bd2 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs @@ -38,8 +38,12 @@ pub(crate) fn mismatched_arg_count( ) -> Diagnostic { let s = if d.expected == 1 { "" } else { "s" }; let message = format!("expected {} argument{s}, found {}", d.expected, d.found); + // E0057 is the code rustc emits when calling something via the `Fn`/`FnMut`/`FnOnce` + // traits with the wrong number of arguments; E0061 is used for direct function calls. + // (Previously this used E0107, which is actually "wrong number of generic arguments".) + let code = if d.is_fn_trait_call { "E0057" } else { "E0061" }; Diagnostic::new( - DiagnosticCode::RustcHardError("E0107"), + DiagnosticCode::RustcHardError(code), message, invalid_args_range(ctx, d.call_expr, d.expected, d.found), ) @@ -395,6 +399,30 @@ fn main() { ) } + // A multi-argument closure exercises the same tuple-arguments code path in + // hir-ty (`TupleArgumentsFlag::TupleArguments` in `crates/hir-ty/src/infer/expr.rs`) + // as calls through `Fn`/`FnMut`/`FnOnce`. The mismatch is reported with error + // code E0057 (rustc's Fn-trait code), not E0061 which is reserved for direct + // function calls. `arg_count_lambda` above covers the 1-tuple case; this one + // covers the multi-argument case to make sure the tuple size is reported + // correctly. + #[test] + fn arg_count_multi_arg_closure() { + check_diagnostics( + r#" +//- minicore: fn +fn main() { + let f = |_a: u8, _b: u8| (); + f(); + //^^ error: expected 2 arguments, found 0 + f(1, 2); + f(1, 2, 3); + //^^ error: expected 2 arguments, found 3 +} +"#, + ) + } + #[test] fn cfgd_out_call_arguments() { check_diagnostics( From d1bfa493fcdcb628f4084d7441b5029f8da5eef0 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 29 Jul 2026 13:27:47 +0200 Subject: [PATCH 27/63] Implement `slice_get_unchecked` mir shim --- .../crates/hir-ty/src/mir/eval/shim.rs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) 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 e569b32bd779f..177b2c870f084 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 @@ -1012,6 +1012,43 @@ impl<'a, 'db> Evaluator<'a, 'db> { let dst = Interval { addr: dst, size }; dst.write_from_interval(self, src) } + "slice_get_unchecked" => { + let [slice_ptr, index] = args else { + return Err(MirEvalError::InternalError( + "slice_get_unchecked args are not provided".into(), + )); + }; + let Some(ty) = generic_args.as_slice().get(2).and_then(|it| it.ty()) else { + return Err(MirEvalError::InternalError( + "slice_get_unchecked item type is not provided".into(), + )); + }; + let slice_ptr = slice_ptr.get(self)?; + let ptr_size = self.ptr_size(); + let Some(data) = slice_ptr.get(..ptr_size) else { + return Err(MirEvalError::InternalError( + "slice_get_unchecked slice pointer is too small".into(), + )); + }; + let Some(len) = slice_ptr.get(ptr_size..2 * ptr_size) else { + return Err(MirEvalError::InternalError( + "slice_get_unchecked slice metadata is missing".into(), + )); + }; + let slice_ptr = Address::from_bytes(data)?; + let len = from_bytes!(usize, len); + let index = from_bytes!(usize, index.get(self)?); + if index >= len { + return Err(MirEvalError::UndefinedBehavior(format!( + "slice_get_unchecked index {index} is out of bounds for slice of length {len}" + ))); + } + let size = self.size_of_sized(ty, locals, "slice_get_unchecked item type")?; + let offset = index* size; + let addr = slice_ptr.to_usize() + offset; + let addr = Address::from_usize(addr); + destination.write_from_bytes(self, &addr.to_bytes()[..destination.size]) + } "offset" | "arith_offset" => { let [ptr, offset] = args else { return Err(MirEvalError::InternalError("offset args are not provided".into())); From 945c8fa4ee57b1a21ee27b8853d94f02553fcb79 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 29 Jul 2026 13:27:47 +0200 Subject: [PATCH 28/63] Recursively patch addresses of slices --- .../crates/hir-ty/src/mir/eval.rs | 28 ++++- .../crates/hir-ty/src/mir/eval/tests.rs | 103 ++++++++++++++++++ 2 files changed, 127 insertions(+), 4 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs index e968da5111add..b08381603470c 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs @@ -2548,7 +2548,6 @@ impl<'a, 'db> Evaluator<'a, 'db> { ty: Ty<'db>, locals: &Locals<'a, 'db>, ) -> Result<'db, ()> { - // FIXME: support indirect references let layout = self.layout(ty)?; let my_size = self.size_of_sized(ty, locals, "value to patch address")?; use rustc_type_ir::TyKind; @@ -2574,9 +2573,30 @@ impl<'a, 'db> Evaluator<'a, 'db> { )?; } None => { - let current = from_bytes!(usize, self.read_memory(addr, my_size / 2)?); - if let Some(it) = patch_map.get(¤t) { - self.write_memory(addr, &it.to_le_bytes())?; + let bytes = self.read_memory(addr, my_size)?; + let (current, metadata) = bytes.split_at(my_size / 2); + let metadata = metadata.to_vec(); + let current = from_bytes!(usize, current); + let patched = match patch_map.get(¤t) { + Some(it) => { + self.write_memory(addr, &it.to_le_bytes())?; + *it + } + None => current, + }; + let patched = Address::from_usize(patched); + if let TyKind::Slice(inner) = t.kind() { + let len = from_bytes!(usize, metadata); + let size = self.size_of_sized(inner, locals, "slice item to patch")?; + for i in 0..len { + self.patch_addresses( + patch_map, + ty_of_bytes, + patched.offset(i * size), + inner, + locals, + )?; + } } } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/tests.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/tests.rs index 68d19769d4811..7431ac8293e97 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/tests.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval/tests.rs @@ -1114,6 +1114,109 @@ fn main() { ); } +#[test] +fn slice_get_unchecked_intrinsic() { + check_pass( + r#" +//- minicore: panic +#[rustc_intrinsic] +unsafe fn slice_get_unchecked( + slice_ptr: SlicePtr, + index: usize, +) -> ItemPtr; + +fn should_not_reach() { panic!() } + +fn main() { + let values = [10, 20, 30]; + let slice_ptr = &values as *const [i32]; + let item_ptr = unsafe { + slice_get_unchecked::<*const i32, *const [i32], i32>(slice_ptr, 1) + }; + if unsafe { *item_ptr } != 20 { + should_not_reach(); + } +} +"#, + ); +} + +#[test] +fn slice_get_unchecked_out_of_bounds() { + check_error_with( + r#" +#[rustc_intrinsic] +unsafe fn slice_get_unchecked( + slice_ptr: SlicePtr, + index: usize, +) -> ItemPtr; + +fn main() { + let values = [()]; + let slice_ptr = &values as *const [()]; + let _item = unsafe { + slice_get_unchecked::<*const (), *const [()], ()>(slice_ptr, 1) + }; +} +"#, + |e| { + let mut err = &e; + while let MirEvalError::InFunction(inner, _) = err { + err = inner; + } + matches!(err, MirEvalError::UndefinedBehavior(_)) + }, + ); +} + +#[test] +fn slice_get_unchecked_const_slice() { + check_pass( + r#" +//- minicore: panic +#[rustc_intrinsic] +unsafe fn slice_get_unchecked( + slice_ptr: SlicePtr, + index: usize, +) -> ItemPtr; + +struct Flag { + name: &'static str, + value: u16, +} + +const PURE: &str = "PURE"; +const NOMEM: &str = "NOMEM"; +const READONLY: &str = "READONLY"; +const PRESERVES_FLAGS: &str = "PRESERVES_FLAGS"; +const NORETURN: &str = "NORETURN"; +const NOSTACK: &str = "NOSTACK"; +const ATT_SYNTAX: &str = "ATT_SYNTAX"; +const FLAGS: &[Flag] = &[ + Flag { name: PURE, value: 1 }, + Flag { name: NOMEM, value: 2 }, + Flag { name: READONLY, value: 4 }, + Flag { name: PRESERVES_FLAGS, value: 8 }, + Flag { name: NORETURN, value: 16 }, + Flag { name: NOSTACK, value: 32 }, + Flag { name: ATT_SYNTAX, value: 64 }, +]; + +fn should_not_reach() { panic!() } + +fn main() { + let flag = unsafe { + slice_get_unchecked::<&Flag, &[Flag], Flag>(FLAGS, 6) + }; + let name = flag.name as *const str as *const u8; + if unsafe { *name } != b'A' || flag.value != 64 { + should_not_reach(); + } +} +"#, + ); +} + #[test] fn unreachable_intrinsic() { check_error_with( From 7fab15a8e2b35fcb641c84bdab29e8ec9f66e90d Mon Sep 17 00:00:00 2001 From: edragain Date: Tue, 28 Jul 2026 11:57:02 +0000 Subject: [PATCH 29/63] fix: avoid escaping bound vars in closure MIR parameter types add minimal reproduce test code and use liberated closure sig to fix --- .../crates/hir-ty/src/mir/lower.rs | 6 ++-- .../crates/hir-ty/src/mir/lower/tests.rs | 28 +++++++++++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs index 1c7e5c2f51396..620d768cff422 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs @@ -53,7 +53,6 @@ use crate::{ next_solver::{ Const, DbInterner, ParamConst, ParamEnv, Region, StoredGenericArgs, StoredTy, TyKind, TypingMode, UnevaluatedConst, - abi::Safety, infer::{DbInternerInferExt, InferCtxt}, }, }; @@ -2147,11 +2146,10 @@ pub fn mir_body_for_closure_query<'db>( .store(), }); ctx.result.param_locals.push(closure_local); - - let sig = ctx.interner().signature_unclosure(substs.as_closure().sig(), Safety::Safe); + let sig = infer.closures_data[&expr].liberated_sig.get(); let resolver_guard = ctx.resolver.update_to_inner_scope(db, ctx.store_owner, expr); let current = ctx.lower_params_and_bindings( - args.iter().zip(sig.skip_binder().inputs().iter()).map(|(it, y)| (*it, *y)), + args.iter().zip(sig.inputs().iter()).map(|(it, y)| (*it, *y)), None, |_| true, )?; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/tests.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/tests.rs index 8eb0a02694ce7..fdd67fc4fb626 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/tests.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/tests.rs @@ -150,3 +150,31 @@ fn caller(path: &PathBuf) { "#, ); } + +#[test] +fn borrowck_hrtb_closure_argument_does_not_panic() { + check_borrowck( + r#" +//- minicore: fn, copy +enum Res { + Ok(T), + Err(E), +} + +struct S; + +impl S { + fn set(&mut self, _: F) + where + F: for<'a> Fn(&mut (), &'a [u8]) -> Res<(), ()>, + { + } +} + +fn main() { + let mut s = S; + s.set(|_, _| Res::Err(())); +} + "#, + ); +} From 56a1c0d458d80bae94bc6b5cc4f9b78109c5e31a Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Wed, 29 Jul 2026 22:08:42 +0300 Subject: [PATCH 30/63] Double stack size for threads to 16MiB This does not impact memory usage since this is only reserved, not committed, memory; it could crash if we allocate more than the commit limit and overcommit is disabled but for such small numbers it practically can't happen. This is an alternative to using `stacker` to grow the stack on-demand; rustc is transitioning to the same model - https://github.com/rust-lang/compiler-team/issues/1011. 16MiB was chosen because when that was chosen as the limit in rustc (https://github.com/rust-lang/rust/pull/158759), crater succeeded for practically all crates, so it should be enough for us as well. It's also two times the current number (8MiB) which is a lot. --- .../rust-analyzer/crates/rust-analyzer/src/bin/main.rs | 6 ++---- .../crates/rust-analyzer/src/cli/diagnostics.rs | 3 --- .../crates/rust-analyzer/src/cli/unresolved_references.rs | 3 --- src/tools/rust-analyzer/crates/stdx/src/thread.rs | 8 +++++++- src/tools/rust-analyzer/crates/stdx/src/thread/pool.rs | 2 -- 5 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs index 6bd27c2621978..d3b2030205aa7 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/bin/main.rs @@ -166,8 +166,6 @@ fn setup_logging(log_file_flag: Option) -> anyhow::Result<()> { Ok(()) } -const STACK_SIZE: usize = 1024 * 1024 * 8; - /// Parts of rust-analyzer can use a lot of stack space, and some operating systems only give us /// 1 MB by default (eg. Windows), so this spawns a new thread with hopefully sufficient stack /// space. @@ -176,8 +174,7 @@ fn with_extra_thread( thread_intent: stdx::thread::ThreadIntent, f: impl FnOnce() -> anyhow::Result<()> + Send + 'static, ) -> anyhow::Result<()> { - let handle = - stdx::thread::Builder::new(thread_intent, thread_name).stack_size(STACK_SIZE).spawn(f)?; + let handle = stdx::thread::Builder::new(thread_intent, thread_name).spawn(f)?; handle.join()?; @@ -189,6 +186,7 @@ fn run_server(startup_notice: Option) -> anyhow::Result<()> { rayon::ThreadPoolBuilder::new() .thread_name(|ix| format!("RayonWorker{}", ix)) + .stack_size(stdx::thread::DEFAULT_STACK_SIZE) .build_global() .unwrap(); diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/diagnostics.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/diagnostics.rs index e50e1c26bb971..8e24e0bd2ea06 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/diagnostics.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/diagnostics.rs @@ -13,13 +13,10 @@ use crate::cli::{flags, progress_report::ProgressReport}; impl flags::Diagnostics { pub fn run(self) -> anyhow::Result<()> { - const STACK_SIZE: usize = 1024 * 1024 * 8; - let handle = stdx::thread::Builder::new( stdx::thread::ThreadIntent::LatencySensitive, "BIG_STACK_THREAD", ) - .stack_size(STACK_SIZE) .spawn(|| self.run_()) .unwrap(); diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/unresolved_references.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/unresolved_references.rs index f8eacbb670587..d9a56098bd9dc 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/unresolved_references.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/unresolved_references.rs @@ -11,13 +11,10 @@ use crate::cli::flags; impl flags::UnresolvedReferences { pub fn run(self) -> anyhow::Result<()> { - const STACK_SIZE: usize = 1024 * 1024 * 8; - let handle = stdx::thread::Builder::new( stdx::thread::ThreadIntent::LatencySensitive, "BIG_STACK_THREAD", ) - .stack_size(STACK_SIZE) .spawn(|| self.run_()) .unwrap(); diff --git a/src/tools/rust-analyzer/crates/stdx/src/thread.rs b/src/tools/rust-analyzer/crates/stdx/src/thread.rs index 37b7a9f5edfa8..0000fd1954d40 100644 --- a/src/tools/rust-analyzer/crates/stdx/src/thread.rs +++ b/src/tools/rust-analyzer/crates/stdx/src/thread.rs @@ -34,6 +34,8 @@ where Builder::new(intent, name).spawn(f).expect("failed to spawn thread") } +pub const DEFAULT_STACK_SIZE: usize = 16 * 1024 * 1024; + pub struct Builder { intent: ThreadIntent, inner: jod_thread::Builder, @@ -43,7 +45,11 @@ pub struct Builder { impl Builder { #[must_use] pub fn new(intent: ThreadIntent, name: impl Into) -> Self { - Self { intent, inner: jod_thread::Builder::new().name(name.into()), allow_leak: false } + Self { + intent, + inner: jod_thread::Builder::new().name(name.into()).stack_size(DEFAULT_STACK_SIZE), + allow_leak: false, + } } #[must_use] diff --git a/src/tools/rust-analyzer/crates/stdx/src/thread/pool.rs b/src/tools/rust-analyzer/crates/stdx/src/thread/pool.rs index 918b88d960f1a..1ef6954e5a6ce 100644 --- a/src/tools/rust-analyzer/crates/stdx/src/thread/pool.rs +++ b/src/tools/rust-analyzer/crates/stdx/src/thread/pool.rs @@ -45,7 +45,6 @@ impl Pool { /// Panics if job panics #[must_use] pub fn new(threads: usize) -> Self { - const STACK_SIZE: usize = 8 * 1024 * 1024; const INITIAL_INTENT: ThreadIntent = ThreadIntent::Worker; let (job_sender, job_receiver) = crossbeam_channel::unbounded(); @@ -54,7 +53,6 @@ impl Pool { let mut handles = Vec::with_capacity(threads); for idx in 0..threads { let handle = Builder::new(INITIAL_INTENT, format!("Worker{idx}",)) - .stack_size(STACK_SIZE) .allow_leak(true) .spawn({ let extant_tasks = Arc::clone(&extant_tasks); From 97c95c6b2bc4a5e8408acfe400a1b7ecc2c328a1 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Thu, 30 Jul 2026 04:02:24 +0800 Subject: [PATCH 31/63] fix: no hint with similar name raw-ident arg Example --- **Before this PR** ```rust fn faz(r#loop: u32) {} fn main() { faz(r#loop); } //^^^^^^ r#loop ``` **After this PR** ```rust fn faz(r#loop: u32) {} fn main() { faz(r#loop); } ``` --- .../rust-analyzer/crates/ide/src/inlay_hints/param_name.rs | 4 ++++ 1 file changed, 4 insertions(+) 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 f1689e5f9dc8a..5da8f2e1624a3 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 @@ -295,6 +295,7 @@ pub(super) fn is_argument_similar_to_param_name( debug_assert!(!param_name.is_empty()); let param_name = param_name.split('_'); let argument = argument.iter().flat_map(|it| it.text_non_mutable().split('_')); + let argument = argument.map(|it| it.strip_prefix("r#").unwrap_or(it)); let prefix_match = zip(argument.clone(), param_name.clone()) .all(|(arg, param)| arg.eq_ignore_ascii_case(param)); @@ -710,9 +711,12 @@ fn main() { let param_eter2 = 0; bar(param_eter2); //^^^^^^^^^^^ param_eter + let r#loop = true; let loop_level = 0; far(loop_level); faz(loop_level); + far(r#loop); + faz(r#loop); non_ident_pat((0, 0)); From 883659d2e945a6eb44499a17da6ff6029d95516c Mon Sep 17 00:00:00 2001 From: Nico Lehmann Date: Wed, 29 Jul 2026 19:52:20 -0400 Subject: [PATCH 32/63] Revert "Remove lockfile-path support for Cargo versions below 1.94.0" This reverts commit 05fcbfe1806fdfc0ba5c333c1929cf7869ad8a7f. --- .../crates/project-model/src/build_dependencies.rs | 4 ++++ .../crates/project-model/src/cargo_config_file.rs | 13 +++++++++++++ .../crates/project-model/src/cargo_workspace.rs | 4 ++++ 3 files changed, 21 insertions(+) diff --git a/src/tools/rust-analyzer/crates/project-model/src/build_dependencies.rs b/src/tools/rust-analyzer/crates/project-model/src/build_dependencies.rs index 926a9e327e8c4..9f84f632d5e44 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/build_dependencies.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/build_dependencies.rs @@ -473,6 +473,10 @@ impl WorkspaceBuildScripts { if let Some(lockfile_copy) = &lockfile_copy { requires_unstable_options = true; match lockfile_copy.usage { + LockfileUsage::WithFlag => { + cmd.arg("--lockfile-path"); + cmd.arg(lockfile_copy.path.as_str()); + } LockfileUsage::WithEnvVarUnstable => { cmd.arg("-Zlockfile-path"); cmd.env( diff --git a/src/tools/rust-analyzer/crates/project-model/src/cargo_config_file.rs b/src/tools/rust-analyzer/crates/project-model/src/cargo_config_file.rs index a6bfea8200c53..defd9f96ab5fb 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/cargo_config_file.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/cargo_config_file.rs @@ -143,6 +143,8 @@ pub(crate) struct LockfileCopy { } pub(crate) enum LockfileUsage { + /// Rust [1.82.0, 1.95.0). `cargo --lockfile-path ` + WithFlag, /// Rust [1.95.0, 1.97.0). `CARGO_RESOLVER_LOCKFILE_PATH= cargo -Zlockfile-path ` WithEnvVarUnstable, /// Rust >= 1.97.0. `CARGO_RESOLVER_LOCKFILE_PATH= cargo ` @@ -153,6 +155,15 @@ pub(crate) fn make_lockfile_copy( toolchain_version: &semver::Version, lockfile_path: &Utf8Path, ) -> Option { + const MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH_FLAG: semver::Version = + semver::Version { + major: 1, + minor: 82, + patch: 0, + pre: semver::Prerelease::EMPTY, + build: semver::BuildMetadata::EMPTY, + }; + const MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH_ENV_UNSTABLE: semver::Version = semver::Version { major: 1, @@ -176,6 +187,8 @@ pub(crate) fn make_lockfile_copy( } else if *toolchain_version >= MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH_ENV_UNSTABLE { LockfileUsage::WithEnvVarUnstable + } else if *toolchain_version >= MINIMUM_TOOLCHAIN_VERSION_SUPPORTING_LOCKFILE_PATH_FLAG { + LockfileUsage::WithFlag } else { return None; }; 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 3db5a0fffce7f..97375fe9ddd1a 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 @@ -767,6 +767,10 @@ impl FetchMetadata { let mut using_lockfile_copy = false; if let Some(lockfile_copy) = &lockfile_copy { match lockfile_copy.usage { + LockfileUsage::WithFlag => { + other_options.push("--lockfile-path".to_owned()); + other_options.push(lockfile_copy.path.to_string()); + } LockfileUsage::WithEnvVarUnstable => { other_options.push("-Zlockfile-path".to_owned()); command.env("CARGO_RESOLVER_LOCKFILE_PATH", lockfile_copy.path.as_os_str()); From c828771fc5c7b8b1299d02cd28c51901601eef5b Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Thu, 30 Jul 2026 03:40:13 +0300 Subject: [PATCH 33/63] Support `CovariantUnsafeCell` --- .../rust-analyzer/crates/hir-def/src/signatures.rs | 7 ++++++- src/tools/rust-analyzer/crates/hir-ty/src/variance.rs | 10 +++++++++- .../rust-analyzer/crates/intern/src/symbol/symbols.rs | 1 + 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/signatures.rs b/src/tools/rust-analyzer/crates/hir-def/src/signatures.rs index 10a38ec71e372..8c097c8b0ac6b 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/signatures.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/signatures.rs @@ -52,7 +52,7 @@ pub struct StructSignature { bitflags! { #[derive(Debug, Copy, Clone, PartialEq, Eq)] - pub struct StructFlags: u8 { + pub struct StructFlags: u16 { /// Indicates whether this struct has `#[repr]`. const HAS_REPR = 1 << 0; /// Indicates whether the struct has a `#[rustc_has_incoherent_inherent_impls]` attribute. @@ -69,6 +69,8 @@ bitflags! { const IS_UNSAFE_CELL = 1 << 6; /// Indicates whether this struct is `UnsafePinned`. const IS_UNSAFE_PINNED = 1 << 7; + /// Indicates whether this struct is `CovariantUnsafeCell`. + const IS_COVARIANT_UNSAFE_CELL = 1 << 8; } } @@ -104,6 +106,9 @@ impl StructSignature { _ if lang == sym::owned_box => flags |= StructFlags::IS_BOX, _ if lang == sym::manually_drop => flags |= StructFlags::IS_MANUALLY_DROP, _ if lang == sym::unsafe_cell => flags |= StructFlags::IS_UNSAFE_CELL, + _ if lang == sym::covariant_unsafe_cell => { + flags |= StructFlags::IS_COVARIANT_UNSAFE_CELL + } _ if lang == sym::unsafe_pinned => flags |= StructFlags::IS_UNSAFE_PINNED, _ => (), } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/variance.rs b/src/tools/rust-analyzer/crates/hir-ty/src/variance.rs index 9e04353087760..2690297283988 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/variance.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/variance.rs @@ -49,7 +49,9 @@ fn variances_of_query(db: &dyn HirDatabase, def: GenericDefId) -> StoredVariance let types = || crate::next_solver::default_types(db); if flags.contains(StructFlags::IS_UNSAFE_CELL) { return types().one_invariant.store(); - } else if flags.contains(StructFlags::IS_PHANTOM_DATA) { + } else if flags.intersects( + StructFlags::IS_PHANTOM_DATA | StructFlags::IS_COVARIANT_UNSAFE_CELL, + ) { return types().one_covariant.store(); } } @@ -433,6 +435,7 @@ struct Covariant { check( r#" //- minicore: cell +#![feature(lang_items)] use core::cell::UnsafeCell; @@ -461,6 +464,10 @@ enum Enum { //~ ERROR [A: +, B: -, C: o] Bar(Contravariant),` Zed(Covariant,Contravariant) } + +#[repr(transparent)] +#[lang = "covariant_unsafe_cell"] +pub struct CovariantUnsafeCell(UnsafeCell); //~ ERROR [T: +] "#, expect![[r#" InvariantMut['a: covariant, A: invariant, B: invariant] @@ -469,6 +476,7 @@ enum Enum { //~ ERROR [A: +, B: -, C: o] Covariant[A: covariant] Contravariant[A: contravariant] Enum[A: covariant, B: contravariant, C: invariant] + CovariantUnsafeCell[T: covariant] "#]], ); } diff --git a/src/tools/rust-analyzer/crates/intern/src/symbol/symbols.rs b/src/tools/rust-analyzer/crates/intern/src/symbol/symbols.rs index fe303aa0e0c36..9a566ee687e0d 100644 --- a/src/tools/rust-analyzer/crates/intern/src/symbol/symbols.rs +++ b/src/tools/rust-analyzer/crates/intern/src/symbol/symbols.rs @@ -597,6 +597,7 @@ define_symbols! { unreachable_2021, unreachable, unsafe_cell, + covariant_unsafe_cell, unsafe_pinned, unsize, unstable, From def682350eefd030b9faa560e4d9b46ad2cca26b Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Thu, 30 Jul 2026 20:05:09 +0530 Subject: [PATCH 34/63] Add comment on replacements_are_disjoint method and better upmap syntax --- .../crates/syntax/src/syntax_editor/edit_algo.rs | 3 +++ .../crates/syntax/src/syntax_editor/mapping.rs | 7 +++---- 2 files changed, 6 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 71b03784e981e..d24d9b1334dec 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 @@ -200,6 +200,9 @@ impl EditPlan { } /// Checks that replacement at the same tree depth do not overlap + /// + /// `changes` is sorted by range start, so overlap is a single comparison against the + /// last range at that key, and `insert` can throw away the range it evicts. fn replacements_are_disjoint( changes: &[Change], mut node_depth: impl FnMut(SyntaxNode) -> usize, 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 d6498a5ec93fb..17319368afc32 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 @@ -135,12 +135,11 @@ impl SyntaxMapping { SyntaxElement::Node(node) => node.clone(), SyntaxElement::Token(token) => token.parent().unwrap(), }; - let Some(input_ancestor) = - node.ancestors().find(|ancestor| self.upmap_node_single(ancestor).is_some()) - else { + let Some((input_ancestor, output_ancestor)) = node.ancestors().find_map(|ancestor| { + self.upmap_node_single(&ancestor).map(|output_ancestor| (ancestor, output_ancestor)) + }) else { return current; }; - let output_ancestor = self.upmap_node_single(&input_ancestor).unwrap(); current = self .upmap_child_element(¤t, &input_ancestor, &output_ancestor.parent().unwrap()) .expect("the nearest mapped ancestor must map its descendants"); From 1d5bcea38afead533bd9ec2716d22d872eaf0af4 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Fri, 31 Jul 2026 02:38:19 +0300 Subject: [PATCH 35/63] Avoid having a separate query for defined opaques I want to push towards the goal of only lowering once. This helps perf, but more importantly this makes defining `AnonConst` a tracked struct instead of interned possible, as different queries won't create the same anon const. --- .../rust-analyzer/crates/hir-ty/src/db.rs | 17 +- .../rust-analyzer/crates/hir-ty/src/lib.rs | 6 +- .../rust-analyzer/crates/hir-ty/src/lower.rs | 211 +++++++----------- .../crates/hir-ty/src/opaques.rs | 25 +-- .../crates/hir-ty/src/tests/incremental.rs | 18 +- 5 files changed, 108 insertions(+), 169 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/db.rs b/src/tools/rust-analyzer/crates/hir-ty/src/db.rs index 8e7e55a77b349..9853f174eb76b 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/db.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/db.rs @@ -27,7 +27,7 @@ use crate::{ consteval::ConstEvalError, dyn_compatibility::DynCompatibilityViolation, layout::{Layout, LayoutError}, - lower::{GenericDefaults, TrackedStructToken, TypeAliasBounds}, + lower::{GenericDefaults, TrackedStructToken, TypeAliasBounds, WithDefinedOpaques}, mir::{MirBody, MirLowerError}, next_solver::{ Allocation, Clause, EarlyBinder, GenericArgs, ParamEnv, PolyFnSig, StoredClauses, @@ -172,7 +172,7 @@ pub trait HirDatabase: SourceDatabase + 'static { fn type_for_type_alias_with_diagnostics<'db>( &'db self, def: TypeAliasId, - ) -> &'db TyLoweringResult<'db, StoredEarlyBinder> { + ) -> &'db TyLoweringResult<'db, WithDefinedOpaques>> { let db = self.as_dyn(); crate::lower::type_for_type_alias_with_diagnostics(db, def) } @@ -275,12 +275,12 @@ pub trait HirDatabase: SourceDatabase + 'static { crate::lower::callable_item_signature(db, def) } - fn callable_item_signature_with_diagnostics<'db>( + fn fn_sig_for_fn_with_diagnostics<'db>( &'db self, - def: CallableDefId, - ) -> &'db TyLoweringResult<'db, StoredEarlyBinder> { + def: FunctionId, + ) -> &'db TyLoweringResult<'db, WithDefinedOpaques>> { let db = self.as_dyn(); - crate::lower::callable_item_signature_with_diagnostics(db, def) + crate::lower::fn_sig_for_fn(db, def) } fn trait_environment<'db>(&'db self, def: GenericDefId) -> ParamEnv<'db> { @@ -513,8 +513,9 @@ impl<'db> AnonConstId<'db> { result.push(db.type_for_type_alias_with_diagnostics(id).defined_anon_consts()); result.push(db.type_alias_bounds_with_diagnostics(id).defined_anon_consts()); } - GenericDefId::FunctionId(id) => result - .push(db.callable_item_signature_with_diagnostics(id.into()).defined_anon_consts()), + GenericDefId::FunctionId(id) => { + result.push(db.fn_sig_for_fn_with_diagnostics(id).defined_anon_consts()) + } GenericDefId::ConstId(def) => { result.push(db.type_for_const_with_diagnostics(def).defined_anon_consts()) } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs index 0dd558828fd7f..afd500a3e3343 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs @@ -111,9 +111,9 @@ pub use infer::{ infer_query_with_inspect, }; pub use lower::{ - FieldType, GenericDefaults, GenericDefaultsRef, GenericPredicates, ImplTraits, - LifetimeElisionKind, LifetimeLoweringMode, LoweringMode, TyDefId, TyLoweringContext, - TyLoweringInferVarsCtx, TyLoweringResult, ValueTyDefId, diagnostics::*, + FieldType, GenericDefaults, GenericDefaultsRef, GenericPredicates, LifetimeElisionKind, + LifetimeLoweringMode, LoweringMode, TyDefId, TyLoweringContext, TyLoweringInferVarsCtx, + TyLoweringResult, ValueTyDefId, diagnostics::*, }; pub use next_solver::interner::{attach_db, attach_db_allow_change, with_attached_db}; pub use target_feature::TargetFeatures; 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 bdb882b70b259..098a43a876a7e 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs @@ -10,7 +10,6 @@ pub(crate) mod path; use std::{cell::OnceCell, iter, mem, sync::OnceLock}; -use base_db::salsa::update_fallback_db; use either::Either; use hir_def::{ AdtId, AssocItemId, CallableDefId, ConstId, ConstParamId, EnumId, EnumVariantId, @@ -80,13 +79,14 @@ use crate::{ pub(crate) struct PathDiagnosticCallbackData(pub(crate) TypeRefId); #[derive(PartialEq, Eq, Debug, Hash)] -pub struct ImplTraits { - pub(crate) impl_traits: Arena, +pub struct WithDefinedOpaques { + value: T, + impl_traits: Option>>, } #[derive(PartialEq, Eq, Debug, Hash)] pub struct ImplTrait { - pub(crate) predicates: StoredClauses, + pub(crate) predicates: StoredEarlyBinder, pub(crate) assoc_ty_bounds_start: u32, } @@ -417,6 +417,15 @@ impl<'db, 'a> TyLoweringContext<'db, 'a> { BoundVarKinds::new_from_iter(interner, args) } + + fn take_defined_opaques(&mut self) -> Option>> { + if self.impl_trait_mode.opaque_type_data.is_empty() { + None + } else { + self.impl_trait_mode.opaque_type_data.shrink_to_fit(); + Some(Box::new(mem::take(&mut self.impl_trait_mode.opaque_type_data))) + } + } } #[derive(Copy, Clone, Debug, PartialEq, Eq, Default)] @@ -631,7 +640,7 @@ impl<'db, 'a> TyLoweringContext<'db, 'a> { // place even if we encounter more opaque types while // lowering the bounds let idx = self.impl_trait_mode.opaque_type_data.alloc(ImplTrait { - predicates: Clauses::empty(interner).store(), + predicates: StoredEarlyBinder::bind(Clauses::empty(interner).store()), assoc_ty_bounds_start: 0, }); @@ -1319,7 +1328,7 @@ impl<'db, 'a> TyLoweringContext<'db, 'a> { self.is_lowering_impl_trait_bounds = prev_is_lowering_impl_trait_bounds; ImplTrait { - predicates: Clauses::new_from_slice(&predicates).store(), + predicates: StoredEarlyBinder::bind(Clauses::new_from_slice(&predicates).store()), assoc_ty_bounds_start, } } @@ -1365,7 +1374,6 @@ impl<'db, 'a> TyLoweringContext<'db, 'a> { pub struct TyLoweringResult<'db, T> { #[update(fallback)] pub value: T, - #[update(bounds(TyLoweringResultInfo<'db>: Update), unsafe(with(update_fallback_db::<'db, _>)))] info: Option>>, } @@ -1497,19 +1505,21 @@ pub(crate) fn impl_trait_with_diagnostics_cycle_result<'db>( impl ImplTraitId { #[inline] - pub fn predicates<'db>(self, db: &'db dyn HirDatabase) -> EarlyBinder<'db, &'db [Clause<'db>]> { + fn data(self, db: &dyn HirDatabase) -> &ImplTrait { let (impl_traits, idx) = match self { ImplTraitId::ReturnTypeImplTrait(owner, idx) => { - (ImplTraits::return_type_impl_traits(db, owner), idx) + (ImplTrait::return_type_impl_traits(db, owner), idx) } ImplTraitId::TypeAliasImplTrait(owner, idx) => { - (ImplTraits::type_alias_impl_traits(db, owner), idx) + (ImplTrait::type_alias_impl_traits(db, owner), idx) } }; - impl_traits - .as_deref() - .expect("owner should have opaque type") - .get_with(|it| it.impl_traits[idx].predicates.as_ref().as_slice()) + &impl_traits[idx] + } + + #[inline] + pub fn predicates<'db>(self, db: &'db dyn HirDatabase) -> EarlyBinder<'db, &'db [Clause<'db>]> { + self.data(db).predicates.get().map_bound(|it| it.as_slice()) } #[inline] @@ -1517,24 +1527,8 @@ impl ImplTraitId { self, db: &'db dyn HirDatabase, ) -> EarlyBinder<'db, &'db [Clause<'db>]> { - let (impl_traits, idx) = match self { - ImplTraitId::ReturnTypeImplTrait(owner, idx) => { - (ImplTraits::return_type_impl_traits(db, owner), idx) - } - ImplTraitId::TypeAliasImplTrait(owner, idx) => { - (ImplTraits::type_alias_impl_traits(db, owner), idx) - } - }; - let predicates = - impl_traits.as_deref().expect("owner should have opaque type").get_with(|it| { - let impl_trait = &it.impl_traits[idx]; - ( - impl_trait.predicates.as_ref().as_slice(), - impl_trait.assoc_ty_bounds_start as usize, - ) - }); - - predicates.map_bound(|(preds, len)| &preds[..len]) + let data = self.data(db); + data.predicates.get().map_bound(|it| &it.as_slice()[..data.assoc_ty_bounds_start as usize]) } } @@ -1553,71 +1547,25 @@ impl InternedOpaqueTyId<'_> { } } -#[salsa::tracked] -impl ImplTraits { - #[salsa::tracked(returns(ref))] +impl ImplTrait { + #[inline] pub(crate) fn return_type_impl_traits( db: &dyn HirDatabase, - def: hir_def::FunctionId, - ) -> Option>> { - // FIXME unify with fn_sig_for_fn instead of doing lowering twice, maybe - let data = FunctionSignature::of(db, def); - let resolver = def.resolver(db); - let generics = OnceCell::new(); - let mut ctx_ret = TyLoweringContext::new( - db, - &resolver, - &data.store, - ExpressionStoreOwnerId::Signature(def.into()), - def.into(), - &generics, - LifetimeElisionKind::Infer, - LifetimeLoweringMode::Bound, - ) - .with_impl_trait_mode(ImplTraitLoweringMode::Opaque); - if let Some(ret_type) = data.ret_type { - let _ret = ctx_ret.lower_ty(ret_type); - } - let mut return_type_impl_traits = - ImplTraits { impl_traits: ctx_ret.impl_trait_mode.opaque_type_data }; - if return_type_impl_traits.impl_traits.is_empty() { - None - } else { - return_type_impl_traits.impl_traits.shrink_to_fit(); - Some(Box::new(StoredEarlyBinder::bind(return_type_impl_traits))) - } + def: FunctionId, + ) -> &Arena { + fn_sig_for_fn(db, def).value.impl_traits.as_deref().unwrap_or(const { &Arena::new() }) } - #[salsa::tracked(returns(ref))] + #[inline] pub(crate) fn type_alias_impl_traits( db: &dyn HirDatabase, - def: hir_def::TypeAliasId, - ) -> Option>> { - let data = TypeAliasSignature::of(db, def); - let resolver = def.resolver(db); - let generics = OnceCell::new(); - let mut ctx = TyLoweringContext::new( - db, - &resolver, - &data.store, - ExpressionStoreOwnerId::Signature(def.into()), - def.into(), - &generics, - LifetimeElisionKind::AnonymousReportError, - LifetimeLoweringMode::Bound, - ) - .with_impl_trait_mode(ImplTraitLoweringMode::Opaque); - if let Some(type_ref) = data.ty { - let _ty = ctx.lower_ty(type_ref); - } - let mut type_alias_impl_traits = - ImplTraits { impl_traits: ctx.impl_trait_mode.opaque_type_data }; - if type_alias_impl_traits.impl_traits.is_empty() { - None - } else { - type_alias_impl_traits.impl_traits.shrink_to_fit(); - Some(Box::new(StoredEarlyBinder::bind(type_alias_impl_traits))) - } + def: TypeAliasId, + ) -> &Arena { + type_for_type_alias_with_diagnostics(db, def) + .value + .impl_traits + .as_deref() + .unwrap_or(const { &Arena::new() }) } } @@ -1666,7 +1614,7 @@ pub(crate) fn ty_query<'db>(db: &'db dyn HirDatabase, def: TyDefId) -> EarlyBind it, GenericArgs::identity_for_item(interner, it.into()), )), - TyDefId::TypeAliasId(it) => db.type_for_type_alias_with_diagnostics(it).value.get(), + TyDefId::TypeAliasId(it) => db.type_for_type_alias_with_diagnostics(it).value.value.get(), } } @@ -1804,13 +1752,14 @@ pub(crate) fn value_ty<'db>( pub(crate) fn type_for_type_alias_with_diagnostics<'db>( db: &'db dyn HirDatabase, t: TypeAliasId, -) -> TyLoweringResult<'db, StoredEarlyBinder> { +) -> TyLoweringResult<'db, WithDefinedOpaques>> { let type_alias_data = TypeAliasSignature::of(db, t); let interner = DbInterner::new_no_crate(db); if type_alias_data.flags.contains(TypeAliasFlags::IS_EXTERN) { - TyLoweringResult::empty(StoredEarlyBinder::bind( - Ty::new_foreign(interner, t.into()).store(), - )) + TyLoweringResult::empty(WithDefinedOpaques { + value: StoredEarlyBinder::bind(Ty::new_foreign(interner, t.into()).store()), + impl_traits: None, + }) } else { let resolver = t.resolver(db); let generics = OnceCell::new(); @@ -1832,7 +1781,10 @@ pub(crate) fn type_for_type_alias_with_diagnostics<'db>( .unwrap_or_else(|| Ty::new_error(interner, ErrorGuaranteed)) .store(), ); - TyLoweringResult::from_ctx(res, ctx) + TyLoweringResult::from_ctx( + WithDefinedOpaques { value: res, impl_traits: ctx.take_defined_opaques() }, + ctx, + ) } } @@ -1840,10 +1792,13 @@ pub(crate) fn type_for_type_alias_with_diagnostics_cycle_result<'db>( db: &'db dyn HirDatabase, _: salsa::Id, _adt: TypeAliasId, -) -> TyLoweringResult<'db, StoredEarlyBinder> { - TyLoweringResult::empty(StoredEarlyBinder::bind( - Ty::new_error(DbInterner::new_no_crate(db), ErrorGuaranteed).store(), - )) +) -> TyLoweringResult<'db, WithDefinedOpaques>> { + TyLoweringResult::empty(WithDefinedOpaques { + value: StoredEarlyBinder::bind( + Ty::new_error(DbInterner::new_no_crate(db), ErrorGuaranteed).store(), + ), + impl_traits: None, + }) } pub(crate) fn impl_self_ty_query<'db>( @@ -2822,27 +2777,18 @@ pub(crate) fn callable_item_signature<'db>( db: &'db dyn HirDatabase, def: CallableDefId, ) -> EarlyBinder<'db, PolyFnSig<'db>> { - callable_item_signature_with_diagnostics(db, def).value.get() -} - -#[salsa::tracked(returns(ref))] -pub(crate) fn callable_item_signature_with_diagnostics<'db>( - db: &'db dyn HirDatabase, - def: CallableDefId, -) -> TyLoweringResult<'db, StoredEarlyBinder> { match def { - CallableDefId::FunctionId(f) => fn_sig_for_fn(db, f), - CallableDefId::StructId(s) => TyLoweringResult::empty(fn_sig_for_struct_constructor(db, s)), - CallableDefId::EnumVariantId(e) => { - TyLoweringResult::empty(fn_sig_for_enum_variant_constructor(db, e)) - } + CallableDefId::FunctionId(f) => fn_sig_for_fn(db, f).value.value.get(), + CallableDefId::StructId(s) => fn_sig_for_struct_constructor(db, s).get(), + CallableDefId::EnumVariantId(e) => fn_sig_for_enum_variant_constructor(db, e).get(), } } -fn fn_sig_for_fn<'db>( +#[salsa::tracked(returns(ref))] +pub(crate) fn fn_sig_for_fn<'db>( db: &'db dyn HirDatabase, def: FunctionId, -) -> TyLoweringResult<'db, StoredEarlyBinder> { +) -> TyLoweringResult<'db, WithDefinedOpaques>> { let data = FunctionSignature::of(db, def); let resolver = def.resolver(db); let interner = DbInterner::new_no_crate(db); @@ -2874,6 +2820,7 @@ fn fn_sig_for_fn<'db>( Some(ret_type) => ctx_ret.lower_ty(ret_type), None => Ty::new_unit(interner), }; + let impl_traits = ctx_ret.take_defined_opaques(); let inputs_and_output = Tys::new_from_iter(interner, params.chain(Some(ret))); ctx_params.diagnostics.extend(ctx_ret.diagnostics); @@ -2891,7 +2838,7 @@ fn fn_sig_for_fn<'db>( }, binder, ))); - TyLoweringResult::from_ctx(result, ctx_params) + TyLoweringResult::from_ctx(WithDefinedOpaques { value: result, impl_traits }, ctx_params) } fn type_for_adt<'db>(db: &'db dyn HirDatabase, adt: AdtId) -> EarlyBinder<'db, Ty<'db>> { @@ -2901,13 +2848,14 @@ fn type_for_adt<'db>(db: &'db dyn HirDatabase, adt: AdtId) -> EarlyBinder<'db, T EarlyBinder::bind(ty) } -fn fn_sig_for_struct_constructor( +fn ctor_signature( db: &dyn HirDatabase, - def: StructId, + variant: VariantId, + adt: AdtId, ) -> StoredEarlyBinder { - let field_tys = db.field_types(def.into()); + let field_tys = db.field_types(variant); let params = field_tys.iter().map(|(_, field)| field.ty().skip_binder()); - let ret = type_for_adt(db, def.into()).skip_binder(); + let ret = type_for_adt(db, adt).skip_binder(); let inputs_and_output = Tys::new_from_iter(DbInterner::new_no_crate(db), params.chain(Some(ret))); @@ -2917,21 +2865,20 @@ fn fn_sig_for_struct_constructor( }))) } +#[salsa::tracked(returns(ref))] +fn fn_sig_for_struct_constructor( + db: &dyn HirDatabase, + def: StructId, +) -> StoredEarlyBinder { + ctor_signature(db, def.into(), def.into()) +} + +#[salsa::tracked(returns(ref))] fn fn_sig_for_enum_variant_constructor( db: &dyn HirDatabase, def: EnumVariantId, ) -> StoredEarlyBinder { - let field_tys = db.field_types(def.into()); - let params = field_tys.iter().map(|(_, field)| field.ty().skip_binder()); - let parent = def.lookup(db).parent; - let ret = type_for_adt(db, parent.into()).skip_binder(); - - let inputs_and_output = - Tys::new_from_iter(DbInterner::new_no_crate(db), params.chain(Some(ret))); - StoredEarlyBinder::bind(StoredPolyFnSig::new(Binder::dummy(FnSig { - fn_sig_kind: FnSigKind::new(ExternAbi::Rust, Safety::Safe, false), - inputs_and_output, - }))) + ctor_signature(db, def.into(), def.lookup(db).parent.into()) } // FIXME: Remove this. diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/opaques.rs b/src/tools/rust-analyzer/crates/hir-ty/src/opaques.rs index 9cb0022ca6bfc..194c866c68056 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/opaques.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/opaques.rs @@ -5,14 +5,14 @@ use hir_def::{ signatures::ImplSignature, }; use hir_expand::name::Name; -use la_arena::ArenaMap; +use la_arena::{Arena, ArenaMap}; use rustc_type_ir::inherent::Ty as _; use syntax::ast; use crate::{ ImplTraitId, InferBodyId, InferenceResult, db::{HirDatabase, InternedOpaqueTyId}, - lower::{ImplTraitIdx, ImplTraits}, + lower::{ImplTrait, ImplTraitIdx}, next_solver::{ DbInterner, ErrorGuaranteed, SolverDefId, StoredEarlyBinder, StoredTy, Ty, TypingMode, infer::{DbInternerInferExt, traits::ObligationCause}, @@ -29,7 +29,7 @@ pub(crate) fn opaque_types_defined_by<'db>( // A function may define its own RPITs. extend_with_opaques( db, - ImplTraits::return_type_impl_traits(db, func), + ImplTrait::return_type_impl_traits(db, func), |opaque_idx| ImplTraitId::ReturnTypeImplTrait(func, opaque_idx), result, ); @@ -38,7 +38,7 @@ pub(crate) fn opaque_types_defined_by<'db>( let extend_with_taits = |type_alias| { extend_with_opaques( db, - ImplTraits::type_alias_impl_traits(db, type_alias), + ImplTrait::type_alias_impl_traits(db, type_alias), |opaque_idx| ImplTraitId::TypeAliasImplTrait(type_alias, opaque_idx), result, ); @@ -81,15 +81,13 @@ pub(crate) fn opaque_types_defined_by<'db>( fn extend_with_opaques<'db>( db: &'db dyn HirDatabase, - opaques: &Option>>, + opaques: &Arena, mut make_impl_trait: impl FnMut(ImplTraitIdx) -> ImplTraitId, result: &mut Vec>, ) { - if let Some(opaques) = opaques { - for (opaque_idx, _) in (**opaques).as_ref().skip_binder().impl_traits.iter() { - let opaque_id = InternedOpaqueTyId::new(db, make_impl_trait(opaque_idx)); - result.push(opaque_id.into()); - } + for (opaque_idx, _) in opaques.iter() { + let opaque_id = InternedOpaqueTyId::new(db, make_impl_trait(opaque_idx)); + result.push(opaque_id.into()); } } } @@ -116,12 +114,7 @@ pub(crate) fn tait_hidden_types( type_alias: TypeAliasId, ) -> ArenaMap> { // Call this first, to not perform redundant work if there are no TAITs. - let Some(taits_count) = ImplTraits::type_alias_impl_traits(db, type_alias) - .as_deref() - .map(|taits| taits.as_ref().skip_binder().impl_traits.len()) - else { - return ArenaMap::new(); - }; + let taits_count = ImplTrait::type_alias_impl_traits(db, type_alias).len(); let loc = type_alias.loc(db); let module = loc.module(db); 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 08efef10ee78a..4574e095e91f1 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 @@ -47,7 +47,7 @@ fn foo() -> i32 { "lang_items", "crate_lang_items", "GenericPredicates::query_with_diagnostics_", - "ImplTraits::return_type_impl_traits_", + "fn_sig_for_fn", "ExprScopes::body_expr_scopes_", "body_upvars_mentioned", ] @@ -136,7 +136,7 @@ fn baz() -> i32 { "lang_items", "crate_lang_items", "GenericPredicates::query_with_diagnostics_", - "ImplTraits::return_type_impl_traits_", + "fn_sig_for_fn", "ExprScopes::body_expr_scopes_", "body_upvars_mentioned", "InferenceResult < 'db >::for_body_", @@ -147,7 +147,7 @@ fn baz() -> i32 { "Body::with_source_map_", "trait_environment_query", "GenericPredicates::query_with_diagnostics_", - "ImplTraits::return_type_impl_traits_", + "fn_sig_for_fn", "ExprScopes::body_expr_scopes_", "body_upvars_mentioned", "InferenceResult < 'db >::for_body_", @@ -158,7 +158,7 @@ fn baz() -> i32 { "Body::with_source_map_", "trait_environment_query", "GenericPredicates::query_with_diagnostics_", - "ImplTraits::return_type_impl_traits_", + "fn_sig_for_fn", "ExprScopes::body_expr_scopes_", "body_upvars_mentioned", ] @@ -599,21 +599,20 @@ fn main() { "crate_lang_items", "GenericPredicates::query_with_diagnostics_", "GenericPredicates::query_with_diagnostics_", - "ImplTraits::return_type_impl_traits_", + "fn_sig_for_fn", "body_upvars_mentioned", "InferenceResult < 'db >::for_body_", "FunctionSignature::of_", "FunctionSignature::with_source_map_", "trait_environment_query", "GenericPredicates::query_with_diagnostics_", - "ImplTraits::return_type_impl_traits_", + "fn_sig_for_fn", "ExprScopes::body_expr_scopes_", "StructSignature::of_", "StructSignature::with_source_map_", "AttrFlags::query_", "GenericPredicates::query_with_diagnostics_", "InherentImpls < 'db >::for_crate_", - "callable_item_signature_with_diagnostics", "TraitImpls < 'db >::for_crate_and_deps_", "TraitImpls < 'db >::for_crate_", "impl_trait_with_diagnostics", @@ -692,18 +691,17 @@ fn main() { "crate_lang_items", "GenericPredicates::query_with_diagnostics_", "GenericPredicates::query_with_diagnostics_", - "ImplTraits::return_type_impl_traits_", + "fn_sig_for_fn", "body_upvars_mentioned", "InferenceResult < 'db >::for_body_", "FunctionSignature::with_source_map_", "GenericPredicates::query_with_diagnostics_", - "ImplTraits::return_type_impl_traits_", + "fn_sig_for_fn", "ExprScopes::body_expr_scopes_", "StructSignature::with_source_map_", "AttrFlags::query_", "GenericPredicates::query_with_diagnostics_", "InherentImpls < 'db >::for_crate_", - "callable_item_signature_with_diagnostics", "TraitImpls < 'db >::for_crate_", "ImplSignature::with_source_map_", "ImplSignature::of_", From 735a39d7292785fbde8668eceaf8f86298846b49 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Fri, 31 Jul 2026 01:19:30 +0300 Subject: [PATCH 36/63] Do not alloc anon consts for bare paths in blocks --- .../crates/hir-ty/src/consteval.rs | 17 ++++++--- .../rust-analyzer/crates/hir-ty/src/infer.rs | 3 -- .../crates/hir-ty/src/tests/regression.rs | 35 +++++++++++++++++++ .../crates/hir-ty/src/tests/simple.rs | 7 ++-- .../crates/ide/src/hover/tests.rs | 2 +- 5 files changed, 51 insertions(+), 13 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/consteval.rs b/src/tools/rust-analyzer/crates/hir-ty/src/consteval.rs index 15dd530312067..7aa6604ac09b8 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/consteval.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/consteval.rs @@ -361,7 +361,7 @@ pub(crate) fn create_anon_const<'a, 'db>( interner: DbInterner<'db>, owner: ExpressionStoreOwnerId, store: &ExpressionStore, - expr: ExprId, + expr_id: ExprId, resolver: &Resolver<'db>, expected_ty: Ty<'db>, generics: &dyn Fn() -> &'a Generics<'db>, @@ -369,10 +369,19 @@ pub(crate) fn create_anon_const<'a, 'db>( lowering_mode: LoweringMode, forbid_params_after: Option, ) -> Result, CreateConstError<'db>> { - match &store[expr] { + let mut expr = &store[expr_id]; + if let Expr::Block { statements, tail: Some(tail), .. } = expr + && statements.is_empty() + { + // rustc unwraps *one* layer of blocks, so we do too (this impacts whether the const can use generic parameters. + // Anon consts sometimes cannot while bare paths can). mGCA allows arbitrarily many blocks, but we don't implement + // it yet. + expr = &store[*tail]; + } + match expr { Expr::Literal(literal) => intern_const_ref(interner, literal, expected_ty), Expr::Underscore => match create_var { - Some(create_var) => Ok(create_var(expr.into())), + Some(create_var) => Ok(create_var(expr_id.into())), None => Err(CreateConstError::UnderscoreExpr), }, Expr::Path(path) @@ -395,7 +404,7 @@ pub(crate) fn create_anon_const<'a, 'db>( interner.db, AnonConstLoc { owner, - expr, + expr: expr_id, ty: StoredEarlyBinder::bind(expected_ty.store()), allow_using_generic_params, }, 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 a5a82209c6030..319a8ae9bd7fc 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs @@ -1085,10 +1085,7 @@ impl<'db> InferenceResult<'db> { fn for_body(db: &dyn HirDatabase, def: DefWithBodyId) -> InferenceResult<'_> { infer_query(db, def) } -} -#[salsa::tracked] -impl<'db> InferenceResult<'db> { /// Infer types for all const expressions in an item's signature. /// /// Returns an `InferenceResult` containing type information for array lengths, 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 2836f977a9ac5..683c938fb5a25 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 @@ -3028,3 +3028,38 @@ fn f() { "#, ); } + +#[test] +fn braced_const_path() { + check_types( + r#" +//- minicore: default, builtin_impls +trait ToNum { + type Num; +} +trait Bar { + type Ty; +} +struct Gen; +struct Int; + +impl ToNum for Gen<{ B }> { + type Num = Int; +} + +impl Bar for Int { + type Ty = i32; +} +impl Bar for Int { + type Ty = f32; +} + +type A = < as ToNum>::Num as Bar>::Ty; + +fn main() { + let x = A::default(); + // ^ i32 +} + "#, + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs index e8f378db3228a..3cdfe4edcb908 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs @@ -4201,8 +4201,6 @@ fn foo() { 248..282 'LazyLo..._LOCK)': &'? [u32; 0] 264..281 '&VALUE...Y_LOCK': &'? LazyLock<[u32; 0]> 265..281 'VALUES...Y_LOCK': LazyLock<[u32; 0]> - 197..202 '{ 0 }': usize - 199..200 '0': usize "#]], ); } @@ -4308,9 +4306,8 @@ enum Enum { } "#, expect![[r#" - 29..34 '{ 2 }': usize - 31..32 '2': usize - "#]], + +"#]], ); } 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 89f1cf2fc1e11..f4335e227ff62 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs @@ -9508,7 +9508,7 @@ pub fn f(x$0: impl Tr<{ 0 }>) {} *x* ```rust - x: impl Tr<{const}> + ?Sized + x: impl Tr<0> + ?Sized ``` --- From 13b4a5ecf406f7e28da8901ef9f0775f3775ecbd Mon Sep 17 00:00:00 2001 From: Tyler Breisacher Date: Thu, 30 Jul 2026 17:35:01 -0700 Subject: [PATCH 37/63] Use format! instead of string --- src/tools/rust-analyzer/crates/rust-analyzer/src/command.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/command.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/command.rs index bc3fa21c6658f..a79e78f3377b8 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/command.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/command.rs @@ -174,7 +174,7 @@ impl CommandHandle { let mut child = child .spawn() .map(JodGroupChild) - .with_context(|| "Failed to spawn command: {child:?}")?; + .with_context(|| format!("Failed to spawn command: {child:?}"))?; let stdout = child.0.stdout().take().unwrap(); let stderr = child.0.stderr().take().unwrap(); From 036c57f1b2d74d4372369acb6e0ed6bb5210d4c5 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 31 Jul 2026 07:35:31 +0530 Subject: [PATCH 38/63] Remove add_tabstop_after_token from source change --- .../rust-analyzer/crates/ide-db/src/source_change.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) 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 540b0ee99dd21..25d8ae097182b 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 @@ -386,13 +386,7 @@ impl SourceChangeBuilder { assert!(token.parent().is_some()); self.add_snippet(PlaceSnippet::Before(token.into())); } - - /// Adds a tabstop snippet to place the cursor after `token` - pub fn add_tabstop_after_token(&mut self, _cap: SnippetCap, token: SyntaxToken) { - assert!(token.parent().is_some()); - self.add_snippet(PlaceSnippet::After(token.into())); - } - + /// Adds a snippet to move the cursor selected over `node` pub fn add_placeholder_snippet(&mut self, _cap: SnippetCap, node: impl AstNode) { assert!(node.syntax().parent().is_some()); From 5030272f2d0a37bccb7ff71f70752e7677c4e074 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 31 Jul 2026 07:38:04 +0530 Subject: [PATCH 39/63] Remove add_tabstop_before_token and adapt generate_derive assist --- .../ide-assists/src/handlers/generate_derive.rs | 11 +++++------ .../rust-analyzer/crates/ide-db/src/source_change.rs | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_derive.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_derive.rs index f293e956bca5b..ba6bb5c70bcb1 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_derive.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_derive.rs @@ -42,9 +42,9 @@ pub(crate) fn generate_derive(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> }; acc.add(AssistId::generate("generate_derive"), "Add `#[derive]`", target, |edit| { + let editor = edit.make_editor(nominal.syntax()); match derive_attr { None => { - let editor = edit.make_editor(nominal.syntax()); let make = editor.make(); let derive = make.attr_outer(make.meta_token_tree( @@ -79,16 +79,15 @@ pub(crate) fn generate_derive(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> let tabstop_before = edit.make_tabstop_before(cap); editor.add_annotation(delimiter, tabstop_before); - edit.add_file_edits(ctx.vfs_file_id(), editor); } Some(_) => { + let delimiter = delimiter.expect("Right delim token could not be found."); + let tabstop_before = edit.make_tabstop_before(cap); // Just move the cursor. - edit.add_tabstop_before_token( - cap, - delimiter.expect("Right delim token could not be found."), - ); + editor.add_annotation(delimiter, tabstop_before); } }; + edit.add_file_edits(ctx.vfs_file_id(), editor); }) } 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 25d8ae097182b..553aa3ae805bd 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 @@ -386,7 +386,7 @@ impl SourceChangeBuilder { assert!(token.parent().is_some()); self.add_snippet(PlaceSnippet::Before(token.into())); } - + /// Adds a snippet to move the cursor selected over `node` pub fn add_placeholder_snippet(&mut self, _cap: SnippetCap, node: impl AstNode) { assert!(node.syntax().parent().is_some()); From e761d0243ef9a2f2a6fac5c564fabff54ddf2bb8 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 31 Jul 2026 08:01:17 +0530 Subject: [PATCH 40/63] Remove add_tabstop_before_token fomr source_change --- src/tools/rust-analyzer/crates/ide-db/src/source_change.rs | 6 ------ 1 file changed, 6 deletions(-) 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 553aa3ae805bd..a62087fb6a018 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 @@ -381,12 +381,6 @@ impl SourceChangeBuilder { self.add_snippet(PlaceSnippet::Before(node.syntax().clone().into())); } - /// Adds a tabstop snippet to place the cursor before `token` - pub fn add_tabstop_before_token(&mut self, _cap: SnippetCap, token: SyntaxToken) { - assert!(token.parent().is_some()); - self.add_snippet(PlaceSnippet::Before(token.into())); - } - /// Adds a snippet to move the cursor selected over `node` pub fn add_placeholder_snippet(&mut self, _cap: SnippetCap, node: impl AstNode) { assert!(node.syntax().parent().is_some()); From 06917be3b3a1d1a6b3ae57ab583a2934b62c212a Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 31 Jul 2026 08:02:00 +0530 Subject: [PATCH 41/63] Remove add_placeholder_snippet fomr source_change --- src/tools/rust-analyzer/crates/ide-db/src/source_change.rs | 6 ------ 1 file changed, 6 deletions(-) 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 a62087fb6a018..878d6566dd42f 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 @@ -381,12 +381,6 @@ impl SourceChangeBuilder { self.add_snippet(PlaceSnippet::Before(node.syntax().clone().into())); } - /// Adds a snippet to move the cursor selected over `node` - pub fn add_placeholder_snippet(&mut self, _cap: SnippetCap, node: impl AstNode) { - assert!(node.syntax().parent().is_some()); - self.add_snippet(PlaceSnippet::Over(node.syntax().clone().into())) - } - fn add_snippet(&mut self, snippet: PlaceSnippet) { let snippet_builder = self.snippet_builder.get_or_insert(SnippetBuilder { places: vec![] }); snippet_builder.places.push(snippet); From 5a6c923d42eb56b3855097a6aecc510830c1b106 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 31 Jul 2026 08:02:40 +0530 Subject: [PATCH 42/63] Remove add_tabstop_before from source_change --- src/tools/rust-analyzer/crates/ide-db/src/source_change.rs | 6 ------ 1 file changed, 6 deletions(-) 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 878d6566dd42f..34112cd1b1e89 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 @@ -375,12 +375,6 @@ impl SourceChangeBuilder { self.command = Some(Command::Rename); } - /// Adds a tabstop snippet to place the cursor before `node` - pub fn add_tabstop_before(&mut self, _cap: SnippetCap, node: impl AstNode) { - assert!(node.syntax().parent().is_some()); - self.add_snippet(PlaceSnippet::Before(node.syntax().clone().into())); - } - fn add_snippet(&mut self, snippet: PlaceSnippet) { let snippet_builder = self.snippet_builder.get_or_insert(SnippetBuilder { places: vec![] }); snippet_builder.places.push(snippet); From f5601fac7e67377080b5c94870d0d17f85bc317f Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 31 Jul 2026 08:03:05 +0530 Subject: [PATCH 43/63] Remove add_snippet from source change --- src/tools/rust-analyzer/crates/ide-db/src/source_change.rs | 6 ------ 1 file changed, 6 deletions(-) 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 34112cd1b1e89..8118112d2bf92 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 @@ -375,12 +375,6 @@ impl SourceChangeBuilder { self.command = Some(Command::Rename); } - fn add_snippet(&mut self, snippet: PlaceSnippet) { - let snippet_builder = self.snippet_builder.get_or_insert(SnippetBuilder { places: vec![] }); - snippet_builder.places.push(snippet); - self.source_change.is_snippet = true; - } - fn add_snippet_annotation(&mut self, kind: AnnotationSnippet) -> SyntaxAnnotation { let annotation = SyntaxAnnotation::default(); self.snippet_annotations.push((kind, annotation)); From 72a9c717c25261a6f297a002f4465c4bdf31f67f Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 31 Jul 2026 08:11:48 +0530 Subject: [PATCH 44/63] removed PlaceSnippet and its unused SnippetBuilder --- .../crates/ide-db/src/source_change.rs | 41 ++----------------- 1 file changed, 3 insertions(+), 38 deletions(-) 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 8118112d2bf92..d403cf6e0c926 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 @@ -15,7 +15,7 @@ use rustc_hash::FxHashMap; use span::FileId; use stdx::never; use syntax::{ - AstNode, SyntaxElement, SyntaxNode, SyntaxToken, TextRange, TextSize, + AstNode, SyntaxNode, TextRange, TextSize, syntax_editor::{SyntaxAnnotation, SyntaxEditor}, }; @@ -231,15 +231,6 @@ pub struct SourceChangeBuilder { pub file_editors: FxHashMap, /// Keeps track of which annotations correspond to which snippets pub snippet_annotations: Vec<(AnnotationSnippet, SyntaxAnnotation)>, - - /// Keeps track of where to place snippets - pub snippet_builder: Option, -} - -#[derive(Default)] -pub struct SnippetBuilder { - /// Where to place snippets at - places: Vec, } impl SourceChangeBuilder { @@ -251,7 +242,6 @@ impl SourceChangeBuilder { command: None, file_editors: FxHashMap::default(), snippet_annotations: vec![], - snippet_builder: None, } } @@ -329,15 +319,9 @@ impl SourceChangeBuilder { } // Apply mutable edits - let snippet_edit = self.snippet_builder.take().map(|builder| { - SnippetEdit::new( - builder.places.into_iter().flat_map(PlaceSnippet::finalize_position).collect(), - ) - }); - let edit = mem::take(&mut self.edit).finish(); - if !edit.is_empty() || snippet_edit.is_some() { - self.source_change.insert_source_and_snippet_edit(self.file_id, edit, snippet_edit); + if !edit.is_empty() { + self.source_change.insert_source_edit(self.file_id, edit); } } @@ -439,22 +423,3 @@ pub enum AnnotationSnippet { /// Place a placeholder snippet in place of the element(s) Over, } - -enum PlaceSnippet { - /// Place a tabstop before an element - Before(SyntaxElement), - /// Place a tabstop before an element - After(SyntaxElement), - /// Place a placeholder snippet in place of the element - Over(SyntaxElement), -} - -impl PlaceSnippet { - fn finalize_position(self) -> Vec { - match self { - PlaceSnippet::Before(it) => vec![Snippet::Tabstop(it.text_range().start())], - PlaceSnippet::After(it) => vec![Snippet::Tabstop(it.text_range().end())], - PlaceSnippet::Over(it) => vec![Snippet::Placeholder(it.text_range())], - } - } -} From d19e40446a594f69d42c198aca55de3beec57763 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 31 Jul 2026 08:26:46 +0530 Subject: [PATCH 45/63] Removed unused From>, and the extend variant. And also make fields private which doesn't need to be pub --- .../crates/ide-db/src/source_change.rs | 35 +++++-------------- 1 file changed, 8 insertions(+), 27 deletions(-) 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 d403cf6e0c926..58f459003ec46 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 @@ -68,7 +68,7 @@ impl SourceChange { /// Inserts a [`TextEdit`] and potentially a [`SnippetEdit`] for the given [`FileId`]. /// This properly handles merging existing edits for a file if some already exist. - pub fn insert_source_and_snippet_edit( + fn insert_source_and_snippet_edit( &mut self, file_id: impl Into, edit: TextEdit, @@ -105,7 +105,7 @@ impl SourceChange { pub fn merge(mut self, other: SourceChange) -> SourceChange { self.extend(other.source_file_edits); - self.extend(other.file_system_edits); + self.file_system_edits.extend(other.file_system_edits); self.is_snippet |= other.is_snippet; self } @@ -128,25 +128,6 @@ impl Extend<(FileId, (TextEdit, Option))> for SourceChange { } } -impl Extend for SourceChange { - fn extend>(&mut self, iter: T) { - iter.into_iter().for_each(|edit| self.push_file_system_edit(edit)); - } -} - -impl From> for SourceChange { - fn from(source_file_edits: IntMap) -> SourceChange { - let source_file_edits = - source_file_edits.into_iter().map(|(file_id, edit)| (file_id, (edit, None))).collect(); - SourceChange { - source_file_edits, - file_system_edits: Vec::new(), - is_snippet: false, - ..SourceChange::default() - } - } -} - impl FromIterator<(FileId, TextEdit)> for SourceChange { fn from_iter>(iter: T) -> Self { let mut this = SourceChange::default(); @@ -222,15 +203,15 @@ impl SnippetEdit { } pub struct SourceChangeBuilder { - pub edit: TextEditBuilder, + edit: TextEditBuilder, pub file_id: FileId, pub source_change: SourceChange, pub command: Option, /// Keeps track of all edits performed on each file - pub file_editors: FxHashMap, + file_editors: FxHashMap, /// Keeps track of which annotations correspond to which snippets - pub snippet_annotations: Vec<(AnnotationSnippet, SyntaxAnnotation)>, + snippet_annotations: Vec<(AnnotationSnippet, SyntaxAnnotation)>, } impl SourceChangeBuilder { @@ -379,7 +360,7 @@ impl SourceChangeBuilder { .is_err() ); - mem::take(&mut self.source_change) + self.source_change } } @@ -415,10 +396,10 @@ pub enum Snippet { PlaceholderGroup(Vec), } -pub enum AnnotationSnippet { +enum AnnotationSnippet { /// Place a tabstop before an element Before, - /// Place a tabstop before an element + /// Place a tabstop after an element After, /// Place a placeholder snippet in place of the element(s) Over, From 1f5c92a1d8d6b9155a3b5cfec85972bc3195c5f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristoffer=20S=C3=B8holm?= Date: Wed, 22 Jul 2026 13:51:34 +0200 Subject: [PATCH 46/63] fix: Fix glob imports overriding later specific imports --- .../crates/hir-def/src/item_scope.rs | 32 ++++++++++++------- .../crates/hir-def/src/per_ns.rs | 10 ++++++ .../src/handlers/type_mismatch.rs | 25 +++++++++++++++ 3 files changed, 55 insertions(+), 12 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs index 1443d3ea4be4c..14f10651cb8af 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs @@ -621,7 +621,11 @@ impl ItemScope { // for that. } _ => { - if glob_imports.types.remove(&lookup) { + // A non-glob import either shadows a glob import of the same + // name, or re-resolves a stale binding it recorded earlier. + if glob_imports.types.remove(&lookup) + || entry.get().is_reresolved_by(&fld.def, import) + { let prev = std::mem::replace(&mut fld.import, import); if let Some(import) = import { self.use_imports_types.insert( @@ -659,19 +663,22 @@ impl ItemScope { changed = true; } Entry::Occupied(mut entry) - if !matches!(import, Some(ImportOrExternCrate::Glob(..))) - && glob_imports.values.remove(&lookup) => + if !matches!(import, Some(ImportOrExternCrate::Glob(..))) => { - cov_mark::hit!(import_shadowed); - let import = import.and_then(ImportOrExternCrate::import_or_glob); - let prev = std::mem::replace(&mut fld.import, import); - if let Some(import) = import { - self.use_imports_values - .insert(import, prev.map_or(ImportOrDef::Def(fld.def), Into::into)); + if glob_imports.values.remove(&lookup) + || entry.get().is_reresolved_by(&fld.def, import) + { + cov_mark::hit!(import_shadowed); + + let prev = std::mem::replace(&mut fld.import, import); + if let Some(import) = import { + self.use_imports_values + .insert(import, prev.map_or(ImportOrDef::Def(fld.def), Into::into)); + } + entry.insert(fld); + changed = true; } - entry.insert(fld); - changed = true; } _ => {} } @@ -699,7 +706,8 @@ impl ItemScope { } Entry::Occupied(mut entry) if !matches!(import, Some(ImportOrExternCrate::Glob(..))) - && glob_imports.macros.remove(&lookup) => + && (glob_imports.macros.remove(&lookup) + || entry.get().is_reresolved_by(&fld.def, import)) => { cov_mark::hit!(import_shadowed); let prev = std::mem::replace(&mut fld.import, import); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/per_ns.rs b/src/tools/rust-analyzer/crates/hir-def/src/per_ns.rs index 8721cd65dbac7..62947f1511d86 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/per_ns.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/per_ns.rs @@ -35,6 +35,16 @@ pub struct Item { pub import: Option, } +impl Item { + /// Whether `import` is the same import that produced `self`, now resolving to a different + /// `def`. This happens when an import is first recorded as an indeterminate resolution + /// (e.g. only one namespace was available at the time) and later re-resolves to another + /// def, such as an explicit import that shadows a glob only after the glob has been seen. + pub(crate) fn is_reresolved_by(&self, def: &Def, import: Option) -> bool { + import.is_some() && self.import == import && self.def != *def + } +} + pub type TypesItem = Item; pub type ValuesItem = Item; // May be Externcrate for `[macro_use]`'d macros 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 295f37ab1d6b3..b16be503effd3 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 @@ -1810,6 +1810,31 @@ fn main() { // ^^ error: type annotations needed // ^^^ error: the trait bound `i32: Foo` is not satisfied } +"#, + ); + } + + #[test] + fn regression_21668() { + check_diagnostics( + r#" +mod std { + pub enum Ordering { Less } +} +pub use std::*; + +pub mod evil { + pub struct Ordering(pub i32); +} + +pub mod oblivious { + use crate::Ordering; + + pub fn what() -> Ordering { + Ordering(2) + } +} +pub use evil::Ordering; "#, ); } From 2945d4d17dcd07e55591839f1df3e7dbdab18ceb Mon Sep 17 00:00:00 2001 From: Ritesh Date: Thu, 30 Jul 2026 18:19:02 +0000 Subject: [PATCH 47/63] fix: show qualified paths when type names collide in E0308 --- .../src/handlers/type_mismatch.rs | 129 ++++++++++++++++-- 1 file changed, 120 insertions(+), 9 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 b16be503effd3..df815d6322b63 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 @@ -45,18 +45,31 @@ pub(crate) fn type_mismatch( cov_mark::hit!(type_mismatch_range_adjustment); Some(salient_token_range) }); + + let expected = d + .expected + .display(ctx.db(), ctx.display_target) + .with_closure_style(ClosureStyle::ClosureWithId) + .to_string(); + let actual = d + .actual + .display(ctx.db(), ctx.display_target) + .with_closure_style(ClosureStyle::ClosureWithId) + .to_string(); + + // The types differ (that's why we're here), yet they render the same, e.g. `foo::S` and + // `bar::S` both render as `S`. Retry with qualified paths so the message isn't a useless + // "expected S, found S". + let (expected, actual) = if expected == actual { + qualified_display(ctx, d).unwrap_or((expected, actual)) + } else { + (expected, actual) + }; + Some( Diagnostic::new( DiagnosticCode::RustcHardError("E0308"), - format!( - "expected {}, found {}", - d.expected - .display(ctx.db(), ctx.display_target) - .with_closure_style(ClosureStyle::ClosureWithId), - d.actual - .display(ctx.db(), ctx.display_target) - .with_closure_style(ClosureStyle::ClosureWithId), - ), + format!("expected {expected}, found {actual}"), display_range, ) .stable() @@ -64,6 +77,20 @@ pub(crate) fn type_mismatch( ) } +/// Renders both sides of the mismatch with qualified paths, for when their plain names collide. +/// Returns `None` if either side has no renderable path, in which case both keep their plain +/// names — mixing a qualified and an unqualified name would be more confusing, not less. +fn qualified_display( + ctx: &DiagnosticsContext<'_, '_>, + d: &hir::TypeMismatch<'_>, +) -> Option<(String, String)> { + let root = d.expr_or_pat.file_id.parse_or_expand(ctx.db()); + let module = ctx.sema.scope(d.expr_or_pat.value.to_node(&root).syntax())?.module(); + let expected = d.expected.display_source_code(ctx.db(), module.into(), true).ok()?; + let actual = d.actual.display_source_code(ctx.db(), module.into(), true).ok()?; + Some((expected, actual)) +} + fn fixes(ctx: &DiagnosticsContext<'_, '_>, d: &hir::TypeMismatch<'_>) -> Option> { let mut fixes = Vec::new(); @@ -1835,6 +1862,90 @@ pub mod oblivious { } } pub use evil::Ordering; +"#, + ); + } + + // Tests for qualified paths on name collision (issue #22331) + + #[test] + fn type_mismatch_collision_basic_structs() { + check_diagnostics( + r#" +mod foo { + pub struct S; +} +mod bar { + pub struct S; +} +fn test(_: foo::S) { + test(bar::S); + //^^^^^^ error: expected foo::S, found bar::S +} +"#, + ); + } + + #[test] + fn type_mismatch_collision_in_generic() { + check_diagnostics( + r#" +//- minicore: option +mod foo { + pub struct S; +} +mod bar { + pub struct S; +} +fn make() -> Option { loop {} } +fn test(_: Option) { + test(make()); + //^^^^^^ error: expected Option, found Option +} +"#, + ); + } + + #[test] + fn type_mismatch_no_collision_unchanged() { + check_diagnostics( + r#" +mod foo { + pub struct S; +} +mod bar { + pub struct T; +} +fn test(_: foo::S) { + test(bar::T); + //^^^^^^ error: expected S, found T +} +"#, + ); + } + + #[test] + fn type_mismatch_multiple_collisions() { + check_diagnostics( + r#" +//- minicore: result +mod foo { + pub struct T; +} +mod bar { + pub struct T; +} +mod baz { + pub struct E; +} +mod qux { + pub struct E; +} +fn make() -> Result { loop {} } +fn test(_: Result) { + test(make()); + //^^^^^^ error: expected Result, found Result +} "#, ); } From 6b5fd9605b9eaafbc6ec6a11a1a829bf60a27331 Mon Sep 17 00:00:00 2001 From: shulaoda <165626830+shulaoda@users.noreply.github.com> Date: Sun, 2 Aug 2026 05:38:27 +0800 Subject: [PATCH 48/63] fix: don't panic on a self-referential `impl Trait` function --- .../rust-analyzer/crates/hir-ty/src/infer.rs | 2 +- .../crates/hir-ty/src/infer/unify.rs | 6 ++-- .../crates/hir-ty/src/next_solver/interner.rs | 12 +++++-- .../crates/hir-ty/src/tests/regression.rs | 31 +++++++++++++++++++ 4 files changed, 44 insertions(+), 7 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 319a8ae9bd7fc..935b2f9ffaba3 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs @@ -1438,7 +1438,7 @@ impl<'db> InferenceContext<'db> { lowering_mode: LoweringMode, ) -> Self { let trait_env = db.trait_environment(generic_def); - let table = unify::InferenceTable::new(db, trait_env, resolver.krate(), store_owner); + let table = unify::InferenceTable::new(db, trait_env, resolver.krate(), owner); let types = crate::next_solver::default_types(db); InferenceContext { result: InferenceResult::new(types.types.error), diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs index 6157f51500d9e..7b589efba2f63 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs @@ -3,7 +3,7 @@ use std::fmt; use base_db::Crate; -use hir_def::{ExpressionStoreOwnerId, GenericParamId, TraitId}; +use hir_def::{GenericParamId, TraitId}; use rustc_hash::FxHashSet; use rustc_type_ir::{ TyVid, TypeFoldable, TypeVisitableExt, @@ -14,7 +14,7 @@ use smallvec::SmallVec; use thin_vec::ThinVec; use crate::{ - InferenceDiagnostic, Span, + InferBodyId, InferenceDiagnostic, Span, db::HirDatabase, next_solver::{ Canonical, ClauseKind, Const, ConstKind, DbInterner, ErrorGuaranteed, GenericArg, @@ -144,7 +144,7 @@ impl<'db> InferenceTable<'db> { db: &'db dyn HirDatabase, trait_env: ParamEnv<'db>, krate: Crate, - owner: ExpressionStoreOwnerId, + owner: InferBodyId<'db>, ) -> Self { let interner = DbInterner::new_with(db, krate); let typing_mode = TypingMode::typeck_for_body(interner, owner.into()); 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 7554ca6bcd025..02baf27e0bbf2 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 @@ -2085,13 +2085,19 @@ impl<'db> Interner for DbInterner<'db> { opaque: Self::LocalOpaqueTyId, ) -> EarlyBinder { let impl_trait_id = opaque.0.loc(self.db); - match impl_trait_id { + // The entry is missing when this call cycles back into the still-running inference + // of the defining body, as the cycle fallback is an empty result. + let hidden_type = match impl_trait_id { crate::ImplTraitId::ReturnTypeImplTrait(func, idx) => { - crate::opaques::rpit_hidden_types(self.db, func)[idx].get() + crate::opaques::rpit_hidden_types(self.db, func).get(idx) } crate::ImplTraitId::TypeAliasImplTrait(type_alias, idx) => { - crate::opaques::tait_hidden_types(self.db, type_alias)[idx].get() + crate::opaques::tait_hidden_types(self.db, type_alias).get(idx) } + }; + match hidden_type { + Some(hidden_type) => hidden_type.get(), + None => EarlyBinder::bind(Ty::new_error(self, ErrorGuaranteed)), } } 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 683c938fb5a25..59f33f0f1b29a 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 @@ -3063,3 +3063,34 @@ fn main() { "#, ); } + +#[test] +fn regression_22820() { + check_no_mismatches( + r#" +//- minicore: copy +trait MyTrait: Copy { + const ASSOC: usize; +} + +const fn output(_: T) -> usize { + ::ASSOC +} + +const fn yeet() -> impl Clone { + let x = [0u8; output(yeet())]; +} + "#, + ); +} + +#[test] +fn rpit_function_with_non_trivial_anon_const() { + check_no_mismatches( + r#" +fn f() -> impl Sized { + let x = [0u8; 1 + 2]; +} + "#, + ); +} From b33c51e3daf21a29d26a1b7b1e38c158d211836a Mon Sep 17 00:00:00 2001 From: shulaoda <165626830+shulaoda@users.noreply.github.com> Date: Sun, 2 Aug 2026 06:14:38 +0800 Subject: [PATCH 49/63] fix: recognize format arguments after a backslash in raw strings --- ...ighlight_raw_string_format_specifiers.html | 49 +++++++++++++++++++ .../ide/src/syntax_highlighting/tests.rs | 17 +++++++ .../crates/syntax/src/ast/token_ext.rs | 10 ++++ 3 files changed, 76 insertions(+) create mode 100644 src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_raw_string_format_specifiers.html diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_raw_string_format_specifiers.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_raw_string_format_specifiers.html new file mode 100644 index 0000000000000..4497e83a4d4cc --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_raw_string_format_specifiers.html @@ -0,0 +1,49 @@ + + +
fn main() {
+    let here = 1;
+    format_args!(r"backslash \{here} arg");
+    format_args!(r#"hashed \{here} arg"#);
+    format_args!("plain {here} arg");
+}
\ No newline at end of file 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 6cb323b46a521..7a3d71f519f84 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 @@ -452,6 +452,23 @@ macro_rules! void_2024 { ); } +#[test] +fn test_raw_string_format_specifiers() { + check_highlighting( + r####" +//- minicore: fmt +fn main() { + let here = 1; + format_args!(r"backslash \{here} arg"); + format_args!(r#"hashed \{here} arg"#); + format_args!("plain {here} arg"); +} +"####, + expect_file!["./test_data/highlight_raw_string_format_specifiers.html"], + false, + ); +} + #[test] fn test_string_highlighting() { // The format string detection is based on macro-expansion, diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/token_ext.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/token_ext.rs index b5c4e1aa9d9f1..f8bffe9d47b36 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/token_ext.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/token_ext.rs @@ -183,6 +183,16 @@ pub trait IsString: AstToken { let text = &self.text()[text_range_no_quotes - start]; let offset = text_range_no_quotes.start() - start; + if self.is_raw() { + let mut pos = offset; + for c in text.chars() { + let len = TextSize::of(c); + cb(TextRange::at(pos, len), Ok(c)); + pos += len; + } + return; + } + self.unescape(text, &mut |range: Range, unescaped_char| { if let Some((s, e)) = range.start.try_into().ok().zip(range.end.try_into().ok()) { cb(TextRange::new(s, e) + offset, unescaped_char); From c973eb05e512facc46a824e2687c235b0813de4f Mon Sep 17 00:00:00 2001 From: Musteab Date: Sun, 2 Aug 2026 07:30:51 +0800 Subject: [PATCH 50/63] fix: detect the rust-analyzer component in a multi-line components array --- .../editors/code/src/bootstrap.ts | 14 +++- .../editors/code/tests/unit/bootstrap.test.ts | 71 +++++++++++++++++++ 2 files changed, 82 insertions(+), 3 deletions(-) diff --git a/src/tools/rust-analyzer/editors/code/src/bootstrap.ts b/src/tools/rust-analyzer/editors/code/src/bootstrap.ts index ca5b7e3ec7855..98c2b359bf2bc 100644 --- a/src/tools/rust-analyzer/editors/code/src/bootstrap.ts +++ b/src/tools/rust-analyzer/editors/code/src/bootstrap.ts @@ -176,14 +176,21 @@ async function fileExists(uri: vscode.Uri) { ); } +// Matches a `components` array that lists `rust-analyzer`. The elements are matched with +// `[^\]]` rather than `.` so that the array may be spread over several lines, which is just +// as valid TOML as keeping it on one. TOML strings come in both quote flavours. +const RA_COMPONENT_RE = /components\s*=\s*\[[^\]]*["']rust-analyzer["'][^\]]*\]/; + +function declaresRaComponent(toolchainFileContents: string): boolean { + return RA_COMPONENT_RE.test(toolchainFileContents); +} + async function hasToolchainFileWithRaDeclared(uri: vscode.Uri): Promise { try { const toolchainFileContents = new TextDecoder().decode( await vscode.workspace.fs.readFile(uri), ); - return ( - toolchainFileContents.match(/components\s*=\s*\[.*"rust-analyzer".*\]/g)?.length === 1 - ); + return declaresRaComponent(toolchainFileContents); } catch (_) { return false; } @@ -296,6 +303,7 @@ async function patchelf(dest: vscode.Uri): Promise { } export const _private = { + declaresRaComponent, earliestToolchainPath, orderFromPath, }; diff --git a/src/tools/rust-analyzer/editors/code/tests/unit/bootstrap.test.ts b/src/tools/rust-analyzer/editors/code/tests/unit/bootstrap.test.ts index baabf4f89773b..8d348b9ccbd5f 100644 --- a/src/tools/rust-analyzer/editors/code/tests/unit/bootstrap.test.ts +++ b/src/tools/rust-analyzer/editors/code/tests/unit/bootstrap.test.ts @@ -93,4 +93,75 @@ export async function getTests(ctx: Context) { ); }); }); + + await ctx.suite("Bootstrap/Detect RA component in toolchain file", (suite) => { + suite.addTest("Single line components array", async () => { + assert.ok( + _private.declaresRaComponent( + `[toolchain] +channel = "1.88" +components = ["cargo", "rust-analyzer", "rustfmt"] +`, + ), + ); + }); + + suite.addTest("Multi line components array", async () => { + assert.ok( + _private.declaresRaComponent( + `[toolchain] +channel = "1.88" +components = [ + "cargo", + "rust-analyzer", + "rustfmt", +] +profile = "default" +`, + ), + ); + }); + + suite.addTest("Components array with literal strings", async () => { + assert.ok(_private.declaresRaComponent(`components = ['cargo', 'rust-analyzer']`)); + }); + + suite.addTest("Components array without RA", async () => { + assert.ok( + !_private.declaresRaComponent( + `[toolchain] +channel = "1.88" +components = [ + "cargo", + "rustfmt", +] +`, + ), + ); + }); + + suite.addTest("No components array", async () => { + assert.ok( + !_private.declaresRaComponent( + `[toolchain] +channel = "1.88" +`, + ), + ); + }); + + suite.addTest("RA mentioned outside the components array", async () => { + assert.ok( + !_private.declaresRaComponent( + `[toolchain] +channel = "1.88" +components = [ + "cargo", +] +path = "/opt/rust-analyzer" +`, + ), + ); + }); + }); } From 01db4e129a0eff9aca25ac5d37f5792a8ba1438b Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Sun, 2 Aug 2026 03:03:53 +0300 Subject: [PATCH 51/63] Do not store references in `ExprScope`'s visitor --- .../crates/hir-def/src/expr_store/scope.rs | 189 +++++++----------- 1 file changed, 71 insertions(+), 118 deletions(-) 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 ee5396b4cde97..cfbfde88765f0 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 @@ -1,4 +1,6 @@ //! Name resolution for expressions. +use std::mem; + use base_db::SourceDatabase; use hir_expand::{MacroDefId, name::Name}; use la_arena::{Arena, ArenaMap, Idx, IdxRange, RawIdx}; @@ -162,18 +164,13 @@ impl ExprScopes { body.expr_only.as_ref().map_or(0, |it| it.exprs.len()), ), }; - let mut root = scopes.root_scope(); + let root = scopes.root_scope(); if let Some(Param { formal: self_param, user_written: _ }) = body.self_param { scopes.add_bindings(body, root, self_param, body.binding_hygiene(self_param)); } body.params.iter().for_each(|param| scopes.add_pat_bindings(body, root, param.formal)); - ExprScopeVisitor { - store: body, - scopes: &mut scopes, - scope: &mut { root }, - const_scope: &mut root, - } - .on_expr(body.root_expr()); + ExprScopeVisitor { store: body, scopes: &mut scopes, scope: root, const_scope: root } + .on_expr(body.root_expr()); scopes } @@ -187,14 +184,9 @@ impl ExprScopes { }; let root = scopes.root_scope(); for root_expr in roots { - let mut scope = scopes.new_scope(root); - ExprScopeVisitor { - store, - scopes: &mut scopes, - scope: &mut { scope }, - const_scope: &mut scope, - } - .on_expr(root_expr); + let scope = scopes.new_scope(root); + ExprScopeVisitor { store, scopes: &mut scopes, scope, const_scope: scope } + .on_expr(root_expr); } scopes } @@ -290,11 +282,17 @@ impl ExprScopes { struct ExprScopeVisitor<'a> { store: &'a ExpressionStore, scopes: &'a mut ExprScopes, - scope: &'a mut ScopeId, - const_scope: &'a mut ScopeId, + scope: ScopeId, + const_scope: ScopeId, } impl ExprScopeVisitor<'_> { + fn with_scope(&mut self, scope: ScopeId, f: impl FnOnce(&mut Self)) { + let old_scope = mem::replace(&mut self.scope, scope); + f(self); + self.scope = old_scope; + } + fn visit_block( &mut self, expr: ExprId, @@ -303,49 +301,47 @@ impl ExprScopeVisitor<'_> { tail: Option, label: Option, ) { - let mut scope = self.scopes.new_block_scope(*self.scope, id, label); - let mut const_scope = if id.is_some() { - self.scopes.new_block_scope(*self.const_scope, id, None) - } else { - // We don't need to allocate a new scope, since only items matter to us. - *self.const_scope - }; - // Overwrite the old scope for the block expr, so that every block scope can be found - // via the block itself (important for blocks that only contain items, no expressions). - self.scopes.set_scope(expr, scope); - - let mut visitor = ExprScopeVisitor { - store: self.store, - scopes: self.scopes, - scope: &mut scope, - const_scope: &mut const_scope, - }; - for stmt in statements { - match stmt { - Statement::Let { pat, initializer, else_branch, type_ref } => { - visitor.on_type_opt(*type_ref); - visitor.on_expr_opt(*initializer); - visitor.on_expr_opt(*else_branch); - *visitor.scope = visitor.scopes.new_scope(*visitor.scope); - visitor.scopes.add_pat_bindings(visitor.store, *visitor.scope, *pat); - } - Statement::Expr { expr, has_semi: _ } => visitor.on_expr(*expr), - Statement::Item(Item::MacroDef(macro_id)) => { - *visitor.scope = - visitor.scopes.new_macro_def_scope(*visitor.scope, macro_id.clone()); - *visitor.const_scope = - visitor.scopes.new_macro_def_scope(*visitor.const_scope, macro_id.clone()); + let scope = self.scopes.new_block_scope(self.scope, id, label); + self.with_scope(scope, |this| { + let old_const_scope = if id.is_some() { + let const_scope = this.scopes.new_block_scope(this.const_scope, id, None); + mem::replace(&mut this.const_scope, const_scope) + } else { + // We don't need to allocate a new scope, since only items matter to us. + this.const_scope + }; + // Overwrite the old scope for the block expr, so that every block scope can be found + // via the block itthis (important for blocks that only contain items, no expressions). + this.scopes.set_scope(expr, this.scope); + + for stmt in statements { + match stmt { + Statement::Let { pat, initializer, else_branch, type_ref } => { + this.on_type_opt(*type_ref); + this.on_expr_opt(*initializer); + this.on_expr_opt(*else_branch); + this.scope = this.scopes.new_scope(this.scope); + this.scopes.add_pat_bindings(this.store, this.scope, *pat); + } + Statement::Expr { expr, has_semi: _ } => this.on_expr(*expr), + Statement::Item(Item::MacroDef(macro_id)) => { + this.scope = this.scopes.new_macro_def_scope(this.scope, macro_id.clone()); + this.const_scope = + this.scopes.new_macro_def_scope(this.const_scope, macro_id.clone()); + } + Statement::Item(Item::Other) => (), } - Statement::Item(Item::Other) => (), } - } - visitor.on_expr_opt(tail); + this.on_expr_opt(tail); + + this.const_scope = old_const_scope; + }); } } impl StoreVisitor for ExprScopeVisitor<'_> { fn on_expr(&mut self, expr: ExprId) { - self.scopes.set_scope(expr, *self.scope); + self.scopes.set_scope(expr, self.scope); match &self.store[expr] { Expr::Block { statements, tail, id, label } => { self.visit_block(expr, *id, statements, *tail, *label); @@ -355,99 +351,56 @@ impl StoreVisitor for ExprScopeVisitor<'_> { self.visit_block(expr, *id, statements, *tail, None); } Expr::Loop { body, label, source: _ } => { - let mut scope = self.scopes.new_labeled_scope(*self.scope, *label); - ExprScopeVisitor { - store: self.store, - scopes: self.scopes, - scope: &mut scope, - const_scope: self.const_scope, - } - .on_expr(*body); + let scope = self.scopes.new_labeled_scope(self.scope, *label); + self.with_scope(scope, |this| this.on_expr(*body)); } Expr::Closure { args, arg_types, ret_type, body, capture_by: _, closure_kind: _ } => { arg_types.iter().flatten().for_each(|type_ref| self.on_type(*type_ref)); self.on_type_opt(*ret_type); - let mut scope = self.scopes.new_scope(*self.scope); + let scope = self.scopes.new_scope(self.scope); args.iter().for_each(|arg| self.scopes.add_pat_bindings(self.store, scope, *arg)); - ExprScopeVisitor { - store: self.store, - scopes: self.scopes, - scope: &mut scope, - const_scope: self.const_scope, - } - .on_expr(*body); + self.with_scope(scope, |this| this.on_expr(*body)); } Expr::Match { expr, arms } => { self.on_expr(*expr); for arm in arms.iter() { - let mut scope = self.scopes.new_scope(*self.scope); - self.scopes.add_pat_bindings(self.store, scope, arm.pat); - if let Some(guard) = arm.guard { - scope = self.scopes.new_scope(scope); - ExprScopeVisitor { - store: self.store, - scopes: self.scopes, - scope: &mut scope, - const_scope: self.const_scope, + let scope = self.scopes.new_scope(self.scope); + self.with_scope(scope, |this| { + this.scopes.add_pat_bindings(this.store, scope, arm.pat); + if let Some(guard) = arm.guard { + this.on_expr(guard); } - .on_expr(guard); - } - ExprScopeVisitor { - store: self.store, - scopes: self.scopes, - scope: &mut scope, - const_scope: self.const_scope, - } - .on_expr(arm.expr); + this.on_expr(arm.expr); + }); } } &Expr::If { condition, then_branch, else_branch } => { - let mut then_branch_scope = self.scopes.new_scope(*self.scope); - let mut visitor = ExprScopeVisitor { - store: self.store, - scopes: self.scopes, - scope: &mut then_branch_scope, - const_scope: self.const_scope, - }; - visitor.on_expr(condition); - visitor.on_expr(then_branch); + let then_branch_scope = self.scopes.new_scope(self.scope); + self.with_scope(then_branch_scope, |this| { + this.on_expr(condition); + this.on_expr(then_branch); + }); self.on_expr_opt(else_branch); } &Expr::Let { pat, expr } => { self.on_expr(expr); - *self.scope = self.scopes.new_scope(*self.scope); - self.scopes.add_pat_bindings(self.store, *self.scope, pat); + self.scope = self.scopes.new_scope(self.scope); + self.scopes.add_pat_bindings(self.store, self.scope, pat); } _ => self.store.visit_expr_children(expr, &mut *self), } } fn on_anon_const_expr(&mut self, expr: ExprId) { - let mut scope = *self.const_scope; - ExprScopeVisitor { - store: self.store, - scopes: self.scopes, - scope: &mut scope, - const_scope: self.const_scope, - } - .on_expr(expr); + self.with_scope(self.const_scope, |this| this.on_expr(expr)); } fn on_pat(&mut self, pat: PatId) { - self.store.visit_pat_children(pat, &mut *self); + self.store.visit_pat_children(pat, self); } fn on_type(&mut self, ty: TypeRefId) { - let mut scope = *self.const_scope; - self.store.visit_type_ref_children( - ty, - ExprScopeVisitor { - store: self.store, - scopes: self.scopes, - scope: &mut scope, - const_scope: self.const_scope, - }, - ); + self.with_scope(self.const_scope, |this| self.store.visit_type_ref_children(ty, this)); } } From 889a9405a585b6c8158a269ebdeb28beda1cf251 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 31 Jul 2026 11:14:47 +0530 Subject: [PATCH 52/63] Bump rowan to 0.17.0 --- 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 7a2e2d493b59a..51d907024c6c9 100644 --- a/src/tools/rust-analyzer/Cargo.lock +++ b/src/tools/rust-analyzer/Cargo.lock @@ -2296,9 +2296,9 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "rowan" -version = "0.15.19" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2441aaccb50f4267d4f0f58b21e0138e96a449f361ed57a2673a83c9bca0772" +checksum = "14b574c58582fa59fa43a2feb6608b8744184659f08a2e0117e4b8224d95ed61" dependencies = [ "countme", "hashbrown 0.14.5", diff --git a/src/tools/rust-analyzer/Cargo.toml b/src/tools/rust-analyzer/Cargo.toml index 4ef92c5bd2ca3..693a819052634 100644 --- a/src/tools/rust-analyzer/Cargo.toml +++ b/src/tools/rust-analyzer/Cargo.toml @@ -123,7 +123,7 @@ process-wrap = { version = "9.1.0", 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.19" +rowan = "=0.17.0" # Ideally we'd not enable the macros feature but unfortunately the `tracked` attribute does not work # on impls without it salsa = { version = "0.27.0", default-features = false, features = [ From 9c3358aea547ba6b336626499ea4055463c3ddfc Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 31 Jul 2026 11:15:38 +0530 Subject: [PATCH 53/63] Adapt to borrowed Rowan green nodes --- .../crates/syntax/src/ast/node_ext.rs | 35 ++++++------------- .../syntax/src/syntax_editor/edit_algo.rs | 6 ++-- 2 files changed, 13 insertions(+), 28 deletions(-) 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 1eb658f4b8d06..00aeb1dfebf8d 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 @@ -3,7 +3,7 @@ //! //! These methods should only do simple, shallow tasks related to the syntax of the node itself. -use std::{borrow::Cow, fmt, iter::successors}; +use std::{fmt, iter::successors}; use itertools::Itertools; use parser::SyntaxKind; @@ -31,15 +31,9 @@ impl ast::Name { pub fn text(&self) -> TokenText<'_> { text_of_first_token(self.syntax()) } - pub fn text_non_mutable(&self) -> &str { - fn first_token(green_ref: &GreenNodeData) -> &GreenTokenData { - green_ref.children().next().and_then(NodeOrToken::into_token).unwrap() - } - match self.syntax().green() { - Cow::Borrowed(green_ref) => first_token(green_ref).text(), - Cow::Owned(_) => unreachable!(), - } + pub fn text_non_mutable(&self) -> &str { + first_token(self.syntax().green()).text() } } @@ -47,15 +41,9 @@ impl ast::NameRef { pub fn text(&self) -> TokenText<'_> { text_of_first_token(self.syntax()) } - pub fn text_non_mutable(&self) -> &str { - fn first_token(green_ref: &GreenNodeData) -> &GreenTokenData { - green_ref.children().next().and_then(NodeOrToken::into_token).unwrap() - } - match self.syntax().green() { - Cow::Borrowed(green_ref) => first_token(green_ref).text(), - Cow::Owned(_) => unreachable!(), - } + pub fn text_non_mutable(&self) -> &str { + first_token(self.syntax().green()).text() } pub fn as_tuple_field(&self) -> Option { @@ -67,15 +55,12 @@ impl ast::NameRef { } } -fn text_of_first_token(node: &SyntaxNode) -> TokenText<'_> { - fn first_token(green_ref: &GreenNodeData) -> &GreenTokenData { - green_ref.children().next().and_then(NodeOrToken::into_token).unwrap() - } +fn first_token(green_ref: &GreenNodeData) -> &GreenTokenData { + green_ref.children().next().and_then(NodeOrToken::into_token).unwrap() +} - match node.green() { - Cow::Borrowed(green_ref) => TokenText::borrowed(first_token(green_ref).text()), - Cow::Owned(green) => TokenText::owned(first_token(&green).to_owned()), - } +fn text_of_first_token(node: &SyntaxNode) -> TokenText<'_> { + TokenText::borrowed(first_token(node.green()).text()) } fn into_comma(it: NodeOrToken) -> Option { 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 d24d9b1334dec..24e7016f5bdee 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 @@ -449,7 +449,7 @@ impl TreeState { let parent = parent_path.resolve(&self.root).and_then(SyntaxElement::into_node).unwrap(); let green = rowan::GreenNodeData::splice_children( - parent.green().as_ref(), + parent.green(), deleted.clone(), inserted.into_iter().map(PreparedElement::into_green), ); @@ -466,7 +466,7 @@ impl TreeState { let NodeOrToken::Node(node) = replacement.syntax else { panic!("root node replacement should be a node") }; - self.root = SyntaxNode::new_root(node.green().into_owned()); + self.root = SyntaxNode::new_root(node.green().to_owned()); self.changed.clear(); if track_as_changed { self.changed.push(SyntaxPath { child_indices: Vec::new() }); @@ -543,7 +543,7 @@ struct PreparedElement { impl PreparedElement { fn into_green(self) -> rowan::NodeOrToken { match self.syntax { - SyntaxElement::Node(node) => NodeOrToken::Node(node.green().into_owned()), + SyntaxElement::Node(node) => NodeOrToken::Node(node.green().to_owned()), SyntaxElement::Token(token) => NodeOrToken::Token(token.green().to_owned()), } } From f12a1361a12f29a998f3d4a7441ef8289e3a13c4 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Fri, 31 Jul 2026 11:17:20 +0530 Subject: [PATCH 54/63] Remove the obsolete TokenText wrapper --- .../hir-expand/src/builtin/derive_macro.rs | 2 +- .../src/handlers/apply_demorgan.rs | 4 +- .../src/handlers/convert_into_to_from.rs | 2 +- .../extract_struct_from_enum_variant.rs | 23 ++-- .../src/handlers/generate_function.rs | 4 +- .../generate_single_field_struct_from.rs | 16 ++- .../src/handlers/reorder_impl_items.rs | 2 +- .../crates/ide-assists/src/utils.rs | 2 +- .../src/utils/gen_trait_fn_body.rs | 2 +- .../src/completions/attribute.rs | 1 - .../src/completions/attribute/repr.rs | 2 +- .../ide-completion/src/completions/postfix.rs | 2 +- .../crates/ide-db/src/imports/insert_use.rs | 2 +- .../ide-db/src/imports/merge_imports.rs | 10 +- .../crates/ide-db/src/path_transform.rs | 2 +- .../crates/ide-db/src/ra_fixture.rs | 3 +- .../rust-analyzer/crates/ide-db/src/rename.rs | 2 +- .../rust-analyzer/crates/ide-db/src/search.rs | 8 +- .../crates/ide-ssr/src/resolving.rs | 2 +- .../crates/ide/src/inlay_hints/lifetime.rs | 12 +-- .../crates/ide/src/inlay_hints/param_name.rs | 4 +- .../rust-analyzer/crates/span/src/ast_id.rs | 8 +- .../crates/syntax/src/ast/node_ext.rs | 30 ++---- .../rust-analyzer/crates/syntax/src/lib.rs | 2 - .../crates/syntax/src/token_text.rs | 102 ------------------ 25 files changed, 63 insertions(+), 186 deletions(-) delete mode 100644 src/tools/rust-analyzer/crates/syntax/src/token_text.rs diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin/derive_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/derive_macro.rs index 5e85a710e0855..ccd2d6dca29f1 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/builtin/derive_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/derive_macro.rs @@ -412,7 +412,7 @@ fn name_to_token( })?; let span = token_map.span_at(name.syntax().text_range().start()); - let name_token = tt::Ident::new(name.text().as_ref(), span); + let name_token = tt::Ident::new(name.text(), span); Ok(name_token) } 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 10262445a2dfe..e2c1048bdb785 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 @@ -206,7 +206,7 @@ pub(crate) fn apply_demorgan_iterator( let closure_body = closure_expr.body()?; let op_range = method_call.syntax().text_range(); - let label = format!("Apply De Morgan's law to `Iterator::{}`", name.text().as_str()); + let label = format!("Apply De Morgan's law to `Iterator::{}`", name.text()); acc.add_group( &GroupLabel("Apply De Morgan's law".to_owned()), AssistId::refactor_rewrite("apply_demorgan_iterator"), @@ -216,7 +216,7 @@ pub(crate) fn apply_demorgan_iterator( let editor = builder.make_editor(method_call.syntax()); let make = editor.make(); // replace the method name - let new_name = match name.text().as_str() { + let new_name = match name.text() { "all" => make.name_ref("any"), "any" => make.name_ref("all"), "is_some_and" => make.name_ref("is_none_or"), diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_into_to_from.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_into_to_from.rs index a01a66e7b1a52..c8de14bed4bb8 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_into_to_from.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_into_to_from.rs @@ -109,7 +109,7 @@ pub(crate) fn convert_into_to_from(acc: &mut Assists, ctx: &AssistContext<'_, '_ editor.replace(into_fn_name.syntax(), make.name("from").syntax()); for s in selfs { - match s.text().as_ref() { + match s.text() { "self" => editor.replace(s.syntax(), make.name_ref("val").syntax()), "Self" => { if let Some(path_segment) = 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 5e6e74bc94429..89c5470c160ff 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 @@ -241,7 +241,7 @@ fn tag_generics_in_variant(ty: &ast::Type, generics: &mut [(ast::GenericParam, b if matches!(token.kind(), T![lifetime_ident]) => { if let Some(lt) = lt.lifetime() - && lt.text().as_str() == token.text() + && lt.text() == token.text() { *tag = true; tagged_one = true; @@ -250,18 +250,15 @@ fn tag_generics_in_variant(ty: &ast::Type, generics: &mut [(ast::GenericParam, b } param if matches!(token.kind(), T![ident]) => { if match param { - ast::GenericParam::ConstParam(konst) => konst - .name() - .map(|name| name.text().as_str() == token.text()) - .unwrap_or_default(), - ast::GenericParam::TypeParam(ty) => ty - .name() - .map(|name| name.text().as_str() == token.text()) - .unwrap_or_default(), - ast::GenericParam::LifetimeParam(lt) => lt - .lifetime() - .map(|lt| lt.text().as_str() == token.text()) - .unwrap_or_default(), + ast::GenericParam::ConstParam(konst) => { + konst.name().map(|name| name.text() == token.text()).unwrap_or_default() + } + ast::GenericParam::TypeParam(ty) => { + ty.name().map(|name| name.text() == token.text()).unwrap_or_default() + } + ast::GenericParam::LifetimeParam(lt) => { + lt.lifetime().map(|lt| lt.text() == token.text()).unwrap_or_default() + } } { *tag = true; tagged_one = true; diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs index 3c3fde80f99e7..3bec992252861 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs @@ -67,7 +67,7 @@ fn gen_fn(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> Option<()> { return None; } - let fn_name = &*name_ref.text(); + let fn_name = name_ref.text(); let TargetInfo { target_module, adt_info, target, file } = fn_target_info(ctx, path, &call, fn_name)?; @@ -159,7 +159,7 @@ fn gen_method(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> Option<()> { let (impl_, file) = if let Some(impl_) = cursor_impl { (Some(impl_), ctx.vfs_file_id()) } else { - get_adt_source(ctx, &adt, fn_name.text().as_str())? + get_adt_source(ctx, &adt, fn_name.text())? }; let target = get_method_target(ctx, &impl_, &adt)?; 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 d5629e2e7e073..23ce72670332d 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 @@ -4,14 +4,11 @@ use ide_db::{ RootDatabase, famous_defs::FamousDefs, helpers::mod_path_to_ast_with_factory, imports::import_assets::item_for_path_search, }; -use syntax::syntax_editor::{Position, SyntaxEditor}; -use syntax::{ - TokenText, - ast::{ - self, AstNode, HasAttrs, HasGenericParams, HasName, edit::AstNodeEdit, - syntax_factory::SyntaxFactory, - }, +use syntax::ast::{ + self, AstNode, HasAttrs, HasGenericParams, HasName, edit::AstNodeEdit, + syntax_factory::SyntaxFactory, }; +use syntax::syntax_editor::{Position, SyntaxEditor}; use crate::{ AssistId, @@ -71,8 +68,7 @@ pub(crate) fn generate_single_field_struct_from( return None; } - let main_field_name = - names.as_ref().map_or(TokenText::borrowed("value"), |names| names[main_field_i].text()); + let main_field_name = names.as_ref().map_or("value", |names| names[main_field_i].text()); let main_field_ty = types[main_field_i].clone(); acc.add( @@ -161,7 +157,7 @@ pub(crate) fn generate_single_field_struct_from( fn make_adt_constructor( names: Option<&[ast::Name]>, constructors: Vec>, - main_field_name: &TokenText<'_>, + main_field_name: &str, make: &SyntaxFactory, ) -> ast::Expr { if let Some(names) = names { diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/reorder_impl_items.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/reorder_impl_items.rs index 658947abe135f..ed5a372e546db 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/reorder_impl_items.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/reorder_impl_items.rs @@ -82,7 +82,7 @@ pub(crate) fn reorder_impl_items(acc: &mut Assists, ctx: &AssistContext<'_, '_>) ast::AssocItem::MacroCall(_) => None, }; - name.and_then(|n| ranks.get(n.text().as_str().trim_start_matches("r#")).copied()) + name.and_then(|n| ranks.get(n.text().trim_start_matches("r#")).copied()) .unwrap_or(usize::MAX) }) .collect(); 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 670a030255cfc..ad46c61935f65 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs @@ -344,7 +344,7 @@ fn invert_special_case(make: &SyntaxFactory, expr: &ast::Expr) -> Option "is_none", "is_none" => "is_some", "is_ok" => "is_err", 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 c0ddcb950cbac..277b5bd8dfc5e 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 @@ -21,7 +21,7 @@ pub(crate) fn gen_trait_fn_body( trait_ref: Option>, ) -> Option { let _ = func.body()?; - match trait_path.segment()?.name_ref()?.text().as_str() { + match trait_path.segment()?.name_ref()?.text() { "Clone" => { stdx::always!(func.name().is_some_and(|name| name.text() == "clone")); gen_clone_impl(make, adt) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute.rs index 109ebce01c9af..c2d7cb98cf9a8 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute.rs @@ -40,7 +40,6 @@ pub(crate) fn complete_known_attribute_input( let path = attribute.path()?; let segments = path.segments().map(|s| s.name_ref()).collect::>>()?; let segments = segments.iter().map(|n| n.text()).collect::>(); - let segments = segments.iter().map(|t| t.as_str()).collect::>(); let tt = attribute.token_tree()?; match segments.as_slice() { diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/repr.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/repr.rs index 63cddb365e301..73ba847e7e61f 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/repr.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/repr.rs @@ -23,7 +23,7 @@ pub(super) fn complete_repr( }) .any(|it| { let text = it.text(); - lookup.unwrap_or(label) == text || collides.contains(&text.as_str()) + lookup.unwrap_or(label) == text || collides.contains(&text) }); if repr_already_annotated { continue; 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 5a3a3ac39cb2f..34b53e5e5bfea 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 @@ -365,7 +365,7 @@ fn suggest_receiver_name( match receiver { ast::Expr::PathExpr(path) => { if let Some(name) = path.path().and_then(|it| it.as_single_name_ref()) { - return placeholder(name.text().as_str()); + return placeholder(name.text()); } } ast::Expr::RefExpr(it) => { 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 27e3ed6bdb52e..0235389763080 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 @@ -314,7 +314,7 @@ impl ImportGroup { PathSegmentKind::SelfKw => ImportGroup::ThisModule, PathSegmentKind::SuperKw => ImportGroup::SuperModule, PathSegmentKind::CrateKw => ImportGroup::ThisCrate, - PathSegmentKind::Name(name) => match name.text().as_str() { + PathSegmentKind::Name(name) => match name.text() { "std" => ImportGroup::Std, "core" => ImportGroup::Std, _ => ImportGroup::ExternCrate, diff --git a/src/tools/rust-analyzer/crates/ide-db/src/imports/merge_imports.rs b/src/tools/rust-analyzer/crates/ide-db/src/imports/merge_imports.rs index 59099056f5c5b..f9251458fd026 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/imports/merge_imports.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/imports/merge_imports.rs @@ -582,8 +582,8 @@ fn path_segment_cmp(a: &ast::PathSegment, b: &ast::PathSegment) -> Ordering { (Some(_), None) => Ordering::Greater, (None, Some(_)) => Ordering::Less, (Some(a_name), Some(b_name)) => { - let a_text = a_name.as_str().trim_start_matches("r#"); - let b_text = b_name.as_str().trim_start_matches("r#"); + let a_text = a_name.trim_start_matches("r#"); + let b_text = b_name.trim_start_matches("r#"); version_sort::version_sort(a_text, b_text) } } @@ -614,15 +614,13 @@ fn use_tree_cmp_by_tree_list_glob_or_alias( .name() .as_ref() .map(ast::Name::text) - .as_ref() - .map_or("_", |a_name| a_name.as_str().trim_start_matches("r#")) + .map_or("_", |a_name| a_name.trim_start_matches("r#")) .cmp( b_rename .name() .as_ref() .map(ast::Name::text) - .as_ref() - .map_or("_", |b_name| b_name.as_str().trim_start_matches("r#")), + .map_or("_", |b_name| b_name.trim_start_matches("r#")), ), }, }; 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 101046cf54436..55d602ce2e4f0 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 @@ -651,7 +651,7 @@ fn find_trait_for_assoc_item( }); for name in names { - if assoc_item_name.as_str() == name.as_str() { + if assoc_item_name == name.as_str() { // It is fine to return the first match because in case of // multiple possibilities, the exact trait must be disambiguated // in the definition of trait being implemented, so this search 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 c8607a8099b91..09a270c143888 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 @@ -102,8 +102,7 @@ impl RaFixtureAnalysis { else { return false; }; - segment1.text_non_mutable() == "rust_analyzer" - && segment2.text_non_mutable() == "rust_fixture" + segment1.text() == "rust_analyzer" && segment2.text() == "rust_fixture" }) }); if !has_rust_fixture_attr { diff --git a/src/tools/rust-analyzer/crates/ide-db/src/rename.rs b/src/tools/rust-analyzer/crates/ide-db/src/rename.rs index b89c2fdf4ad87..775b85c479662 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/rename.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/rename.rs @@ -734,7 +734,7 @@ fn source_edit_from_def<'db>( // special cases required for renaming fields/locals in Record patterns if let Some(pat_field) = pat.syntax().parent().and_then(ast::RecordPatField::cast) { if let Some(name_ref) = pat_field.name_ref() { - if new_name.as_str() == name_ref.text().as_str().trim_start_matches("r#") + if new_name.as_str() == name_ref.text().trim_start_matches("r#") && pat.at_token().is_none() { // Foo { field: ref mut local } -> Foo { ref mut field } 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 b688cb188d58d..6a492a54798ce 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/search.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/search.rs @@ -119,13 +119,13 @@ impl FileReferenceNode { _ => None, } } - pub fn text(&self) -> syntax::TokenText<'_> { + pub fn text(&self) -> &str { match self { FileReferenceNode::NameRef(name_ref) => name_ref.text(), FileReferenceNode::Name(name) => name.text(), FileReferenceNode::Lifetime(lifetime) => lifetime.text(), FileReferenceNode::FormatStringEntry(it, range) => { - syntax::TokenText::borrowed(&it.text()[*range - it.syntax().text_range().start()]) + &it.text()[*range - it.syntax().text_range().start()] } } } @@ -751,7 +751,7 @@ impl<'a, 'db> FindUsages<'a, 'db> { insert_type_alias( sema.db, &mut to_process, - name.text().as_str(), + name.text(), def.into(), ); } else { @@ -814,7 +814,7 @@ impl<'a, 'db> FindUsages<'a, 'db> { insert_type_alias( sema.db, &mut to_process, - name.text().as_str(), + name.text(), def.into(), ); } else { diff --git a/src/tools/rust-analyzer/crates/ide-ssr/src/resolving.rs b/src/tools/rust-analyzer/crates/ide-ssr/src/resolving.rs index 3dbba0ff2dab4..9d6079202a5b6 100644 --- a/src/tools/rust-analyzer/crates/ide-ssr/src/resolving.rs +++ b/src/tools/rust-analyzer/crates/ide-ssr/src/resolving.rs @@ -155,7 +155,7 @@ impl<'db> Resolver<'_, 'db> { fn path_contains_placeholder(&self, path: &ast::Path) -> bool { if let Some(segment) = path.segment() && let Some(name_ref) = segment.name_ref() - && self.placeholders_by_stand_in.contains_key(name_ref.text().as_str()) + && self.placeholders_by_stand_in.contains_key(name_ref.text()) { return true; } diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/lifetime.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/lifetime.rs index 7a8a6eb84a5fa..89a2d9fa97eba 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/lifetime.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/lifetime.rs @@ -204,7 +204,7 @@ fn hints_( mut is_trivial: bool, ) -> Option<()> { let is_elided = |lt: &Option| match lt { - Some(lt) => matches!(lt.text().as_str(), "'_"), + Some(lt) => matches!(lt.text(), "'_"), None => true, }; let self_param = self_param.and_then(|it| { @@ -298,12 +298,12 @@ fn hints_( potential_lt_refs.for_each(|(name, ..)| { let name = match name { Some(it) if config.param_names_for_lifetime_elision_hints => { - if let Some(c) = used_names.get_mut(it.text().as_str()) { + if let Some(c) = used_names.get_mut(it.text()) { *c += 1; - format_smolstr!("'{}{c}", it.text().as_str()) + format_smolstr!("'{}{c}", it.text()) } else { - used_names.insert(it.text().as_str().into(), 0); - format_smolstr!("'{}", it.text().as_str()) + used_names.insert(it.text().into(), 0); + format_smolstr!("'{}", it.text()) } } _ => gen_idx_name(), @@ -316,7 +316,7 @@ fn hints_( let output = match potential_lt_refs.as_slice() { [(_, _, lifetime, _), ..] if self_param.is_some() || potential_lt_refs.len() == 1 => { match lifetime { - Some(lt) => match lt.text().as_str() { + Some(lt) => match lt.text() { "'_" => allocated_lifetimes.first().cloned(), "'static" => None, name => Some(name.into()), 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 5da8f2e1624a3..71cc17755288e 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 @@ -294,7 +294,7 @@ pub(super) fn is_argument_similar_to_param_name( debug_assert!(!argument.is_empty()); debug_assert!(!param_name.is_empty()); let param_name = param_name.split('_'); - let argument = argument.iter().flat_map(|it| it.text_non_mutable().split('_')); + let argument = argument.iter().flat_map(|it| it.text().split('_')); let argument = argument.map(|it| it.strip_prefix("r#").unwrap_or(it)); let prefix_match = zip(argument.clone(), param_name.clone()) @@ -313,7 +313,7 @@ pub(super) fn get_segment_representation( let receiver = method_call_expr.receiver().and_then(|expr| get_segment_representation(&expr)); let name_ref = method_call_expr.name_ref()?; - if INSIGNIFICANT_METHOD_NAMES.contains(&name_ref.text().as_str()) { + if INSIGNIFICANT_METHOD_NAMES.contains(&name_ref.text()) { return receiver; } Some(Either::Left(match receiver { diff --git a/src/tools/rust-analyzer/crates/span/src/ast_id.rs b/src/tools/rust-analyzer/crates/span/src/ast_id.rs index 83a6748c01eaf..369f383dbb935 100644 --- a/src/tools/rust-analyzer/crates/span/src/ast_id.rs +++ b/src/tools/rust-analyzer/crates/span/src/ast_id.rs @@ -381,8 +381,8 @@ fn impl_ast_id( let self_ty_name = type_as_name(node.self_ty()); let trait_name = type_as_name(node.trait_()); let data = ImplFileAstId { - self_ty_name: self_ty_name.as_ref().map(|it| it.text_non_mutable()), - trait_name: trait_name.as_ref().map(|it| it.text_non_mutable()), + self_ty_name: self_ty_name.as_ref().map(|it| it.text()), + trait_name: trait_name.as_ref().map(|it| it.text()), }; Some(index_map.new_id(ErasedFileAstIdKind::Impl, data)) } else { @@ -473,7 +473,7 @@ macro_rules! register_has_name_ast_id { $( ast::$ident(node) => { let name = node.$name_method(); - let name = name.as_ref().map_or("", |it| it.text_non_mutable()); + let name = name.as_ref().map_or("", |it| it.text()); let result = ErasedHasNameFileAstId { name, }; @@ -519,7 +519,7 @@ macro_rules! register_assoc_item_ast_id { $( ast::$ident(node) => { let name = $name_callback(node); - let name = name.as_ref().map_or("", |it| it.text_non_mutable()); + let name = name.as_ref().map_or("", |it| it.text()); let properties = ErasedHasNameFileAstId { name, }; 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 00aeb1dfebf8d..43bca6ed9e63d 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 @@ -11,7 +11,7 @@ use rowan::{GreenNodeData, GreenTokenData}; use smallvec::{SmallVec, smallvec}; use crate::{ - NodeOrToken, SmolStr, SyntaxElement, SyntaxElementChildren, SyntaxToken, T, TokenText, + NodeOrToken, SmolStr, SyntaxElement, SyntaxElementChildren, SyntaxToken, T, ast::{ self, AstNode, AstToken, HasAttrs, HasGenericArgs, HasGenericParams, HasName, HasTypeBounds, SyntaxNode, support, @@ -22,30 +22,22 @@ use crate::{ use super::{GenericParam, RangeItem, RangeOp}; impl ast::Lifetime { - pub fn text(&self) -> TokenText<'_> { + pub fn text(&self) -> &str { text_of_first_token(self.syntax()) } } impl ast::Name { - pub fn text(&self) -> TokenText<'_> { + pub fn text(&self) -> &str { text_of_first_token(self.syntax()) } - - pub fn text_non_mutable(&self) -> &str { - first_token(self.syntax().green()).text() - } } impl ast::NameRef { - pub fn text(&self) -> TokenText<'_> { + pub fn text(&self) -> &str { text_of_first_token(self.syntax()) } - pub fn text_non_mutable(&self) -> &str { - first_token(self.syntax().green()).text() - } - pub fn as_tuple_field(&self) -> Option { self.text().parse().ok() } @@ -55,12 +47,12 @@ impl ast::NameRef { } } -fn first_token(green_ref: &GreenNodeData) -> &GreenTokenData { - green_ref.children().next().and_then(NodeOrToken::into_token).unwrap() -} +fn text_of_first_token(node: &SyntaxNode) -> &str { + fn first_token(green_ref: &GreenNodeData) -> &GreenTokenData { + green_ref.children().next().and_then(NodeOrToken::into_token).unwrap() + } -fn text_of_first_token(node: &SyntaxNode) -> TokenText<'_> { - TokenText::borrowed(first_token(node.green()).text()) + first_token(node.green()).text() } fn into_comma(it: NodeOrToken) -> Option { @@ -604,7 +596,7 @@ impl NameLike { _ => None, } } - pub fn text(&self) -> TokenText<'_> { + pub fn text(&self) -> &str { match self { NameLike::NameRef(name_ref) => name_ref.text(), NameLike::Name(name) => name.text(), @@ -676,7 +668,7 @@ impl ast::AstNode for NameOrNameRef { } impl NameOrNameRef { - pub fn text(&self) -> TokenText<'_> { + pub fn text(&self) -> &str { match self { NameOrNameRef::Name(name) => name.text(), NameOrNameRef::NameRef(name_ref) => name_ref.text(), diff --git a/src/tools/rust-analyzer/crates/syntax/src/lib.rs b/src/tools/rust-analyzer/crates/syntax/src/lib.rs index 614678536a512..204ebbd2633b3 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/lib.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/lib.rs @@ -30,7 +30,6 @@ mod syntax_error; mod syntax_node; #[cfg(test)] mod tests; -mod token_text; mod validation; pub mod algo; @@ -54,7 +53,6 @@ pub use crate::{ PreorderWithTokens, RustLanguage, SyntaxElement, SyntaxElementChildren, SyntaxNode, SyntaxNodeChildren, SyntaxToken, SyntaxTreeBuilder, }, - token_text::TokenText, }; pub use parser::{Edition, SyntaxKind, T}; pub use rowan::{ diff --git a/src/tools/rust-analyzer/crates/syntax/src/token_text.rs b/src/tools/rust-analyzer/crates/syntax/src/token_text.rs deleted file mode 100644 index e69deb49ce142..0000000000000 --- a/src/tools/rust-analyzer/crates/syntax/src/token_text.rs +++ /dev/null @@ -1,102 +0,0 @@ -//! Yet another version of owned string, backed by a syntax tree token. - -use std::{cmp::Ordering, fmt, ops}; - -use rowan::GreenToken; -use smol_str::SmolStr; - -pub struct TokenText<'a>(pub(crate) Repr<'a>); - -pub(crate) enum Repr<'a> { - Borrowed(&'a str), - Owned(GreenToken), -} - -impl<'a> TokenText<'a> { - pub fn borrowed(text: &'a str) -> Self { - TokenText(Repr::Borrowed(text)) - } - - pub(crate) fn owned(green: GreenToken) -> Self { - TokenText(Repr::Owned(green)) - } - - pub fn as_str(&self) -> &str { - match &self.0 { - &Repr::Borrowed(it) => it, - Repr::Owned(green) => green.text(), - } - } -} - -impl ops::Deref for TokenText<'_> { - type Target = str; - - fn deref(&self) -> &str { - self.as_str() - } -} -impl AsRef for TokenText<'_> { - fn as_ref(&self) -> &str { - self.as_str() - } -} - -impl From> for String { - fn from(token_text: TokenText<'_>) -> Self { - token_text.as_str().into() - } -} - -impl From> for SmolStr { - fn from(token_text: TokenText<'_>) -> Self { - SmolStr::new(token_text.as_str()) - } -} - -impl PartialEq<&'_ str> for TokenText<'_> { - fn eq(&self, other: &&str) -> bool { - self.as_str() == *other - } -} -impl PartialEq> for &'_ str { - fn eq(&self, other: &TokenText<'_>) -> bool { - other == self - } -} -impl PartialEq for TokenText<'_> { - fn eq(&self, other: &String) -> bool { - self.as_str() == other.as_str() - } -} -impl PartialEq> for String { - fn eq(&self, other: &TokenText<'_>) -> bool { - other == self - } -} -impl PartialEq for TokenText<'_> { - fn eq(&self, other: &TokenText<'_>) -> bool { - self.as_str() == other.as_str() - } -} -impl Eq for TokenText<'_> {} -impl Ord for TokenText<'_> { - fn cmp(&self, other: &Self) -> Ordering { - self.as_str().cmp(other.as_str()) - } -} -impl PartialOrd for TokenText<'_> { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} -impl fmt::Display for TokenText<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(self.as_str(), f) - } -} -impl fmt::Debug for TokenText<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self.as_str(), f) - } -} From 29133dfb934d92fc5cdfadea05d6d8063b154e89 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Sun, 2 Aug 2026 09:57:10 +0530 Subject: [PATCH 55/63] Fix tokenText removal fallout --- .../rust-analyzer/crates/hir-def/src/attrs.rs | 2 +- .../crates/hir-def/src/expr_store/lower.rs | 8 ++++---- .../crates/hir-def/src/expr_store/lower/asm.rs | 4 ++-- .../hir-def/src/expr_store/lower/generics.rs | 2 +- .../hir-expand/src/builtin/derive_macro.rs | 16 ++++++++-------- .../rust-analyzer/crates/hir-expand/src/name.rs | 4 ++-- .../crates/hir/src/source_analyzer.rs | 6 +++--- .../src/handlers/convert_closure_to_fn.rs | 2 +- .../src/handlers/convert_match_to_let_else.rs | 3 +-- .../src/handlers/convert_range_for_to_while.rs | 2 +- .../convert_tuple_struct_to_named_struct.rs | 4 ++-- .../ide-assists/src/handlers/extract_function.rs | 4 ++-- .../handlers/extract_struct_from_enum_variant.rs | 2 +- .../src/handlers/extract_type_alias.rs | 9 ++++----- .../src/handlers/generate_blanket_trait_impl.rs | 4 ++-- .../generate_default_from_enum_variant.rs | 2 +- .../src/handlers/generate_enum_is_method.rs | 2 +- .../handlers/generate_enum_projection_method.rs | 2 +- .../src/handlers/generate_enum_variant.rs | 2 +- .../src/handlers/generate_function.rs | 2 +- .../src/handlers/generate_getter_or_setter.rs | 4 ++-- .../ide-assists/src/handlers/generate_impl.rs | 2 +- .../src/handlers/generate_mut_trait_impl.rs | 2 +- .../generate_single_field_struct_from.rs | 8 ++++---- .../src/handlers/generate_trait_from_impl.rs | 4 ++-- .../src/handlers/inline_type_alias.rs | 2 +- .../ide-assists/src/handlers/merge_match_arms.rs | 2 +- .../src/handlers/replace_method_eager_lazy.rs | 4 ++-- .../crates/ide-assists/src/utils.rs | 4 ++-- .../crates/ide-db/src/imports/import_assets.rs | 6 +++--- .../crates/ide-db/src/path_transform.rs | 2 +- .../rust-analyzer/crates/ide-db/src/rename.rs | 4 ++-- .../rust-analyzer/crates/ide/src/doc_links.rs | 2 +- .../crates/ide/src/file_structure.rs | 2 +- .../crates/ide/src/inlay_hints/param_name.rs | 6 +++--- .../crates/ide/src/navigation_target.rs | 2 +- .../rust-analyzer/crates/syntax/src/ast/edit.rs | 2 +- 37 files changed, 69 insertions(+), 71 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 d55509e2f0884..c330b374f71f9 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/attrs.rs @@ -201,7 +201,7 @@ fn match_attr_flags(attr_flags: &mut AttrFlags, attr: ast::Meta) -> ControlFlow< let segment4 = segment4.and_then(|it| it.segment()?.name_ref()); segment1.text() == "test" && segment3.is_none_or(|it| it.text() == "prelude") - && segment4.is_none_or(|it| matches!(&*it.text(), "core" | "std")) + && segment4.is_none_or(|it| matches!(it.text(), "core" | "std")) }); if is_test { attr_flags.insert(AttrFlags::IS_TEST); 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 df4fc6e531466..aaca830088e33 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 @@ -665,7 +665,7 @@ impl<'db> ExprCollector<'db> { lifetime: ast::Lifetime, ) -> LifetimeRefId { // FIXME: Keyword check? - let lifetime_ref = match &*lifetime.text() { + let lifetime_ref = match lifetime.text() { "" | "'" => LifetimeRef::Error, "'static" => LifetimeRef::Static, "'_" => LifetimeRef::Placeholder, @@ -1295,7 +1295,7 @@ impl<'db> ExprCollector<'db> { match binder.generic_param_list() { Some(gpl) => gpl .lifetime_params() - .flat_map(|lp| lp.lifetime().map(|lt| Name::new_lifetime(<.text()))) + .flat_map(|lp| lp.lifetime().map(|lt| Name::new_lifetime(lt.text()))) .collect(), None => ThinVec::default(), } @@ -3175,7 +3175,7 @@ impl<'db> ExprCollector<'db> { name: ast_label .lifetime() .as_ref() - .map_or_else(Name::missing, |lt| Name::new_lifetime(<.text())), + .map_or_else(Name::missing, |lt| Name::new_lifetime(lt.text())), }; self.alloc_label(label, AstPtr::new(&ast_label)) } @@ -3195,7 +3195,7 @@ impl<'db> ExprCollector<'db> { (hygiene_id.syntax_context().parent(self.db), expansion.def) }) }; - let name = Name::new_lifetime(&lifetime.text()); + let name = Name::new_lifetime(lifetime.text()); for (rib_idx, rib) in self.label_ribs.iter().enumerate().rev() { match &rib.kind { diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/asm.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/asm.rs index 63a0594f74c1b..fb0a5b0bf7a71 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/asm.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/asm.rs @@ -39,7 +39,7 @@ impl ExprCollector<'_> { Some(InlineAsmRegOrRegClass::Reg(Symbol::intern(string.text()))) } else { reg.name_ref().map(|name_ref| { - InlineAsmRegOrRegClass::RegClass(Symbol::intern(&name_ref.text())) + InlineAsmRegOrRegClass::RegClass(Symbol::intern(name_ref.text())) }) } }; @@ -69,7 +69,7 @@ impl ExprCollector<'_> { continue; } ast::AsmPiece::AsmOperandNamed(op) => { - let name = op.name().map(|name| Symbol::intern(&name.text())); + let name = op.name().map(|name| Symbol::intern(name.text())); if let Some(name) = &name { named_args.insert(name.clone(), slot); named_pos.insert(slot, name.clone()); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/generics.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/generics.rs index ce6e73670cba4..65877fb627f24 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/generics.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/generics.rs @@ -181,7 +181,7 @@ impl GenericParamsCollector { .map(|lifetime_param| { lifetime_param .lifetime() - .map_or_else(Name::missing, |lt| Name::new_lifetime(<.text())) + .map_or_else(Name::missing, |lt| Name::new_lifetime(lt.text())) }) .collect() }); diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin/derive_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/derive_macro.rs index ccd2d6dca29f1..63e57647283cf 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/builtin/derive_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/derive_macro.rs @@ -1184,7 +1184,7 @@ fn coerce_pointee_expand( let new_bounds = bounds.bounds().filter_map(|bound| { let new_bound = substitute_type_bound( bound.clone(), - &pointee_param_name.text(), + pointee_param_name.text(), ADDED_PARAM, ); @@ -1197,7 +1197,7 @@ fn coerce_pointee_expand( let new_bounds_target = if is_pointee { make.name_ref(ADDED_PARAM) } else { - make.name_ref(¶m_name.text()) + make.name_ref(param_name.text()) }; new_predicates.push(make.where_pred( Either::Right( @@ -1240,12 +1240,12 @@ fn coerce_pointee_expand( // If the target type references the pointee, duplicate the bound as whole. // Otherwise, duplicate only bounds that mention the pointee. if let Some(predicate_with_substituted_target) = - substitute_where_pred(&predicate, &pointee_param_name.text(), ADDED_PARAM) + substitute_where_pred(&predicate, pointee_param_name.text(), ADDED_PARAM) { new_predicates.push(predicate_with_substituted_target); } else if let Some(bounds) = predicate.type_bound_list() { let new_bounds = bounds.bounds().filter_map(|bound| { - substitute_type_bound(bound, &pointee_param_name.text(), ADDED_PARAM) + substitute_type_bound(bound, pointee_param_name.text(), ADDED_PARAM) }); new_predicates.push(make.where_pred(Either::Right(pred_target), new_bounds)); } @@ -1259,7 +1259,7 @@ fn coerce_pointee_expand( new_predicates.push( make.where_pred( Either::Right(make.ty_path_from_segments( - [make.path_segment(make.name_ref(&pointee_param_name.text()))], + [make.path_segment(make.name_ref(pointee_param_name.text()))], false, )), [make.type_bound( @@ -1294,7 +1294,7 @@ fn coerce_pointee_expand( .filter_map(|param| { Some(match param { ast::GenericParam::ConstParam(param) => { - ast::GenericArg::ConstArg(make.expr_const_value(¶m.name()?.text())) + ast::GenericArg::ConstArg(make.expr_const_value(param.name()?.text())) } ast::GenericParam::LifetimeParam(param) => { make.lifetime_arg(param.lifetime()?).into() @@ -1303,7 +1303,7 @@ fn coerce_pointee_expand( let name = if pointee_param_idx == type_param_idx { make.name_ref(ADDED_PARAM) } else { - make.name_ref(¶m.name()?.text()) + make.name_ref(param.name()?.text()) }; type_param_idx += 1; make.type_arg(make.ty_path_from_segments([make.path_segment(name)], false)) @@ -1314,7 +1314,7 @@ fn coerce_pointee_expand( make.path_from_segments( [make.generic_ty_path_segment( - make.name_ref(&struct_name.text()), + make.name_ref(struct_name.text()), self_params_for_traits, )], false, diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/name.rs b/src/tools/rust-analyzer/crates/hir-expand/src/name.rs index d91b0f378e191..7968adabbccf2 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/name.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/name.rs @@ -246,14 +246,14 @@ impl AsName for ast::NameRef { fn as_name(&self) -> Name { match self.as_tuple_field() { Some(idx) => Name::new_tuple_field(idx), - None => Name::new_root(&self.text()), + None => Name::new_root(self.text()), } } } impl AsName for ast::Name { fn as_name(&self) -> Name { - Name::new_root(&self.text()) + Name::new_root(self.text()) } } diff --git a/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs b/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs index e80567641baf3..209091683a01b 100644 --- a/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs +++ b/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs @@ -1320,7 +1320,7 @@ impl<'db> SourceAnalyzer<'db> { .first_segment() .and_then(|it| it.name_ref()) .and_then(|name_ref| { - ToolModule::by_name(db, self.resolver.krate().into(), &name_ref.text()) + ToolModule::by_name(db, self.resolver.krate().into(), name_ref.text()) .map(PathResolution::ToolModule) }) .map(|it| (it, None)), @@ -1361,7 +1361,7 @@ impl<'db> SourceAnalyzer<'db> { // in this case we have to check for inert/builtin attributes and tools and prioritize // resolution of attributes over other namespaces if let Some(name_ref) = path.as_single_name_ref() { - let builtin = BuiltinAttr::builtin(&name_ref.text()); + let builtin = BuiltinAttr::builtin(name_ref.text()); if builtin.is_some() { return builtin.map(|it| (PathResolution::BuiltinAttr(it), None)); } @@ -1411,7 +1411,7 @@ impl<'db> SourceAnalyzer<'db> { .first_segment() .and_then(|it| it.name_ref()) .and_then(|name_ref| { - ToolModule::by_name(db, self.resolver.krate().into(), &name_ref.text()) + ToolModule::by_name(db, self.resolver.krate().into(), name_ref.text()) .map(PathResolution::ToolModule) }) .map(|it| (it, None)), diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_closure_to_fn.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_closure_to_fn.rs index c9f5e0a4fbede..83effa11820b7 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_closure_to_fn.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_closure_to_fn.rs @@ -507,7 +507,7 @@ fn wrap_capture_in_deref_if_needed( capture_kind: CaptureKind, is_ref: bool, ) -> ast::Expr { - let capture_name = make.expr_path(make.path_from_text(&capture_name.text())); + let capture_name = make.expr_path(make.path_from_text(capture_name.text())); if capture_kind == CaptureKind::Move || is_ref { return capture_name; } 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 9dffdf3f367c4..db084c6ea21d7 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 @@ -148,8 +148,7 @@ 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()) - .syntax(), + make.record_pat_field(make.name_ref(name_ref.text()), binding.clone()).syntax(), ); } } else { 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 7026b5bafdc7c..ae8f626c5da8a 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 @@ -74,7 +74,7 @@ pub(crate) fn convert_range_for_to_while( let mut elements = vec![]; - let var_expr = make.expr_path(make.ident_path(&name.text())); + let var_expr = make.expr_path(make.ident_path(name.text())); let op = ast::BinaryOp::CmpOp(ast::CmpOp::Ord { ordering: ast::Ordering::Less, strict: !inclusive, 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 eb74e9107581b..0bb9bf12b12cc 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 @@ -194,7 +194,7 @@ fn process_struct_name_reference( let range = ctx.sema.original_range_opt(pat.syntax())?.range; let place = cover_edit_range(source.syntax(), range); let elements = vec![ - make.name_ref(&name.text()).syntax().clone().into(), + make.name_ref(name.text()).syntax().clone().into(), make.token(T![:]).into(), make.whitespace(" ").into(), ]; @@ -237,7 +237,7 @@ fn process_struct_name_reference( let range = ctx.sema.original_range_opt(expr.syntax())?.range; let place = cover_edit_range(source.syntax(), range); let elements = vec![ - make.name_ref(&name.text()).syntax().clone().into(), + make.name_ref(name.text()).syntax().clone().into(), make.token(T![:]).into(), make.whitespace(" ").into(), ]; 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 c2eb49dde5695..46333ed726388 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 @@ -934,7 +934,7 @@ impl FunctionBody { }; // FIXME: make trait arguments - let trait_name = trait_name.map(|name| make.ty_path(make.ident_path(&name.text())).into()); + let trait_name = trait_name.map(|name| make.ty_path(make.ident_path(name.text())).into()); let parent = self.parent()?; let parents = generic_parents(&parent); @@ -1561,7 +1561,7 @@ fn format_function<'db>( old_indent: IndentLevel, make: &SyntaxFactory, ) -> ast::Fn { - let fun_name = make.name(&fun.name.text()); + let fun_name = make.name(fun.name.text()); let params = fun.make_param_list(make, ctx, module, fun.mods.edition); let ret_ty = fun.make_ret_ty(make, ctx, module); let body = make_body(make, ctx, old_indent, fun); 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 89c5470c160ff..c1ac4f1724893 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 @@ -335,7 +335,7 @@ fn update_variant( // 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()), + None => make.ty(name.text()), }; // change from a record to a tuple field list diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_type_alias.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_type_alias.rs index 329f8325b4c12..a378256b598aa 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_type_alias.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_type_alias.rs @@ -145,7 +145,7 @@ fn collect_used_generics<'gp>( .filter_map(|it| match it { ast::GenericArg::LifetimeArg(lt) => { let lt = lt.lifetime()?; - known_generics.iter().find(find_lifetime(<.text())) + known_generics.iter().find(find_lifetime(lt.text())) } _ => None, }), @@ -157,7 +157,7 @@ fn collect_used_generics<'gp>( generics.extend( it.bounds() .filter_map(|it| it.lifetime()) - .filter_map(|lt| known_generics.iter().find(find_lifetime(<.text()))), + .filter_map(|lt| known_generics.iter().find(find_lifetime(lt.text()))), ); } } @@ -166,13 +166,12 @@ fn collect_used_generics<'gp>( generics.extend( it.bounds() .filter_map(|it| it.lifetime()) - .filter_map(|lt| known_generics.iter().find(find_lifetime(<.text()))), + .filter_map(|lt| known_generics.iter().find(find_lifetime(lt.text()))), ); } } ast::Type::RefType(ref_) => generics.extend( - ref_.lifetime() - .and_then(|lt| known_generics.iter().find(find_lifetime(<.text()))), + ref_.lifetime().and_then(|lt| known_generics.iter().find(find_lifetime(lt.text()))), ), ast::Type::ArrayType(ar) => { if let Some(ast::Expr::PathExpr(p)) = ar.const_arg().and_then(|x| x.expr()) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs index acd98aed00cee..738f461a1f2fd 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs @@ -75,12 +75,12 @@ pub(crate) fn generate_blanket_trait_impl( |builder| { let editor = builder.make_editor(traitd.syntax()); let make = editor.make(); - let namety = make.ty_path(make.path_from_text(&name.text())); + let namety = make.ty_path(make.path_from_text(name.text())); let trait_where_clause = traitd.where_clause().map(|it| it.reset_indent()); let bounds = traitd.type_bound_list().and_then(|list| exclude_sized(make, list)); let is_unsafe = traitd.unsafe_token().is_some(); let thisname = this_name(make, &traitd); - let thisty = make.ty_path(make.path_from_text(&thisname.text())); + let thisty = make.ty_path(make.path_from_text(thisname.text())); let indent = traitd.indent_level(); let gendecl = make.generic_param_list([GenericParam::TypeParam(make.type_param( diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_enum_variant.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_enum_variant.rs index 713d6a3fb708c..07c191b0eb666 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_enum_variant.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_default_from_enum_variant.rs @@ -76,7 +76,7 @@ pub(crate) fn generate_default_from_enum_variant( fn default_impl(variant_name: ast::Name, adt: &ast::Adt, make: &SyntaxFactory) -> ast::Impl { let impl_ = utils::generate_trait_impl_intransitive(make, adt, make.ty("Default")); - let fn_ = default_fn(&variant_name.text(), make); + let fn_ = default_fn(variant_name.text(), make); let (impl_editor, impl_) = SyntaxEditor::with_ast_node(&impl_); impl_ diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_is_method.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_is_method.rs index 53e77b49474c4..5e2ee772b9a51 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_is_method.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_is_method.rs @@ -141,7 +141,7 @@ impl Method { }; let variant_name = variant.name()?; - let fn_name = format!("is_{}", to_lower_snake_case(&variant_name.text())); + let fn_name = format!("is_{}", to_lower_snake_case(variant_name.text())); Some(Method { pattern_suffix, fn_name, variant_name }) } } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_projection_method.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_projection_method.rs index 8a194ae02bff0..479143c133353 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_projection_method.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_projection_method.rs @@ -219,7 +219,7 @@ impl Method { fn new(variant: &ast::Variant, fn_name_prefix: &str) -> Option { use itertools::Itertools as _; let variant_name = variant.name()?; - let fn_name = format!("{fn_name_prefix}_{}", to_lower_snake_case(&variant_name.text())); + let fn_name = format!("{fn_name_prefix}_{}", to_lower_snake_case(variant_name.text())); match variant.kind() { ast::StructKind::Record(record) => { diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_variant.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_variant.rs index fb43e3eaa37e0..73837f486cb30 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_variant.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_enum_variant.rs @@ -62,7 +62,7 @@ pub(crate) fn generate_enum_variant(acc: &mut Assists, ctx: &AssistContext<'_, ' let editor = builder.make_editor(enum_node.syntax()); let make = editor.make(); let field_list = parent.make_field_list(ctx, make); - let variant = make.variant(None, make.name(&name_ref.text()), field_list, None); + let variant = make.variant(None, make.name(name_ref.text()), field_list, None); if let Some(it) = enum_node.variant_list() { it.add_variant(&editor, &variant); } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs index 3bec992252861..13096c6efc37a 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs @@ -269,7 +269,7 @@ impl FunctionBuilder { // If generated function has the name "new" and is an associated function, we generate fn body // as a constructor and assume a "Self" return type. if let Some(body) = - make_fn_body_as_new_function(make, ctx, &fn_name.text(), adt_info, target_edition) + make_fn_body_as_new_function(make, ctx, fn_name.text(), adt_info, target_edition) { ret_type = Some(make.ret_type(make.ty_path(make.ident_path("Self")).into())); should_focus_return_type = false; 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 7e5d5cec71bc5..b21e60876296f 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 @@ -225,7 +225,7 @@ fn generate_getter_from_info( ( make.ty_ref(record_field_info.field_ty.clone(), true), make.expr_ref( - make.expr_field(self_expr, &record_field_info.field_name.text()).into(), + make.expr_field(self_expr, record_field_info.field_name.text()).into(), true, ), ) @@ -250,7 +250,7 @@ fn generate_getter_from_info( make.expr_ref( make.expr_field( make.expr_path(make.ident_path("self")), - &record_field_info.field_name.text(), + record_field_info.field_name.text(), ) .into(), false, 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 ab0eb56fcf19d..ecff6267bb995 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 @@ -185,7 +185,7 @@ pub(crate) fn generate_impl_trait(acc: &mut Assists, ctx: &AssistContext<'_, '_> None, None, false, - make.ty(&name.text()), + make.ty(name.text()), make.ty_placeholder(), None, None, 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 fd095dd9b2aab..6858b62f8d22d 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 @@ -210,7 +210,7 @@ fn get_trait_mut(apply_trait: &hir::Trait, famous: FamousDefs<'_, '_>) -> Option } fn process_method_name(name: ast::Name) -> Option<(ast::Name, &'static str)> { - let new_name = match &*name.text() { + let new_name = match name.text() { "index" => "index_mut", "as_ref" => "as_mut", "borrow" => "borrow_mut", 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 23ce72670332d..242712ff2ed6d 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 @@ -88,10 +88,10 @@ pub(crate) fn generate_single_field_struct_from( 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, make); + make_adt_constructor(names.as_deref(), constructors, main_field_name, make); let body = make.block_expr([], Some(constructor)); let fn_ = make @@ -104,7 +104,7 @@ pub(crate) fn generate_single_field_struct_from( make.param_list( None, [make.param( - make.path_pat(make.path_from_text(&main_field_name)), + make.path_pat(make.path_from_text(main_field_name)), main_field_ty, )], ), @@ -162,7 +162,7 @@ fn make_adt_constructor( ) -> 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), + |(name, initializer)| make.record_expr_field(make.name_ref(name.text()), initializer), )); make.record_expr(make.path_from_text("Self"), fields).into() } else { 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 12afd9ae6affa..354447cf3356e 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 @@ -117,7 +117,7 @@ pub(crate) fn generate_trait_from_impl( let params = used_params(&impl_ast, make, ctx); let trait_ast = make.trait_( false, - &trait_name(&impl_assoc_items, make).text(), + trait_name(&impl_assoc_items, make).text(), params.clone(), impl_ast.where_clause(), trait_items, @@ -204,7 +204,7 @@ fn trait_name(items: &ast::AssocItemList, make: &SyntaxFactory) -> ast::Name { fn_names .next() .and_then(|name| { - fn_names.next().is_none().then(|| make.name(&stdx::to_camel_case(&name.text()))) + fn_names.next().is_none().then(|| make.name(&stdx::to_camel_case(name.text()))) }) .unwrap_or_else(|| make.name("NewTrait")) } 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 bb76e2743c377..f5d5400404cf0 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 @@ -140,7 +140,7 @@ pub(crate) fn inline_type_alias(acc: &mut Assists, ctx: &AssistContext<'_, '_>) let src = adt.source(ctx.db())?.value; let name = src.name()?; let generic_params = src.generic_param_list(); - let name_ref = make.name_ref(&name.text()); + let name_ref = make.name_ref(name.text()); let segment = match generic_params { Some(params) => { make.path_segment_generics(name_ref, params.to_generic_args(&make)) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/merge_match_arms.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/merge_match_arms.rs index f41769150c042..5060886cfa9ce 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/merge_match_arms.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/merge_match_arms.rs @@ -165,7 +165,7 @@ fn get_arm_types<'db>( { let pat_type = ctx.sema.type_of_binding_in_pat(ident_pat); - map.insert(name.text().to_string(), pat_type); + map.insert(name.text().to_owned(), pat_type); } } _ => (), diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_method_eager_lazy.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_method_eager_lazy.rs index 17ee8597c1020..a414db0a6c8d0 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_method_eager_lazy.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_method_eager_lazy.rs @@ -39,7 +39,7 @@ pub(crate) fn replace_with_lazy_method( let (_, receiver_ty) = callable.receiver_param(ctx.sema.db)?; let n_params = callable.n_params() + 1; - let method_name_lazy = lazy_method_name(&method_name.text()); + let method_name_lazy = lazy_method_name(method_name.text()); receiver_ty.iterate_method_candidates_with_traits( ctx.sema.db, @@ -156,7 +156,7 @@ pub(crate) fn replace_with_eager_method( } let method_name_text = method_name.text(); - let method_name_eager = eager_method_name(&method_name_text)?; + let method_name_eager = eager_method_name(method_name_text)?; receiver_ty.iterate_method_candidates_with_traits( ctx.sema.db, 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 ad46c61935f65..388aac19b40e8 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs @@ -550,7 +550,7 @@ fn has_any_fn(imp: &ast::Impl, names: &[String]) -> bool { for item in il.assoc_items() { if let ast::AssocItem::Fn(f) = item && let Some(name) = f.name() - && names.iter().any(|n| n.eq_ignore_ascii_case(&name.text())) + && names.iter().any(|n| n.eq_ignore_ascii_case(name.text())) { return true; } @@ -664,7 +664,7 @@ fn generate_impl_inner( .zip(generic_params.as_ref()) .and_then(|(trait_, params)| generic_param_associated_bounds(make, adt, trait_, params)); - let ty: ast::Type = make.ty_path(make.ident_path(&adt.name().unwrap().text())).into(); + let ty: ast::Type = make.ty_path(make.ident_path(adt.name().unwrap().text())).into(); let cfg_attrs = adt.attrs().filter(|attr| matches!(attr.meta(), Some(ast::Meta::CfgMeta(_)))); match trait_ { 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 f5dff47acf9ab..422648c8d6c55 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 @@ -979,7 +979,7 @@ impl<'db> ImportCandidate<'db> { return None; } let after = std::iter::successors(path.parent_path(), |it| it.parent_path()) - .map(|seg| seg.segment()?.name_ref().map(|name| Name::new_root(&name.text()))) + .map(|seg| seg.segment()?.name_ref().map(|name| Name::new_root(name.text()))) .collect::>()?; path_import_candidate( sema, @@ -993,7 +993,7 @@ impl<'db> ImportCandidate<'db> { fn for_name(sema: &Semantics<'db, RootDatabase>, name: &ast::Name) -> Option { if sema .scope(name.syntax())? - .speculative_resolve(&make::ext::ident_path(&name.text())) + .speculative_resolve(&make::ext::ident_path(name.text())) .is_some() { return None; @@ -1033,7 +1033,7 @@ fn path_import_candidate<'db>( if qualifier.first_qualifier().is_none_or(|it| sema.resolve_path(&it).is_none()) { let qualifier = qualifier .segments() - .map(|seg| seg.name_ref().map(|name| Name::new_root(&name.text()))) + .map(|seg| seg.name_ref().map(|name| Name::new_root(name.text()))) .collect::>>()?; ImportCandidate::Path(PathImportCandidate { qualifier, 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 55d602ce2e4f0..ff32badd7f14a 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 @@ -536,7 +536,7 @@ impl Ctx<'_> { let name = ident_pat.name()?; let make = editor.make(); - let temp_path = make.path_from_text(&name.text()); + let temp_path = make.path_from_text(name.text()); let resolution = self.source_scope.speculative_resolve(&temp_path)?; diff --git a/src/tools/rust-analyzer/crates/ide-db/src/rename.rs b/src/tools/rust-analyzer/crates/ide-db/src/rename.rs index 775b85c479662..16224ae5ee0b2 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/rename.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/rename.rs @@ -459,7 +459,7 @@ fn rename_field_constructors( }; expr.record_expr_field_list()?.fields().find_map(|record_field| { if record_field.name_ref().is_none() - && Name::new_root(&record_field.field_name()?.text()) == old_name + && Name::new_root(record_field.field_name()?.text()) == old_name && let ast::Expr::PathExpr(field_name) = record_field.expr()? { field_name.path() @@ -747,7 +747,7 @@ fn source_edit_from_def<'db>( .text_range() .cover_offset(pat.syntax().text_range().start()), ); - edit.replace(name_range, name_ref.text().to_string()); + edit.replace(name_range, name_ref.text().to_owned()); } else { // Foo { field: ref mut local @ local 2} -> Foo { field: ref mut new_name @ local2 } // Foo { field: ref mut local } -> Foo { field: ref mut new_name } 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 70d05cd3b5dcc..de8cf971da0eb 100644 --- a/src/tools/rust-analyzer/crates/ide/src/doc_links.rs +++ b/src/tools/rust-analyzer/crates/ide/src/doc_links.rs @@ -282,7 +282,7 @@ pub(crate) fn token_as_doc_comment(doc_token: &SyntaxToken) -> Option TextSize::try_from(comment.prefix().len()).ok(), ast::String(string) => { doc_token.parent_ancestors().find_map(ast::Attr::cast).filter(|attr| attr.simple_name().as_deref() == Some("doc"))?; - if doc_token.parent_ancestors().find_map(ast::MacroCall::cast).filter(|mac| mac.path().and_then(|p| p.segment()?.name_ref()).as_ref().map(|n| n.text()).as_deref() == Some("include_str")).is_some() { + if doc_token.parent_ancestors().find_map(ast::MacroCall::cast).filter(|mac| mac.path().and_then(|p| p.segment()?.name_ref()).as_ref().map(|n| n.text()) == Some("include_str")).is_some() { return None; } string.open_quote_text_range().map(|it| it.len()) diff --git a/src/tools/rust-analyzer/crates/ide/src/file_structure.rs b/src/tools/rust-analyzer/crates/ide/src/file_structure.rs index 21254fc4d6a22..1a85342dc905c 100644 --- a/src/tools/rust-analyzer/crates/ide/src/file_structure.rs +++ b/src/tools/rust-analyzer/crates/ide/src/file_structure.rs @@ -106,7 +106,7 @@ fn structure_node(node: &SyntaxNode, config: &FileStructureConfig) -> Option bool { (|| match sema.resolve_path(path)? { hir::PathResolution::Def(hir::ModuleDef::Adt(_)) => { - Some(to_lower_snake_case(&path.segment()?.name_ref()?.text()) == param_name) + Some(to_lower_snake_case(path.segment()?.name_ref()?.text()) == param_name) } hir::PathResolution::Def(hir::ModuleDef::Function(_) | hir::ModuleDef::EnumVariant(_)) => { - if to_lower_snake_case(&path.segment()?.name_ref()?.text()) == param_name { + if to_lower_snake_case(path.segment()?.name_ref()?.text()) == param_name { return Some(true); } let qual = path.qualifier()?; match sema.resolve_path(&qual)? { hir::PathResolution::Def(hir::ModuleDef::Adt(_)) => { - Some(to_lower_snake_case(&qual.segment()?.name_ref()?.text()) == param_name) + Some(to_lower_snake_case(qual.segment()?.name_ref()?.text()) == param_name) } _ => None, } 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 125b2f495acca..c3d620a35700b 100644 --- a/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs +++ b/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs @@ -190,7 +190,7 @@ impl NavigationTarget { kind: SymbolKind, ) -> UpmappingResult { let name = - value.name().map(|it| Symbol::intern(&it.text())).unwrap_or_else(|| sym::underscore); + value.name().map(|it| Symbol::intern(it.text())).unwrap_or_else(|| sym::underscore); orig_range_with_focus(db, file_id, value.syntax(), value.name()).map( |(FileRange { file_id, range: full_range }, focus_range)| { 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 080f9a7c6b175..852b13fc7a3d2 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/edit.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/edit.rs @@ -360,7 +360,7 @@ impl ast::RecordExprField { // shorthand `{ x }` → expand to `{ x: expr }` let new_field = editor .make() - .record_expr_field(editor.make().name_ref(&name_ref.text()), Some(expr)); + .record_expr_field(editor.make().name_ref(name_ref.text()), Some(expr)); editor.replace(self.syntax(), new_field.syntax()); } } From fa05017d11beaa0fe5178813a3b45d449255f570 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Sun, 2 Aug 2026 10:01:05 +0530 Subject: [PATCH 56/63] Remove absolute versioning in rowan import --- src/tools/rust-analyzer/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/Cargo.toml b/src/tools/rust-analyzer/Cargo.toml index 693a819052634..a56d9770f5b3e 100644 --- a/src/tools/rust-analyzer/Cargo.toml +++ b/src/tools/rust-analyzer/Cargo.toml @@ -123,7 +123,7 @@ process-wrap = { version = "9.1.0", features = ["std"] } pulldown-cmark-to-cmark = "10.0.4" pulldown-cmark = { version = "0.9.6", default-features = false } rayon = "1.10.0" -rowan = "=0.17.0" +rowan = "0.17.0" # Ideally we'd not enable the macros feature but unfortunately the `tracked` attribute does not work # on impls without it salsa = { version = "0.27.0", default-features = false, features = [ From b1ea7ced1a399003afb795e49a21f8b58f125bd9 Mon Sep 17 00:00:00 2001 From: Musteab Date: Sun, 2 Aug 2026 16:03:56 +0800 Subject: [PATCH 57/63] Capture the components array and check it for rust-analyzer --- .../rust-analyzer/editors/code/src/bootstrap.ts | 13 ++++++++----- .../editors/code/tests/unit/bootstrap.test.ts | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/tools/rust-analyzer/editors/code/src/bootstrap.ts b/src/tools/rust-analyzer/editors/code/src/bootstrap.ts index 98c2b359bf2bc..440f21cbe3c50 100644 --- a/src/tools/rust-analyzer/editors/code/src/bootstrap.ts +++ b/src/tools/rust-analyzer/editors/code/src/bootstrap.ts @@ -176,13 +176,16 @@ async function fileExists(uri: vscode.Uri) { ); } -// Matches a `components` array that lists `rust-analyzer`. The elements are matched with -// `[^\]]` rather than `.` so that the array may be spread over several lines, which is just -// as valid TOML as keeping it on one. TOML strings come in both quote flavours. -const RA_COMPONENT_RE = /components\s*=\s*\[[^\]]*["']rust-analyzer["'][^\]]*\]/; +// Captures the elements of a `components` array. They are matched with `[^\]]` rather than `.` +// so that the array may be spread over several lines, which is just as valid TOML as keeping it +// on one, while still stopping at the end of the array. +const COMPONENTS_RE = /components\s*=\s*\[(?[^\]]*)\]/; +// TOML strings come in both quote flavours. +const RA_COMPONENT_RE = /["']rust-analyzer["']/; function declaresRaComponent(toolchainFileContents: string): boolean { - return RA_COMPONENT_RE.test(toolchainFileContents); + const components = toolchainFileContents.match(COMPONENTS_RE)?.groups?.["components"]; + return components !== undefined && RA_COMPONENT_RE.test(components); } async function hasToolchainFileWithRaDeclared(uri: vscode.Uri): Promise { diff --git a/src/tools/rust-analyzer/editors/code/tests/unit/bootstrap.test.ts b/src/tools/rust-analyzer/editors/code/tests/unit/bootstrap.test.ts index 8d348b9ccbd5f..259428da41dbf 100644 --- a/src/tools/rust-analyzer/editors/code/tests/unit/bootstrap.test.ts +++ b/src/tools/rust-analyzer/editors/code/tests/unit/bootstrap.test.ts @@ -158,7 +158,7 @@ channel = "1.88" components = [ "cargo", ] -path = "/opt/rust-analyzer" +# add "rust-analyzer" here to use the matching server `, ), ); From 9cebc266edcab0a0f8679a0bd64864a01171740f Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sun, 2 Aug 2026 20:00:57 +0800 Subject: [PATCH 58/63] fix: add reference for same name param coerce matches Example --- ```rust fn ref_arg(x: &[i32]) {} fn foo(x: [i32; 2]) { ref_ar$0 } ``` **Before this PR** ```rust fn ref_arg(x: &[i32]) {} fn foo(x: [i32; 2]) { ref_arg(${1:x});$0 } ``` **After this PR** ```rust fn ref_arg(x: &[i32]) {} fn foo(x: [i32; 2]) { ref_arg(${1:&x});$0 } ``` --- .../ide-completion/src/render/function.rs | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs index 4f70a90affbdf..eb6331b68e101 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs @@ -288,14 +288,17 @@ pub(super) fn add_call_parens<'b>( } fn ref_of_param(ctx: &CompletionContext<'_, '_>, arg: &str, ty: &hir::Type<'_>) -> &'static str { - if let Some(derefed_ty) = ty.as_reference_inner() { + if ty.is_reference() { + let mutability = hir::Mutability::from_mutable(ty.is_mutable_reference()); + let ref_prefix = if mutability.is_mut() { "&mut " } else { "&" }; + for (name, local) in ctx.locals.iter().sorted_by_key(|&(k, _)| k.clone()) { if name.as_str() == arg { - return if local.ty(ctx.db) == derefed_ty { - if ty.is_mutable_reference() { "&mut " } else { "&" } - } else { - "" - }; + let local_ty = local.ty(ctx.db).instantiate_with_errors(); + let added_ref = local_ty.add_reference(ctx.db, mutability); + let needs_ref = + !local_ty.could_coerce_to(ctx.db, ty) && added_ref.could_coerce_to(ctx.db, ty); + return if needs_ref { ref_prefix } else { "" }; } } } @@ -473,7 +476,7 @@ fn bar(s: &S) { r#" struct S {} impl S { - fn foo(&self, x: i32) { + fn foo(&self, x: i32, y: &i32) { $0 } } @@ -481,8 +484,8 @@ impl S { r#" struct S {} impl S { - fn foo(&self, x: i32) { - self.foo(${1:x});$0 + fn foo(&self, x: i32, y: &i32) { + self.foo(${1:x}, ${2:y});$0 } } "#, @@ -561,6 +564,24 @@ fn main() { let x = Foo {}; ref_arg(${1:&x});$0 } +"#, + ); + check_edit( + "ref_arg", + r#" +//- minicore: coerce_unsized +fn ref_arg(x: &[i32]) {} +fn main() { + let x = [2]; + ref_ar$0 +} +"#, + r#" +fn ref_arg(x: &[i32]) {} +fn main() { + let x = [2]; + ref_arg(${1:&x});$0 +} "#, ); } From 08e0d56015c55cf815db93be20c9db7b32b07a03 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sun, 2 Aug 2026 20:07:54 +0800 Subject: [PATCH 59/63] Use as_reference() api --- .../rust-analyzer/crates/ide-completion/src/render/function.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs index eb6331b68e101..b3ec60d4fafb7 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs @@ -288,8 +288,7 @@ pub(super) fn add_call_parens<'b>( } fn ref_of_param(ctx: &CompletionContext<'_, '_>, arg: &str, ty: &hir::Type<'_>) -> &'static str { - if ty.is_reference() { - let mutability = hir::Mutability::from_mutable(ty.is_mutable_reference()); + if let Some((_, mutability)) = ty.as_reference() { let ref_prefix = if mutability.is_mut() { "&mut " } else { "&" }; for (name, local) in ctx.locals.iter().sorted_by_key(|&(k, _)| k.clone()) { From 95f1a80a1669e15babdc0bc5bc5586b57d8d783d Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sun, 2 Aug 2026 21:20:47 +0800 Subject: [PATCH 60/63] fix: parse postfix range inside closure in access Example --- **Before this PR** ```text EXPR_STMT CLOSURE_EXPR PARAM_LIST PIPE "|" PIPE "|" RANGE_EXPR LITERAL INT_NUMBER "1" DOT2 ".." WHITESPACE " " ERROR DOT "." EXPR_STMT CALL_EXPR PATH_EXPR PATH PATH_SEGMENT NAME_REF IDENT "method" ``` **After this PR** ```rust METHOD_CALL_EXPR CLOSURE_EXPR PARAM_LIST PIPE "|" PIPE "|" RANGE_EXPR LITERAL INT_NUMBER "1" DOT2 ".." WHITESPACE " " DOT "." NAME_REF IDENT "method" ``` --- .../crates/parser/src/grammar/expressions.rs | 11 +++- .../parser/test_data/generated/runner.rs | 4 ++ .../ok/closure_postfix_range_method_call.rast | 53 +++++++++++++++++++ .../ok/closure_postfix_range_method_call.rs | 4 ++ 4 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/closure_postfix_range_method_call.rast create mode 100644 src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/closure_postfix_range_method_call.rs diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/expressions.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/expressions.rs index 3f341c2ab846e..9f3de3b921c6e 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/expressions.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/expressions.rs @@ -299,8 +299,16 @@ fn expr_bp( // match 1.. { _ => () }; // match a.b()..S { _ => () }; // } + + // test closure_postfix_range_method_call + // fn foo() { + // || 1.. .method(); + // || 1.. .field; + // } + let has_access_after = p.at(T![.]) && p.nth_at(1, SyntaxKind::IDENT); + let struct_forbidden = r.forbid_structs && p.at(T!['{']); let has_trailing_expression = - p.at_ts(EXPR_FIRST) && !(r.forbid_structs && p.at(T!['{'])); + p.at_ts(EXPR_FIRST) && !has_access_after && !struct_forbidden; if !has_trailing_expression { // no RHS lhs = m.complete(p, RANGE_EXPR); @@ -382,6 +390,7 @@ fn lhs(p: &mut Parser<'_>, r: Restrictions) -> Option<(CompletedMarker, BlockLik // } let has_access_after = p.at(T![.]) && p.nth_at(1, SyntaxKind::IDENT); let struct_forbidden = r.forbid_structs && p.at(T!['{']); + // NOTE: Similar logic `is_range` flag in expr_bp() if p.at_ts(EXPR_FIRST) && !has_access_after && !struct_forbidden { expr_bp(p, None, r, 2); } 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 8104d28bafdf0..22b5684581252 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 @@ -121,6 +121,10 @@ mod ok { run_and_expect_no_errors("test_data/parser/inline/ok/closure_params.rs"); } #[test] + fn closure_postfix_range_method_call() { + run_and_expect_no_errors("test_data/parser/inline/ok/closure_postfix_range_method_call.rs"); + } + #[test] fn closure_range_method_call() { run_and_expect_no_errors("test_data/parser/inline/ok/closure_range_method_call.rs"); } diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/closure_postfix_range_method_call.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/closure_postfix_range_method_call.rast new file mode 100644 index 0000000000000..555d312e33897 --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/closure_postfix_range_method_call.rast @@ -0,0 +1,53 @@ +SOURCE_FILE + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "foo" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE "\n " + EXPR_STMT + METHOD_CALL_EXPR + CLOSURE_EXPR + PARAM_LIST + PIPE "|" + PIPE "|" + WHITESPACE " " + RANGE_EXPR + LITERAL + INT_NUMBER "1" + DOT2 ".." + WHITESPACE " " + DOT "." + NAME_REF + IDENT "method" + ARG_LIST + L_PAREN "(" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n " + EXPR_STMT + FIELD_EXPR + CLOSURE_EXPR + PARAM_LIST + PIPE "|" + PIPE "|" + WHITESPACE " " + RANGE_EXPR + LITERAL + INT_NUMBER "1" + DOT2 ".." + WHITESPACE " " + DOT "." + NAME_REF + IDENT "field" + SEMICOLON ";" + WHITESPACE "\n" + R_CURLY "}" + WHITESPACE "\n" diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/closure_postfix_range_method_call.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/closure_postfix_range_method_call.rs new file mode 100644 index 0000000000000..71429be2caad5 --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/closure_postfix_range_method_call.rs @@ -0,0 +1,4 @@ +fn foo() { + || 1.. .method(); + || 1.. .field; +} From 95b4e46e02f4003130165a3843f9b110f4cac07f Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sun, 2 Aug 2026 22:42:49 +0800 Subject: [PATCH 61/63] minor: variant eval error use source instead of node debug Example --- ```rust enum E { A$0 = {} } ``` **Before this PR** ```rust A = BlockExpr(BlockExpr { syntax: BLOCK_EXPR@29..31 }) ``` **After this PR** ```rust A = {} ``` --- .../crates/ide/src/hover/render.rs | 2 +- .../crates/ide/src/hover/tests.rs | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/ide/src/hover/render.rs b/src/tools/rust-analyzer/crates/ide/src/hover/render.rs index f26b99292929e..f70783fd3c0c4 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/render.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/render.rs @@ -509,7 +509,7 @@ pub(super) fn definition( Some(if it >= 10 { format!("{it} ({it:#X})") } else { format!("{it}") }) } Err(err) => { - let res = it.value(db).map(|it| format!("{it:?}")); + let res = it.value(db).map(|it| it.to_string()); if env::var_os("RA_DEV").is_some() { let res = res.as_deref().unwrap_or(""); Some(format!( 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 89f1cf2fc1e11..baa95e741df3b 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs @@ -5620,6 +5620,30 @@ enum E { This is a doc "#]], ); + // const eval failed test + check( + r#" +#[repr(u8)] +enum E { + A$0 = {}, +} +"#, + expect![[r#" + *A* + + ```rust + ra_test_fixture::E + ``` + + ```rust + A = {} + ``` + + --- + + size = 1, align = 1, no Drop + "#]], + ); } #[test] From dc281b4ad111ceab31f826faab21986012b4f7e7 Mon Sep 17 00:00:00 2001 From: George Ogden Date: Sat, 1 Aug 2026 14:43:11 +0100 Subject: [PATCH 62/63] Correctly handle unlinked module edge cases Escape keywords when used as module names Display modules with non-identifier names using #[path = "..."] syntax --- src/tools/rust-analyzer/Cargo.lock | 1 + .../src/handlers/unlinked_file.rs | 124 ++++++++++++++++-- .../crates/ide-diagnostics/src/lib.rs | 4 +- .../rust-analyzer/crates/syntax/Cargo.toml | 1 + .../rust-analyzer/crates/syntax/src/lib.rs | 4 + .../rust-analyzer/crates/syntax/src/utils.rs | 19 +++ 6 files changed, 142 insertions(+), 11 deletions(-) diff --git a/src/tools/rust-analyzer/Cargo.lock b/src/tools/rust-analyzer/Cargo.lock index 7a2e2d493b59a..fd0790f4a484b 100644 --- a/src/tools/rust-analyzer/Cargo.lock +++ b/src/tools/rust-analyzer/Cargo.lock @@ -2714,6 +2714,7 @@ dependencies = [ "expect-test", "itertools 0.15.0", "parser", + "ra-ap-rustc_lexer", "rayon", "rowan", "rustc-hash 2.1.2", 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 dc6ae6f08ba5e..376fc4fbdf78d 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 @@ -3,14 +3,15 @@ use std::iter; use hir::crate_def_map; -use hir::{InFile, ModuleSource}; +use hir::{EditionedFileId, InFile, ModuleSource}; use ide_db::text_edit::TextEdit; use ide_db::{FileId, FileRange, base_db::SourceDatabase, source_change::SourceChange}; use ide_db::{base_db, line_index}; use paths::Utf8Component; use syntax::{ - AstNode, TextRange, + AstNode, Edition, TextRange, ast::{self, HasModuleItem, HasName, edit::IndentLevel}, + utils::{is_identifier, is_raw_identifier}, }; use crate::{Assist, Diagnostic, DiagnosticCode, DiagnosticsContext, Severity, fix}; @@ -22,10 +23,11 @@ use crate::{Assist, Diagnostic, DiagnosticCode, DiagnosticsContext, Severity, fi pub(crate) fn unlinked_file( ctx: &DiagnosticsContext<'_, '_>, acc: &mut Vec, - file_id: FileId, + editioned_file_id: EditionedFileId, ) { + let file_id = editioned_file_id.file_id(ctx.sema.db); let mut range = TextRange::up_to(line_index(ctx.sema.db, file_id).len()); - let fixes = fixes(ctx, file_id, range); + let fixes = fixes(ctx, editioned_file_id, range); // FIXME: This is a hack for the vscode extension to notice whether there is an autofix or not before having to resolve diagnostics. // This is to prevent project linking popups from appearing when there is an autofix. https://github.com/rust-lang/rust-analyzer/issues/14523 let message = if fixes.is_none() { @@ -74,13 +76,15 @@ pub(crate) fn unlinked_file( fn fixes( ctx: &DiagnosticsContext<'_, '_>, - file_id: FileId, + editioned_file_id: EditionedFileId, trigger_range: TextRange, ) -> Option> { // If there's an existing module that could add `mod` or `pub mod` items to include the unlinked file, // suggest that as a fix. let db = ctx.sema.db; + let file_id = editioned_file_id.file_id(db); + let edition = editioned_file_id.edition(db); let source_root = ctx.sema.db.file_source_root(file_id).source_root_id(db); let source_root = ctx.sema.db.source_root(source_root).source_root(db); @@ -136,6 +140,7 @@ fn fixes( return make_fixes( parent_file_id.file_id(ctx.sema.db), source, + edition, &module_name, trigger_range, ); @@ -169,6 +174,7 @@ fn fixes( return make_fixes( parent_id, module.definition_source(ctx.sema.db).value, + edition, &module_name, trigger_range, ); @@ -193,6 +199,7 @@ fn fixes( return make_fixes( parent_file_id.file_id(ctx.sema.db), source, + edition, &module_name, trigger_range, ); @@ -202,9 +209,26 @@ fn fixes( None } +/// Convert a module name along with its visibility to code. +/// In most cases, this just adds the visibility and keyword beforehand, +/// but the exceptions are non-identifiers and keywords. +fn format_mod_name(mod_name: &str, visibility_and_keyword: &str, edition: Edition) -> String { + if is_identifier(mod_name, edition) { + format!("{visibility_and_keyword} {mod_name};") + } else { + if is_raw_identifier(mod_name, edition) { + format!("{visibility_and_keyword} r#{mod_name};") + } else { + let file_name = format!("{mod_name}.rs"); + format!("#[path = {file_name:?}]\n{visibility_and_keyword} mod_name;") + } + } +} + fn make_fixes( parent_file_id: FileId, source: ModuleSource, + edition: Edition, new_mod_name: &str, trigger_range: TextRange, ) -> Option> { @@ -212,9 +236,9 @@ fn make_fixes( matches!(item, ast::Item::Module(m) if m.item_list().is_none()) } - let mod_decl = format!("mod {new_mod_name};"); - let pub_mod_decl = format!("pub mod {new_mod_name};"); - let pub_crate_mod_decl = format!("pub(crate) mod {new_mod_name};"); + let mod_decl = format_mod_name(new_mod_name, "mod", edition); + let pub_mod_decl = format_mod_name(new_mod_name, "pub mod", edition); + let pub_crate_mod_decl = format_mod_name(new_mod_name, "pub(crate) mod", edition); let mut mod_decl_builder = TextEdit::builder(); let mut pub_mod_decl_builder = TextEdit::builder(); @@ -545,6 +569,90 @@ mod bar { //- /main.rs include!("bar/foo/mod.rs"); //- /bar/foo/mod.rs +"#, + ); + } + + #[test] + fn unlinked_file_with_strict_keyword_move() { + check_fix( + r#" +//- /main.rs +//- /move.rs +$0 +"#, + r#" +mod r#move; +"#, + ); + } + + #[test] + fn unlinked_file_with_weak_keyword_safe() { + check_fix( + r#" +//- /main.rs +//- /safe.rs +$0 +"#, + r#" +mod safe; +"#, + ); + } + + #[test] + fn unlinked_file_with_reserved_keyword_abstract() { + check_fix( + r#" +//- /main.rs +//- /abstract.rs +$0 +"#, + r#" +mod r#abstract; +"#, + ); + } + + #[test] + fn unlinked_file_with_unescaped_keyword_crate() { + check_fix( + r#" +//- /main.rs +//- /crate.rs +$0 +"#, + r#"#[path = "crate.rs"] +mod mod_name; +"#, + ); + } + + #[test] + fn unlinked_invalid_symbol_in_module_name() { + check_fix( + r#" +//- /main.rs +//- /my-file.rs +$0 +"#, + r#"#[path = "my-file.rs"] +mod mod_name; +"#, + ); + } + + #[test] + fn unlinked_numeric_module_name() { + check_fix( + r#" +//- /main.rs +//- /0000.rs +$0 +"#, + r#"#[path = "0000.rs"] +mod mod_name; "#, ); } 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 26e4a84d2ee35..d4d36e87f4709 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs @@ -435,9 +435,7 @@ pub fn semantic_diagnostics( m.diagnostics(db, &mut diags, config.style_lints); } } - None => { - handlers::unlinked_file::unlinked_file(&ctx, &mut res, editioned_file_id.file_id(db)) - } + None => handlers::unlinked_file::unlinked_file(&ctx, &mut res, editioned_file_id), } for diag in diags { diff --git a/src/tools/rust-analyzer/crates/syntax/Cargo.toml b/src/tools/rust-analyzer/crates/syntax/Cargo.toml index e65836ed8dcb4..a9df1acdae9a7 100644 --- a/src/tools/rust-analyzer/crates/syntax/Cargo.toml +++ b/src/tools/rust-analyzer/crates/syntax/Cargo.toml @@ -15,6 +15,7 @@ doctest = false [dependencies] either.workspace = true itertools.workspace = true +ra-ap-rustc_lexer.workspace = true rowan.workspace = true rustc-hash.workspace = true rustc-literal-escaper.workspace = true diff --git a/src/tools/rust-analyzer/crates/syntax/src/lib.rs b/src/tools/rust-analyzer/crates/syntax/src/lib.rs index 614678536a512..548b7ac909963 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/lib.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/lib.rs @@ -21,8 +21,12 @@ #![cfg_attr(feature = "in-rust-tree", feature(rustc_private))] +#[cfg(not(feature = "in-rust-tree"))] +extern crate ra_ap_rustc_lexer as rustc_lexer; #[cfg(feature = "in-rust-tree")] extern crate rustc_driver as _; +#[cfg(feature = "in-rust-tree")] +extern crate rustc_lexer; mod parsing; mod ptr; diff --git a/src/tools/rust-analyzer/crates/syntax/src/utils.rs b/src/tools/rust-analyzer/crates/syntax/src/utils.rs index d1f60f0b71bcc..9538ae2b90792 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/utils.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/utils.rs @@ -1,6 +1,25 @@ //! A set of utils methods to reuse on other abstraction levels use crate::SyntaxKind; +use rustc_lexer; + +#[inline] +/// Checks that the name is an identifier. +/// This also means that it is not a strict keyword. +/// But it may be a weak keyword. +pub fn is_identifier(name: &str, edition: parser::Edition) -> bool { + if rustc_lexer::is_ident(name) { + if let Some(syntax_kind) = SyntaxKind::from_keyword(name, edition) + && syntax_kind.is_strict_keyword(edition) + { + false + } else { + true + } + } else { + false + } +} #[inline] pub fn is_raw_identifier(name: &str, edition: parser::Edition) -> bool { From c1dcbfc504cf6812015733e50700b28f301f789d Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Mon, 3 Aug 2026 03:58:17 +0800 Subject: [PATCH 63/63] Move instantiate_with_errors from local_ty into param --- .../crates/ide-completion/src/render/function.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs index b3ec60d4fafb7..4698a9cd4a0cd 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs @@ -233,7 +233,8 @@ pub(super) fn add_call_parens<'b>( Some(n) => { let smol_str = n.display_no_db(ctx.edition).to_smolstr(); let text = smol_str.as_str().trim_start_matches('_'); - let ref_ = ref_of_param(ctx, text, param.ty()); + let ref_ = + ref_of_param(ctx, text, ¶m.ty().instantiate_with_errors()); f(&format_args!("${{{}:{ref_}{text}}}", index + offset)) } None => { @@ -293,7 +294,7 @@ fn ref_of_param(ctx: &CompletionContext<'_, '_>, arg: &str, ty: &hir::Type<'_>) for (name, local) in ctx.locals.iter().sorted_by_key(|&(k, _)| k.clone()) { if name.as_str() == arg { - let local_ty = local.ty(ctx.db).instantiate_with_errors(); + let local_ty = local.ty(ctx.db); let added_ref = local_ty.add_reference(ctx.db, mutability); let needs_ref = !local_ty.could_coerce_to(ctx.db, ty) && added_ref.could_coerce_to(ctx.db, ty);