From 0246980060943f14fe9820d92b18d744162ab63d Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Mon, 27 Oct 2025 15:25:53 +0800 Subject: [PATCH 01/26] Fix not applicable on while for replace_is_method_with_if_let_method Example --- ```rust fn main() { let mut x = Some(1); while x.is_som$0e() { x = None } } ``` **Before this PR** Assist not applicable **After this PR** ```rust fn main() { let mut x = Some(1); while let Some(${0:x1}) = x { x = None } } ``` --- .../replace_is_method_with_if_let_method.rs | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs index c57fd4d439dc6..5a2307739cff5 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_is_method_with_if_let_method.rs @@ -1,3 +1,4 @@ +use either::Either; use ide_db::syntax_helpers::suggest_name; use syntax::ast::{self, AstNode, syntax_factory::SyntaxFactory}; @@ -24,9 +25,9 @@ pub(crate) fn replace_is_method_with_if_let_method( acc: &mut Assists, ctx: &AssistContext<'_>, ) -> Option<()> { - let if_expr = ctx.find_node_at_offset::()?; + let has_cond = ctx.find_node_at_offset::>()?; - let cond = if_expr.condition()?; + let cond = either::for_both!(&has_cond, it => it.condition())?; let cond = cover_let_chain(cond, ctx.selection_trimmed())?; let call_expr = match cond { ast::Expr::MethodCallExpr(call) => call, @@ -39,7 +40,7 @@ pub(crate) fn replace_is_method_with_if_let_method( let receiver = call_expr.receiver()?; let mut name_generator = suggest_name::NameGenerator::new_from_scope_locals( - ctx.sema.scope(if_expr.syntax()), + ctx.sema.scope(has_cond.syntax()), ); let var_name = if let ast::Expr::PathExpr(path_expr) = receiver.clone() { name_generator.suggest_name(&path_expr.path()?.to_string()) @@ -48,9 +49,9 @@ pub(crate) fn replace_is_method_with_if_let_method( }; let (assist_id, message, text) = if name_ref.text() == "is_some" { - ("replace_is_some_with_if_let_some", "Replace `is_some` with `if let Some`", "Some") + ("replace_is_some_with_if_let_some", "Replace `is_some` with `let Some`", "Some") } else { - ("replace_is_ok_with_if_let_ok", "Replace `is_ok` with `if let Ok`", "Ok") + ("replace_is_ok_with_if_let_ok", "Replace `is_ok` with `let Ok`", "Ok") }; acc.add( @@ -250,6 +251,25 @@ fn main() { ); } + #[test] + fn replace_is_some_with_while_let_some() { + check_assist( + replace_is_method_with_if_let_method, + r#" +fn main() { + let mut x = Some(1); + while x.is_som$0e() { x = None } +} +"#, + r#" +fn main() { + let mut x = Some(1); + while let Some(${0:x1}) = x { x = None } +} +"#, + ); + } + #[test] fn replace_is_some_with_if_let_some_not_applicable_after_l_curly() { check_assist_not_applicable( From e7fb68aa52050d28a9913b660d36fc51eea70ee5 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Fri, 24 Oct 2025 13:13:40 +0300 Subject: [PATCH 02/26] Properly support opaques By letting the solver take control of them (reveal them when needed and define them when needed), by providing them in the `TypingMode` plus few helpers. --- src/tools/rust-analyzer/.typos.toml | 1 + .../crates/hir-ty/src/autoderef.rs | 2 +- .../rust-analyzer/crates/hir-ty/src/infer.rs | 296 +++--------------- .../crates/hir-ty/src/infer/coerce.rs | 23 +- .../crates/hir-ty/src/infer/expr.rs | 29 +- .../crates/hir-ty/src/infer/opaques.rs | 147 +++++++++ .../crates/hir-ty/src/infer/path.rs | 7 +- .../crates/hir-ty/src/infer/unify.rs | 67 ++-- .../rust-analyzer/crates/hir-ty/src/lib.rs | 6 +- .../crates/hir-ty/src/method_resolution.rs | 124 ++++---- .../crates/hir-ty/src/mir/borrowck.rs | 8 +- .../crates/hir-ty/src/next_solver/def_id.rs | 23 ++ .../hir-ty/src/next_solver/generic_arg.rs | 8 + .../hir-ty/src/next_solver/infer/mod.rs | 66 +++- .../src/next_solver/infer/opaque_types/mod.rs | 4 +- .../next_solver/infer/opaque_types/table.rs | 8 - .../crates/hir-ty/src/next_solver/interner.rs | 68 ++-- .../crates/hir-ty/src/next_solver/solver.rs | 91 ++++-- .../crates/hir-ty/src/opaques.rs | 199 ++++++++++++ .../crates/hir-ty/src/tests/incremental.rs | 2 + .../crates/hir-ty/src/tests/opaque_types.rs | 5 +- .../crates/hir-ty/src/tests/regression.rs | 2 +- .../hir-ty/src/tests/regression/new_solver.rs | 2 +- .../crates/hir-ty/src/tests/traits.rs | 10 +- .../rust-analyzer/crates/hir-ty/src/traits.rs | 26 +- src/tools/rust-analyzer/crates/hir/src/lib.rs | 8 +- .../crates/ide/src/hover/tests.rs | 2 +- .../crates/ide/src/signature_help.rs | 10 +- 28 files changed, 743 insertions(+), 501 deletions(-) create mode 100644 src/tools/rust-analyzer/crates/hir-ty/src/infer/opaques.rs create mode 100644 src/tools/rust-analyzer/crates/hir-ty/src/opaques.rs diff --git a/src/tools/rust-analyzer/.typos.toml b/src/tools/rust-analyzer/.typos.toml index 99464150dab47..e954b08fb1e1b 100644 --- a/src/tools/rust-analyzer/.typos.toml +++ b/src/tools/rust-analyzer/.typos.toml @@ -33,6 +33,7 @@ trivias = "trivias" thir = "thir" jod = "jod" tructure = "tructure" +taits = "taits" [default.extend-identifiers] anc = "anc" diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/autoderef.rs b/src/tools/rust-analyzer/crates/hir-ty/src/autoderef.rs index 6dd3cdb745aa0..392b0b0408255 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/autoderef.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/autoderef.rs @@ -38,7 +38,7 @@ pub fn autoderef<'db>( env: Arc>, ty: Canonical<'db, Ty<'db>>, ) -> impl Iterator> + use<'db> { - let mut table = InferenceTable::new(db, env); + let mut table = InferenceTable::new(db, env, None); let ty = table.instantiate_canonical(ty); let mut autoderef = Autoderef::new_no_tracking(&mut table, ty); let mut v = Vec::new(); 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 361e66522df65..016edb2310ebb 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs @@ -21,6 +21,7 @@ pub(crate) mod diagnostics; mod expr; mod fallback; mod mutability; +mod opaques; mod pat; mod path; pub(crate) mod unify; @@ -31,8 +32,7 @@ use base_db::Crate; use either::Either; use hir_def::{ AdtId, AssocItemId, ConstId, DefWithBodyId, FieldId, FunctionId, GenericDefId, GenericParamId, - ImplId, ItemContainerId, LocalFieldId, Lookup, TraitId, TupleFieldId, TupleId, TypeAliasId, - VariantId, + ItemContainerId, LocalFieldId, Lookup, TraitId, TupleFieldId, TupleId, TypeAliasId, VariantId, expr_store::{Body, ExpressionStore, HygieneId, path::Path}, hir::{BindingAnnotation, BindingId, ExprId, ExprOrPatId, LabelId, PatId}, lang_item::{LangItem, LangItemTarget, lang_item}, @@ -44,11 +44,11 @@ use hir_def::{ use hir_expand::{mod_path::ModPath, name::Name}; use indexmap::IndexSet; use intern::sym; -use la_arena::{ArenaMap, Entry}; +use la_arena::ArenaMap; use rustc_ast_ir::Mutability; use rustc_hash::{FxHashMap, FxHashSet}; use rustc_type_ir::{ - AliasTyKind, Flags, TypeFlags, TypeFoldable, TypeSuperVisitable, TypeVisitable, TypeVisitor, + AliasTyKind, TypeFoldable, inherent::{AdtDef, IntoKind, Region as _, SliceLike, Ty as _}, }; use stdx::never; @@ -61,7 +61,6 @@ use crate::{ coerce::{CoerceMany, DynamicCoerceMany}, diagnostics::{Diagnostics, InferenceTyLoweringContext as TyLoweringContext}, expr::ExprIsRead, - unify::InferenceTable, }, lower::{ ImplTraitIdx, ImplTraitLoweringMode, LifetimeElisionKind, diagnostics::TyLoweringDiagnostic, @@ -69,10 +68,7 @@ use crate::{ mir::MirSpan, next_solver::{ AliasTy, Const, DbInterner, ErrorGuaranteed, GenericArg, GenericArgs, Region, Ty, TyKind, - Tys, - abi::Safety, - fold::fold_tys, - infer::traits::{Obligation, ObligationCause}, + Tys, abi::Safety, infer::traits::ObligationCause, }, traits::FnTrait, utils::TargetFeatureIsSafeInTarget, @@ -132,6 +128,8 @@ pub(crate) fn infer_query(db: &dyn HirDatabase, def: DefWithBodyId) -> Arc Arc { /// unresolved or missing subpatterns or subpatterns of mismatched types. pub(crate) type_of_pat: ArenaMap>, pub(crate) type_of_binding: ArenaMap>, - pub(crate) type_of_rpit: ArenaMap, Ty<'db>>, + pub(crate) type_of_opaque: FxHashMap>, type_mismatches: FxHashMap>, /// Whether there are any type-mismatching errors in the result. // FIXME: This isn't as useful as initially thought due to us falling back placeholders to @@ -499,7 +501,7 @@ impl<'db> InferenceResult<'db> { type_of_expr: Default::default(), type_of_pat: Default::default(), type_of_binding: Default::default(), - type_of_rpit: Default::default(), + type_of_opaque: Default::default(), type_mismatches: Default::default(), has_errors: Default::default(), error_ty, @@ -640,8 +642,14 @@ impl<'db> InferenceResult<'db> { // This method is consumed by external tools to run rust-analyzer as a library. Don't remove, please. pub fn return_position_impl_trait_types( &self, + db: &'db dyn HirDatabase, ) -> impl Iterator, Ty<'db>)> { - self.type_of_rpit.iter().map(|(k, v)| (k, *v)) + self.type_of_opaque.iter().filter_map(move |(&id, &ty)| { + let ImplTraitId::ReturnTypeImplTrait(_, rpit_idx) = id.loc(db) else { + return None; + }; + Some((rpit_idx, ty)) + }) } } @@ -707,6 +715,7 @@ struct InternedStandardTypes<'db> { re_static: Region<'db>, re_error: Region<'db>, + re_erased: Region<'db>, empty_args: GenericArgs<'db>, empty_tys: Tys<'db>, @@ -742,6 +751,7 @@ impl<'db> InternedStandardTypes<'db> { re_static, re_error: Region::error(interner), + re_erased: Region::new_erased(interner), empty_args: GenericArgs::new_from_iter(interner, []), empty_tys: Tys::new_from_iter(interner, []), @@ -848,11 +858,6 @@ fn find_continuable<'a, 'db>( } } -enum ImplTraitReplacingMode<'db> { - ReturnPosition(FxHashSet>), - TypeAlias, -} - impl<'body, 'db> InferenceContext<'body, 'db> { fn new( db: &'db dyn HirDatabase, @@ -861,7 +866,7 @@ impl<'body, 'db> InferenceContext<'body, 'db> { resolver: Resolver<'db>, ) -> Self { let trait_env = db.trait_environment_for_body(owner); - let table = unify::InferenceTable::new(db, trait_env); + let table = unify::InferenceTable::new(db, trait_env, Some(owner)); let types = InternedStandardTypes::new(table.interner()); InferenceContext { result: InferenceResult::new(types.error), @@ -952,7 +957,7 @@ impl<'body, 'db> InferenceContext<'body, 'db> { // `InferenceResult` in the middle of inference. See the fixme comment in `consteval::eval_to_const`. If you // used this function for another workaround, mention it here. If you really need this function and believe that // there is no problem in it being `pub(crate)`, remove this comment. - pub(crate) fn resolve_all(self) -> InferenceResult<'db> { + fn resolve_all(self) -> InferenceResult<'db> { let InferenceContext { mut table, mut result, tuple_field_accesses_rev, diagnostics, .. } = self; @@ -967,7 +972,7 @@ impl<'body, 'db> InferenceContext<'body, 'db> { type_of_expr, type_of_pat, type_of_binding, - type_of_rpit, + type_of_opaque, type_mismatches, has_errors, error_ty: _, @@ -999,11 +1004,7 @@ impl<'body, 'db> InferenceContext<'body, 'db> { *has_errors = *has_errors || ty.references_non_lt_error(); } type_of_binding.shrink_to_fit(); - for ty in type_of_rpit.values_mut() { - *ty = table.resolve_completely(*ty); - *has_errors = *has_errors || ty.references_non_lt_error(); - } - type_of_rpit.shrink_to_fit(); + type_of_opaque.shrink_to_fit(); *has_errors |= !type_mismatches.is_empty(); @@ -1084,9 +1085,6 @@ impl<'body, 'db> InferenceContext<'body, 'db> { LifetimeElisionKind::for_const(self.interner(), id.loc(self.db).container), ); - // Constants might be defining usage sites of TAITs. - self.make_tait_coercion_table(iter::once(return_ty)); - self.return_ty = return_ty; } @@ -1098,9 +1096,6 @@ impl<'body, 'db> InferenceContext<'body, 'db> { LifetimeElisionKind::Elided(self.types.re_static), ); - // Statics might be defining usage sites of TAITs. - self.make_tait_coercion_table(iter::once(return_ty)); - self.return_ty = return_ty; } @@ -1138,16 +1133,12 @@ impl<'body, 'db> InferenceContext<'body, 'db> { let ty = self.process_user_written_ty(ty); self.write_binding_ty(self_param, ty); } - let mut tait_candidates = FxHashSet::default(); for (ty, pat) in param_tys.zip(&*self.body.params) { let ty = self.process_user_written_ty(ty); self.infer_top_pat(*pat, ty, None); - if ty.flags().intersects(TypeFlags::HAS_TY_OPAQUE.union(TypeFlags::HAS_TY_INFER)) { - tait_candidates.insert(ty); - } } - let return_ty = match data.ret_type { + self.return_ty = match data.ret_type { Some(return_ty) => { let return_ty = self.with_ty_lowering( &data.store, @@ -1158,45 +1149,12 @@ impl<'body, 'db> InferenceContext<'body, 'db> { ctx.lower_ty(return_ty) }, ); - let return_ty = self.insert_type_vars(return_ty); - if let Some(rpits) = self.db.return_type_impl_traits(func) { - let mut mode = ImplTraitReplacingMode::ReturnPosition(FxHashSet::default()); - let result = self.insert_inference_vars_for_impl_trait(return_ty, &mut mode); - if let ImplTraitReplacingMode::ReturnPosition(taits) = mode { - tait_candidates.extend(taits); - } - let rpits = (*rpits).as_ref().skip_binder(); - for (id, _) in rpits.impl_traits.iter() { - if let Entry::Vacant(e) = self.result.type_of_rpit.entry(id) { - never!("Missed RPIT in `insert_inference_vars_for_rpit`"); - e.insert(self.types.error); - } - } - result - } else { - return_ty - } + self.process_user_written_ty(return_ty) } None => self.types.unit, }; - self.return_ty = self.process_user_written_ty(return_ty); self.return_coercion = Some(CoerceMany::new(self.return_ty)); - - // Functions might be defining usage sites of TAITs. - // To define an TAITs, that TAIT must appear in the function's signatures. - // So, it suffices to check for params and return types. - fold_tys(self.interner(), self.return_ty, |ty| { - match ty.kind() { - TyKind::Alias(AliasTyKind::Opaque, _) | TyKind::Infer(..) => { - tait_candidates.insert(self.return_ty); - } - _ => {} - } - ty - }); - - self.make_tait_coercion_table(tait_candidates.iter().copied()); } #[inline] @@ -1204,193 +1162,6 @@ impl<'body, 'db> InferenceContext<'body, 'db> { self.table.interner() } - fn insert_inference_vars_for_impl_trait( - &mut self, - t: T, - mode: &mut ImplTraitReplacingMode<'db>, - ) -> T - where - T: TypeFoldable>, - { - fold_tys(self.interner(), t, |ty| { - let ty = self.table.try_structurally_resolve_type(ty); - let opaque_ty_id = match ty.kind() { - TyKind::Alias(AliasTyKind::Opaque, alias_ty) => alias_ty.def_id.expect_opaque_ty(), - _ => return ty, - }; - let (impl_traits, idx) = match self.db.lookup_intern_impl_trait_id(opaque_ty_id) { - // We don't replace opaque types from other kind with inference vars - // because `insert_inference_vars_for_impl_traits` for each kinds - // and unreplaced opaque types of other kind are resolved while - // inferencing because of `tait_coercion_table`. - ImplTraitId::ReturnTypeImplTrait(def, idx) => { - if matches!(mode, ImplTraitReplacingMode::TypeAlias) { - // RPITs don't have `tait_coercion_table`, so use inserted inference - // vars for them. - if let Some(ty) = self.result.type_of_rpit.get(idx) { - return *ty; - } - return ty; - } - (self.db.return_type_impl_traits(def), idx) - } - ImplTraitId::TypeAliasImplTrait(def, idx) => { - if let ImplTraitReplacingMode::ReturnPosition(taits) = mode { - // Gather TAITs while replacing RPITs because TAITs inside RPITs - // may not visited while replacing TAITs - taits.insert(ty); - return ty; - } - (self.db.type_alias_impl_traits(def), idx) - } - }; - let Some(impl_traits) = impl_traits else { - return ty; - }; - let bounds = - (*impl_traits).as_ref().map_bound(|its| its.impl_traits[idx].predicates.as_slice()); - let var = match self.result.type_of_rpit.entry(idx) { - Entry::Occupied(entry) => return *entry.get(), - Entry::Vacant(entry) => *entry.insert(self.table.next_ty_var()), - }; - for clause in bounds.iter_identity_copied() { - let clause = self.insert_inference_vars_for_impl_trait(clause, mode); - self.table.register_predicate(Obligation::new( - self.interner(), - ObligationCause::new(), - self.table.trait_env.env, - clause, - )); - } - var - }) - } - - /// The coercion of a non-inference var into an opaque type should fail, - /// but not in the defining sites of the TAITs. - /// In such cases, we insert an proxy inference var for each TAIT, - /// and coerce into it instead of TAIT itself. - /// - /// The inference var stretagy is effective because; - /// - /// - It can still unify types that coerced into TAITs - /// - We are pushing `impl Trait` bounds into it - /// - /// This function inserts a map that maps the opaque type to that proxy inference var. - fn make_tait_coercion_table(&mut self, tait_candidates: impl Iterator>) { - struct TypeAliasImplTraitCollector<'a, 'db> { - db: &'a dyn HirDatabase, - table: &'a mut InferenceTable<'db>, - assocs: FxHashMap)>, - non_assocs: FxHashMap>, - } - - impl<'db> TypeVisitor> for TypeAliasImplTraitCollector<'_, 'db> { - type Result = (); - - fn visit_ty(&mut self, ty: Ty<'db>) { - let ty = self.table.try_structurally_resolve_type(ty); - - if let TyKind::Alias(AliasTyKind::Opaque, alias_ty) = ty.kind() - && let id = alias_ty.def_id.expect_opaque_ty() - && let ImplTraitId::TypeAliasImplTrait(alias_id, _) = - self.db.lookup_intern_impl_trait_id(id) - { - let loc = self.db.lookup_intern_type_alias(alias_id); - match loc.container { - ItemContainerId::ImplId(impl_id) => { - self.assocs.insert(id, (impl_id, ty)); - } - ItemContainerId::ModuleId(..) | ItemContainerId::ExternBlockId(..) => { - self.non_assocs.insert(id, ty); - } - _ => {} - } - } - - ty.super_visit_with(self) - } - } - - let mut collector = TypeAliasImplTraitCollector { - db: self.db, - table: &mut self.table, - assocs: FxHashMap::default(), - non_assocs: FxHashMap::default(), - }; - for ty in tait_candidates { - ty.visit_with(&mut collector); - } - - // Non-assoc TAITs can be define-used everywhere as long as they are - // in function signatures or const types, etc - let mut taits = collector.non_assocs; - - // assoc TAITs(ATPITs) can be only define-used inside their impl block. - // They cannot be define-used in inner items like in the following; - // - // ``` - // impl Trait for Struct { - // type Assoc = impl Default; - // - // fn assoc_fn() -> Self::Assoc { - // let foo: Self::Assoc = true; // Allowed here - // - // fn inner() -> Self::Assoc { - // false // Not allowed here - // } - // - // foo - // } - // } - // ``` - let impl_id = match self.owner { - DefWithBodyId::FunctionId(it) => { - let loc = self.db.lookup_intern_function(it); - if let ItemContainerId::ImplId(impl_id) = loc.container { - Some(impl_id) - } else { - None - } - } - DefWithBodyId::ConstId(it) => { - let loc = self.db.lookup_intern_const(it); - if let ItemContainerId::ImplId(impl_id) = loc.container { - Some(impl_id) - } else { - None - } - } - _ => None, - }; - - if let Some(impl_id) = impl_id { - taits.extend(collector.assocs.into_iter().filter_map(|(id, (impl_, ty))| { - if impl_ == impl_id { Some((id, ty)) } else { None } - })); - } - - let tait_coercion_table: FxHashMap<_, _> = taits - .into_iter() - .filter_map(|(id, ty)| { - if let ImplTraitId::TypeAliasImplTrait(..) = self.db.lookup_intern_impl_trait_id(id) - { - let ty = self.insert_inference_vars_for_impl_trait( - ty, - &mut ImplTraitReplacingMode::TypeAlias, - ); - Some((id, ty)) - } else { - None - } - }) - .collect(); - - if !tait_coercion_table.is_empty() { - self.table.tait_coercion_table = Some(tait_coercion_table); - } - } - fn infer_body(&mut self) { match self.return_coercion { Some(_) => self.infer_return(self.body.body_expr), @@ -2006,12 +1777,15 @@ impl<'body, 'db> InferenceContext<'body, 'db> { Some(struct_.into()) } - fn get_traits_in_scope(&self) -> Either, &FxHashSet> { - let mut b_traits = self.resolver.traits_in_scope_from_block_scopes().peekable(); + fn get_traits_in_scope<'a>( + resolver: &Resolver<'db>, + traits_in_scope: &'a FxHashSet, + ) -> Either, &'a FxHashSet> { + let mut b_traits = resolver.traits_in_scope_from_block_scopes().peekable(); if b_traits.peek().is_some() { - Either::Left(self.traits_in_scope.iter().copied().chain(b_traits).collect()) + Either::Left(traits_in_scope.iter().copied().chain(b_traits).collect()) } else { - Either::Right(&self.traits_in_scope) + Either::Right(traits_in_scope) } } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs index 78889ccb89a28..40de9234abc54 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs @@ -60,8 +60,7 @@ use crate::{ next_solver::{ Binder, BoundConst, BoundRegion, BoundRegionKind, BoundTy, BoundTyKind, CallableIdWrapper, Canonical, ClauseKind, CoercePredicate, Const, ConstKind, DbInterner, ErrorGuaranteed, - GenericArgs, PolyFnSig, PredicateKind, Region, RegionKind, SolverDefId, TraitRef, Ty, - TyKind, + GenericArgs, PolyFnSig, PredicateKind, Region, RegionKind, TraitRef, Ty, TyKind, infer::{ InferCtxt, InferOk, InferResult, relate::RelateResult, @@ -223,24 +222,6 @@ impl<'a, 'b, 'db> Coerce<'a, 'b, 'db> { } } - // If we are coercing into a TAIT, coerce into its proxy inference var, instead. - // FIXME(next-solver): This should not be here. This is not how rustc does thing, and it also not allows us - // to normalize opaques defined in our scopes. Instead, we should properly register - // `TypingMode::Analysis::defining_opaque_types_and_generators`, and rely on the solver to reveal - // them for us (we'll also need some global-like registry for the values, something we cannot - // really implement, therefore we can really support only RPITs and ITIAT or the new `#[define_opaque]` - // TAIT, not the old global TAIT). - let mut b = b; - if let Some(tait_table) = &self.table.tait_coercion_table - && let TyKind::Alias(rustc_type_ir::Opaque, opaque_ty) = b.kind() - && let SolverDefId::InternedOpaqueTyId(opaque_ty_id) = opaque_ty.def_id - && !matches!(a.kind(), TyKind::Infer(..) | TyKind::Alias(rustc_type_ir::Opaque, _)) - && let Some(ty) = tait_table.get(&opaque_ty_id) - { - b = self.table.shallow_resolve(*ty); - } - let b = b; - // Coercing *from* an unresolved inference variable means that // we have no information about the source type. This will always // ultimately fall back to some form of subtyping. @@ -1528,7 +1509,7 @@ fn coerce<'db>( env: Arc>, tys: &Canonical<'db, (Ty<'db>, Ty<'db>)>, ) -> Result<(Vec>, Ty<'db>), TypeError>> { - let mut table = InferenceTable::new(db, env); + let mut table = InferenceTable::new(db, env, None); let interner = table.interner(); let ((ty1_with_vars, ty2_with_vars), vars) = table.infer_ctxt.instantiate_canonical(tys); 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 fd4e374d9c899..b7ab109b3b9da 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 @@ -1458,10 +1458,11 @@ impl<'db> InferenceContext<'_, 'db> { ) -> Ty<'db> { let coerce_ty = expected.coercion_target_type(&mut self.table); let g = self.resolver.update_to_inner_scope(self.db, self.owner, expr); - let prev_env = block_id.map(|block_id| { + let prev_state = block_id.map(|block_id| { let prev_env = self.table.trait_env.clone(); TraitEnvironment::with_block(&mut self.table.trait_env, block_id); - prev_env + let prev_block = self.table.infer_ctxt.interner.block.replace(block_id); + (prev_env, prev_block) }); let (break_ty, ty) = @@ -1576,8 +1577,9 @@ impl<'db> InferenceContext<'_, 'db> { } }); self.resolver.reset_to_guard(g); - if let Some(prev_env) = prev_env { + if let Some((prev_env, prev_block)) = prev_state { self.table.trait_env = prev_env; + self.table.infer_ctxt.interner.block = prev_block; } break_ty.unwrap_or(ty) @@ -1689,10 +1691,11 @@ impl<'db> InferenceContext<'_, 'db> { // work out while people are typing let canonicalized_receiver = self.canonicalize(receiver_ty); let resolved = method_resolution::lookup_method( - self.db, &canonicalized_receiver, - self.table.trait_env.clone(), - self.get_traits_in_scope().as_ref().left_or_else(|&it| it), + &mut self.table, + Self::get_traits_in_scope(&self.resolver, &self.traits_in_scope) + .as_ref() + .left_or_else(|&it| it), VisibleFromModule::Filter(self.resolver.module()), name, ); @@ -1844,10 +1847,11 @@ impl<'db> InferenceContext<'_, 'db> { let canonicalized_receiver = self.canonicalize(receiver_ty); let resolved = method_resolution::lookup_method( - self.db, &canonicalized_receiver, - self.table.trait_env.clone(), - self.get_traits_in_scope().as_ref().left_or_else(|&it| it), + &mut self.table, + Self::get_traits_in_scope(&self.resolver, &self.traits_in_scope) + .as_ref() + .left_or_else(|&it| it), VisibleFromModule::Filter(self.resolver.module()), method_name, ); @@ -1892,9 +1896,10 @@ impl<'db> InferenceContext<'_, 'db> { let assoc_func_with_same_name = method_resolution::iterate_method_candidates( &canonicalized_receiver, - self.db, - self.table.trait_env.clone(), - self.get_traits_in_scope().as_ref().left_or_else(|&it| it), + &mut self.table, + Self::get_traits_in_scope(&self.resolver, &self.traits_in_scope) + .as_ref() + .left_or_else(|&it| it), VisibleFromModule::Filter(self.resolver.module()), Some(method_name), method_resolution::LookupMode::Path, diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/opaques.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/opaques.rs new file mode 100644 index 0000000000000..f7719f50ac3ef --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/opaques.rs @@ -0,0 +1,147 @@ +//! Defining opaque types via inference. + +use rustc_type_ir::{TypeVisitableExt, fold_regions}; +use tracing::{debug, instrument}; + +use crate::{ + infer::InferenceContext, + next_solver::{ + EarlyBinder, OpaqueTypeKey, SolverDefId, TypingMode, + infer::{opaque_types::OpaqueHiddenType, traits::ObligationCause}, + }, +}; + +impl<'db> InferenceContext<'_, 'db> { + /// This takes all the opaque type uses during HIR typeck. It first computes + /// the concrete hidden type by iterating over all defining uses. + /// + /// A use during HIR typeck is defining if all non-lifetime arguments are + /// unique generic parameters and the hidden type does not reference any + /// inference variables. + /// + /// It then uses these defining uses to guide inference for all other uses. + #[instrument(level = "debug", skip(self))] + pub(super) fn handle_opaque_type_uses(&mut self) { + // We clone the opaques instead of stealing them here as they are still used for + // normalization in the next generation trait solver. + let opaque_types: Vec<_> = self.table.infer_ctxt.clone_opaque_types(); + + self.compute_definition_site_hidden_types(opaque_types); + } +} + +#[expect(unused, reason = "rustc has this")] +#[derive(Copy, Clone, Debug)] +enum UsageKind<'db> { + None, + NonDefiningUse(OpaqueTypeKey<'db>, OpaqueHiddenType<'db>), + UnconstrainedHiddenType(OpaqueHiddenType<'db>), + HasDefiningUse(OpaqueHiddenType<'db>), +} + +impl<'db> UsageKind<'db> { + fn merge(&mut self, other: UsageKind<'db>) { + match (&*self, &other) { + (UsageKind::HasDefiningUse(_), _) | (_, UsageKind::None) => unreachable!(), + (UsageKind::None, _) => *self = other, + // When mergining non-defining uses, prefer earlier ones. This means + // the error happens as early as possible. + ( + UsageKind::NonDefiningUse(..) | UsageKind::UnconstrainedHiddenType(..), + UsageKind::NonDefiningUse(..), + ) => {} + // When merging unconstrained hidden types, we prefer later ones. This is + // used as in most cases, the defining use is the final return statement + // of our function, and other uses with defining arguments are likely not + // intended to be defining. + ( + UsageKind::NonDefiningUse(..) | UsageKind::UnconstrainedHiddenType(..), + UsageKind::UnconstrainedHiddenType(..) | UsageKind::HasDefiningUse(_), + ) => *self = other, + } + } +} + +impl<'db> InferenceContext<'_, 'db> { + fn compute_definition_site_hidden_types( + &mut self, + mut opaque_types: Vec<(OpaqueTypeKey<'db>, OpaqueHiddenType<'db>)>, + ) { + for entry in opaque_types.iter_mut() { + *entry = self.table.infer_ctxt.resolve_vars_if_possible(*entry); + } + debug!(?opaque_types); + + let interner = self.interner(); + let TypingMode::Analysis { defining_opaque_types_and_generators } = + self.table.infer_ctxt.typing_mode() + else { + unreachable!(); + }; + + for def_id in defining_opaque_types_and_generators { + let def_id = match def_id { + SolverDefId::InternedOpaqueTyId(it) => it, + _ => continue, + }; + + // We do actually need to check this the second pass (we can't just + // store this), because we can go from `UnconstrainedHiddenType` to + // `HasDefiningUse` (because of fallback) + let mut usage_kind = UsageKind::None; + for &(opaque_type_key, hidden_type) in &opaque_types { + if opaque_type_key.def_id != def_id.into() { + continue; + } + + usage_kind.merge(self.consider_opaque_type_use(opaque_type_key, hidden_type)); + + if let UsageKind::HasDefiningUse(..) = usage_kind { + break; + } + } + + if let UsageKind::HasDefiningUse(ty) = usage_kind { + for &(opaque_type_key, hidden_type) in &opaque_types { + if opaque_type_key.def_id != def_id.into() { + continue; + } + + let expected = + EarlyBinder::bind(ty.ty).instantiate(interner, opaque_type_key.args); + self.demand_eqtype(expected, hidden_type.ty); + } + + self.result.type_of_opaque.insert(def_id, ty.ty); + + continue; + } + + self.result.type_of_opaque.insert(def_id, self.types.error); + } + } + + #[tracing::instrument(skip(self), ret)] + fn consider_opaque_type_use( + &self, + opaque_type_key: OpaqueTypeKey<'db>, + hidden_type: OpaqueHiddenType<'db>, + ) -> UsageKind<'db> { + // We ignore uses of the opaque if they have any inference variables + // as this can frequently happen with recursive calls. + // + // See `tests/ui/traits/next-solver/opaques/universal-args-non-defining.rs`. + if hidden_type.ty.has_non_region_infer() { + return UsageKind::UnconstrainedHiddenType(hidden_type); + } + + let cause = ObligationCause::new(); + let at = self.table.infer_ctxt.at(&cause, self.table.trait_env.env); + let hidden_type = match at.deeply_normalize(hidden_type) { + Ok(hidden_type) => hidden_type, + Err(_errors) => OpaqueHiddenType { ty: self.types.error }, + }; + let hidden_type = fold_regions(self.interner(), hidden_type, |_, _| self.types.re_erased); + UsageKind::HasDefiningUse(hidden_type) + } +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/path.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/path.rs index 2dae7cb04ffa1..9ade8420138dd 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/path.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/path.rs @@ -310,9 +310,10 @@ impl<'db> InferenceContext<'_, 'db> { let mut not_visible = None; let res = method_resolution::iterate_method_candidates( &canonical_ty, - self.db, - self.table.trait_env.clone(), - self.get_traits_in_scope().as_ref().left_or_else(|&it| it), + &mut self.table, + Self::get_traits_in_scope(&self.resolver, &self.traits_in_scope) + .as_ref() + .left_or_else(|&it| it), VisibleFromModule::Filter(self.resolver.module()), Some(name), method_resolution::LookupMode::Path, 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 a18cdda559d0f..0f582a1c2313b 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 @@ -2,10 +2,10 @@ use std::fmt; -use hir_def::{AdtId, GenericParamId, lang_item::LangItem}; +use hir_def::{AdtId, DefWithBodyId, GenericParamId, lang_item::LangItem}; use hir_expand::name::Name; use intern::sym; -use rustc_hash::{FxHashMap, FxHashSet}; +use rustc_hash::FxHashSet; use rustc_type_ir::{ DebruijnIndex, InferConst, InferTy, RegionVid, TyVid, TypeFoldable, TypeFolder, TypeSuperFoldable, TypeVisitableExt, UpcastFrom, @@ -17,12 +17,12 @@ use triomphe::Arc; use crate::{ TraitEnvironment, - db::{HirDatabase, InternedOpaqueTyId}, + db::HirDatabase, infer::InferenceContext, next_solver::{ self, AliasTy, Binder, Canonical, ClauseKind, Const, ConstKind, DbInterner, ErrorGuaranteed, GenericArg, GenericArgs, Predicate, PredicateKind, Region, RegionKind, - SolverDefId, SolverDefIds, TraitRef, Ty, TyKind, TypingMode, + SolverDefId, TraitRef, Ty, TyKind, TypingMode, fulfill::{FulfillmentCtxt, NextSolverError}, infer::{ DbInternerInferExt, InferCtxt, InferOk, InferResult, @@ -139,10 +139,7 @@ fn could_unify_impl<'db>( select: for<'a> fn(&mut ObligationCtxt<'a, 'db>) -> Vec>, ) -> bool { let interner = DbInterner::new_with(db, Some(env.krate), env.block); - // FIXME(next-solver): I believe this should use `PostAnalysis` (this is only used for IDE things), - // but this causes some bug because of our incorrect impl of `type_of_opaque_hir_typeck()` for TAIT - // and async blocks. - let infcx = interner.infer_ctxt().build(TypingMode::non_body_analysis()); + let infcx = interner.infer_ctxt().build(TypingMode::PostAnalysis); let cause = ObligationCause::dummy(); let at = infcx.at(&cause, env.env); let ((ty1_with_vars, ty2_with_vars), _) = infcx.instantiate_canonical(tys); @@ -158,7 +155,6 @@ fn could_unify_impl<'db>( pub(crate) struct InferenceTable<'db> { pub(crate) db: &'db dyn HirDatabase, pub(crate) trait_env: Arc>, - pub(crate) tait_coercion_table: Option>>, pub(crate) infer_ctxt: InferCtxt<'db>, pub(super) fulfillment_cx: FulfillmentCtxt<'db>, pub(super) diverging_type_vars: FxHashSet>, @@ -170,15 +166,23 @@ pub(crate) struct InferenceTableSnapshot<'db> { } impl<'db> InferenceTable<'db> { - pub(crate) fn new(db: &'db dyn HirDatabase, trait_env: Arc>) -> Self { + /// Inside hir-ty you should use this for inference only, and always pass `owner`. + /// Outside it, always pass `owner = None`. + pub(crate) fn new( + db: &'db dyn HirDatabase, + trait_env: Arc>, + owner: Option, + ) -> Self { let interner = DbInterner::new_with(db, Some(trait_env.krate), trait_env.block); - let infer_ctxt = interner.infer_ctxt().build(rustc_type_ir::TypingMode::Analysis { - defining_opaque_types_and_generators: SolverDefIds::new_from_iter(interner, []), - }); + let typing_mode = match owner { + Some(owner) => TypingMode::typeck_for_body(interner, owner.into()), + // IDE things wants to reveal opaque types. + None => TypingMode::PostAnalysis, + }; + let infer_ctxt = interner.infer_ctxt().build(typing_mode); InferenceTable { db, trait_env, - tait_coercion_table: None, fulfillment_cx: FulfillmentCtxt::new(&infer_ctxt), infer_ctxt, diverging_type_vars: FxHashSet::default(), @@ -698,40 +702,7 @@ impl<'db> InferenceTable<'db> { where T: TypeFoldable>, { - struct Folder<'a, 'db> { - table: &'a mut InferenceTable<'db>, - } - impl<'db> TypeFolder> for Folder<'_, 'db> { - fn cx(&self) -> DbInterner<'db> { - self.table.interner() - } - - fn fold_ty(&mut self, ty: Ty<'db>) -> Ty<'db> { - if !ty.references_error() { - return ty; - } - - if ty.is_ty_error() { self.table.next_ty_var() } else { ty.super_fold_with(self) } - } - - fn fold_const(&mut self, ct: Const<'db>) -> Const<'db> { - if !ct.references_error() { - return ct; - } - - if ct.is_ct_error() { - self.table.next_const_var() - } else { - ct.super_fold_with(self) - } - } - - fn fold_region(&mut self, r: Region<'db>) -> Region<'db> { - if r.is_error() { self.table.next_region_var() } else { r } - } - } - - ty.fold_with(&mut Folder { table: self }) + self.infer_ctxt.insert_type_vars(ty) } /// Replaces `Ty::Error` by a new type var, so we can maybe still infer it. 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 25579e04ed014..fdacc1d899dc9 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs @@ -27,9 +27,11 @@ mod infer; mod inhabitedness; mod lower; pub mod next_solver; +mod opaques; mod specialization; mod target_feature; mod utils; +mod variance; pub mod autoderef; pub mod consteval; @@ -50,7 +52,6 @@ pub mod traits; mod test_db; #[cfg(test)] mod tests; -mod variance; use std::hash::Hash; @@ -471,6 +472,7 @@ where } } +/// To be used from `hir` only. pub fn callable_sig_from_fn_trait<'db>( self_ty: Ty<'db>, trait_env: Arc>, @@ -482,7 +484,7 @@ pub fn callable_sig_from_fn_trait<'db>( .trait_items(db) .associated_type_by_name(&Name::new_symbol_root(sym::Output))?; - let mut table = InferenceTable::new(db, trait_env.clone()); + let mut table = InferenceTable::new(db, trait_env.clone(), None); // Register two obligations: // - Self: FnOnce 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 cec63566338f1..1e30897362052 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 @@ -489,9 +489,8 @@ pub fn def_crates<'db>( /// Look up the method with the given name. pub(crate) fn lookup_method<'db>( - db: &'db dyn HirDatabase, ty: &Canonical<'db, Ty<'db>>, - env: Arc>, + table: &mut InferenceTable<'db>, traits_in_scope: &FxHashSet, visible_from_module: VisibleFromModule, name: &Name, @@ -499,8 +498,7 @@ pub(crate) fn lookup_method<'db>( let mut not_visible = None; let res = iterate_method_candidates( ty, - db, - env, + table, traits_in_scope, visible_from_module, Some(name), @@ -656,8 +654,7 @@ impl ReceiverAdjustments { // FIXME add a context type here? pub(crate) fn iterate_method_candidates<'db, T>( ty: &Canonical<'db, Ty<'db>>, - db: &'db dyn HirDatabase, - env: Arc>, + table: &mut InferenceTable<'db>, traits_in_scope: &FxHashSet, visible_from_module: VisibleFromModule, name: Option<&Name>, @@ -665,10 +662,9 @@ pub(crate) fn iterate_method_candidates<'db, T>( mut callback: impl FnMut(ReceiverAdjustments, AssocItemId, bool) -> Option, ) -> Option { let mut slot = None; - _ = iterate_method_candidates_dyn( + _ = iterate_method_candidates_dyn_impl( ty, - db, - env, + table, traits_in_scope, visible_from_module, name, @@ -985,6 +981,7 @@ pub fn check_orphan_rules<'db>(db: &'db dyn HirDatabase, impl_: ImplId) -> bool is_not_orphan } +/// To be used from `hir` only. pub fn iterate_path_candidates<'db>( ty: &Canonical<'db, Ty<'db>>, db: &'db dyn HirDatabase, @@ -1007,6 +1004,7 @@ pub fn iterate_path_candidates<'db>( ) } +/// To be used from `hir` only. pub fn iterate_method_candidates_dyn<'db>( ty: &Canonical<'db, Ty<'db>>, db: &'db dyn HirDatabase, @@ -1016,6 +1014,26 @@ pub fn iterate_method_candidates_dyn<'db>( name: Option<&Name>, mode: LookupMode, callback: &mut dyn MethodCandidateCallback, +) -> ControlFlow<()> { + iterate_method_candidates_dyn_impl( + ty, + &mut InferenceTable::new(db, env, None), + traits_in_scope, + visible_from_module, + name, + mode, + callback, + ) +} + +fn iterate_method_candidates_dyn_impl<'db>( + ty: &Canonical<'db, Ty<'db>>, + table: &mut InferenceTable<'db>, + traits_in_scope: &FxHashSet, + visible_from_module: VisibleFromModule, + name: Option<&Name>, + mode: LookupMode, + callback: &mut dyn MethodCandidateCallback, ) -> ControlFlow<()> { let _p = tracing::info_span!( "iterate_method_candidates_dyn", @@ -1046,28 +1064,28 @@ pub fn iterate_method_candidates_dyn<'db>( // the methods by autoderef order of *receiver types*, not *self // types*. - let mut table = InferenceTable::new(db, env); - let ty = table.instantiate_canonical(*ty); - let deref_chain = autoderef_method_receiver(&mut table, ty); - - deref_chain.into_iter().try_for_each(|(receiver_ty, adj)| { - iterate_method_candidates_with_autoref( - &mut table, - receiver_ty, - adj, - traits_in_scope, - visible_from_module, - name, - callback, - ) + table.run_in_snapshot(|table| { + let ty = table.instantiate_canonical(*ty); + let deref_chain = autoderef_method_receiver(table, ty); + + deref_chain.into_iter().try_for_each(|(receiver_ty, adj)| { + iterate_method_candidates_with_autoref( + table, + receiver_ty, + adj, + traits_in_scope, + visible_from_module, + name, + callback, + ) + }) }) } LookupMode::Path => { // No autoderef for path lookups iterate_method_candidates_for_self_ty( ty, - db, - env, + table, traits_in_scope, visible_from_module, name, @@ -1250,39 +1268,39 @@ fn iterate_method_candidates_by_receiver<'db>( #[tracing::instrument(skip_all, fields(name = ?name))] fn iterate_method_candidates_for_self_ty<'db>( self_ty: &Canonical<'db, Ty<'db>>, - db: &'db dyn HirDatabase, - env: Arc>, + table: &mut InferenceTable<'db>, traits_in_scope: &FxHashSet, visible_from_module: VisibleFromModule, name: Option<&Name>, callback: &mut dyn MethodCandidateCallback, ) -> ControlFlow<()> { - let mut table = InferenceTable::new(db, env); - let self_ty = table.instantiate_canonical(*self_ty); - iterate_inherent_methods( - self_ty, - &mut table, - name, - None, - None, - visible_from_module, - LookupMode::Path, - &mut |adjustments, item, is_visible| { - callback.on_inherent_method(adjustments, item, is_visible) - }, - )?; - iterate_trait_method_candidates( - self_ty, - &mut table, - traits_in_scope, - name, - None, - None, - LookupMode::Path, - &mut |adjustments, item, is_visible| { - callback.on_trait_method(adjustments, item, is_visible) - }, - ) + table.run_in_snapshot(|table| { + let self_ty = table.instantiate_canonical(*self_ty); + iterate_inherent_methods( + self_ty, + table, + name, + None, + None, + visible_from_module, + LookupMode::Path, + &mut |adjustments, item, is_visible| { + callback.on_inherent_method(adjustments, item, is_visible) + }, + )?; + iterate_trait_method_candidates( + self_ty, + table, + traits_in_scope, + name, + None, + None, + LookupMode::Path, + &mut |adjustments, item, is_visible| { + callback.on_trait_method(adjustments, item, is_visible) + }, + ) + }) } #[tracing::instrument(skip_all, fields(name = ?name, visible_from_module, receiver_ty))] diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/borrowck.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/borrowck.rs index db16c943968fd..01892657bc21a 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/borrowck.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/borrowck.rs @@ -17,7 +17,7 @@ use crate::{ display::DisplayTarget, mir::OperandKind, next_solver::{ - DbInterner, GenericArgs, SolverDefIds, Ty, TypingMode, + DbInterner, GenericArgs, Ty, TypingMode, infer::{DbInternerInferExt, InferCtxt}, }, }; @@ -100,11 +100,11 @@ pub fn borrowck_query<'db>( let interner = DbInterner::new_with(db, Some(module.krate()), module.containing_block()); let env = db.trait_environment_for_body(def); let mut res = vec![]; + // This calculates opaques defining scope which is a bit costly therefore is put outside `all_mir_bodies()`. + let typing_mode = TypingMode::borrowck(interner, def.into()); all_mir_bodies(db, def, |body| { // FIXME(next-solver): Opaques. - let infcx = interner.infer_ctxt().build(TypingMode::Borrowck { - defining_opaque_types: SolverDefIds::new_from_iter(interner, []), - }); + let infcx = interner.infer_ctxt().build(typing_mode); res.push(BorrowckResult { mutability_of_locals: mutability_of_locals(&infcx, &body), moved_out_of_ref: moved_out_of_ref(&infcx, &env, &body), diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/def_id.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/def_id.rs index 0ff0b086a0877..77f21062b4733 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/def_id.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/def_id.rs @@ -154,6 +154,29 @@ impl From for SolverDefId { } } +impl TryFrom for DefWithBodyId { + type Error = (); + + #[inline] + fn try_from(value: SolverDefId) -> Result { + let id = match value { + SolverDefId::ConstId(id) => id.into(), + SolverDefId::FunctionId(id) => id.into(), + SolverDefId::StaticId(id) => id.into(), + SolverDefId::EnumVariantId(id) | SolverDefId::Ctor(Ctor::Enum(id)) => id.into(), + SolverDefId::InternedOpaqueTyId(_) + | SolverDefId::TraitId(_) + | SolverDefId::TypeAliasId(_) + | SolverDefId::ImplId(_) + | SolverDefId::InternedClosureId(_) + | SolverDefId::InternedCoroutineId(_) + | SolverDefId::Ctor(Ctor::Struct(_)) + | SolverDefId::AdtId(_) => return Err(()), + }; + Ok(id) + } +} + impl TryFrom for GenericDefId { type Error = (); 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 90bd44aee86f0..dedd6a1a6da5c 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 @@ -63,6 +63,14 @@ impl<'db> GenericArg<'db> { } } + #[inline] + pub(crate) fn expect_region(self) -> Region<'db> { + match self { + GenericArg::Lifetime(region) => region, + _ => panic!("expected a region, got {self:?}"), + } + } + pub fn error_from_id(interner: DbInterner<'db>, id: GenericParamId) -> GenericArg<'db> { match id { GenericParamId::TypeParamId(_) => Ty::new_error(interner, ErrorGuaranteed).into(), diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/infer/mod.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/infer/mod.rs index 36c6c48c5a0b7..7b8f52bf7203f 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/infer/mod.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/infer/mod.rs @@ -13,27 +13,27 @@ use opaque_types::{OpaqueHiddenType, OpaqueTypeStorage}; use region_constraints::{RegionConstraintCollector, RegionConstraintStorage}; use rustc_next_trait_solver::solve::SolverDelegateEvalExt; use rustc_pattern_analysis::Captures; -use rustc_type_ir::TypeFoldable; -use rustc_type_ir::error::{ExpectedFound, TypeError}; -use rustc_type_ir::inherent::{ - Const as _, GenericArg as _, GenericArgs as _, IntoKind, SliceLike, Term as _, Ty as _, -}; use rustc_type_ir::{ ClosureKind, ConstVid, FloatVarValue, FloatVid, GenericArgKind, InferConst, InferTy, - IntVarValue, IntVid, OutlivesPredicate, RegionVid, TyVid, UniverseIndex, + IntVarValue, IntVid, OutlivesPredicate, RegionVid, TermKind, TyVid, TypeFoldable, TypeFolder, + TypeSuperFoldable, TypeVisitableExt, UniverseIndex, + error::{ExpectedFound, TypeError}, + inherent::{ + Const as _, GenericArg as _, GenericArgs as _, IntoKind, SliceLike, Term as _, Ty as _, + }, }; -use rustc_type_ir::{TermKind, TypeVisitableExt}; use snapshot::undo_log::InferCtxtUndoLogs; use tracing::{debug, instrument}; use traits::{ObligationCause, PredicateObligations}; use type_variable::TypeVariableOrigin; use unify_key::{ConstVariableOrigin, ConstVariableValue, ConstVidKey}; -use crate::next_solver::fold::BoundVarReplacerDelegate; -use crate::next_solver::infer::select::EvaluationResult; -use crate::next_solver::infer::traits::PredicateObligation; -use crate::next_solver::obligation_ctxt::ObligationCtxt; -use crate::next_solver::{BoundConst, BoundRegion, BoundTy, BoundVarKind, Goal, SolverContext}; +use crate::next_solver::{ + BoundConst, BoundRegion, BoundTy, BoundVarKind, Goal, SolverContext, + fold::BoundVarReplacerDelegate, + infer::{select::EvaluationResult, traits::PredicateObligation}, + obligation_ctxt::ObligationCtxt, +}; use super::{ AliasTerm, Binder, CanonicalQueryInput, CanonicalVarValues, Const, ConstKind, DbInterner, @@ -46,7 +46,7 @@ use super::{ pub mod at; pub mod canonical; mod context; -mod opaque_types; +pub mod opaque_types; pub mod region_constraints; pub mod relate; pub mod resolve; @@ -400,6 +400,46 @@ impl<'db> InferCtxt<'db> { )) } + pub(crate) fn insert_type_vars(&self, ty: T) -> T + where + T: TypeFoldable>, + { + struct Folder<'a, 'db> { + infcx: &'a InferCtxt<'db>, + } + impl<'db> TypeFolder> for Folder<'_, 'db> { + fn cx(&self) -> DbInterner<'db> { + self.infcx.interner + } + + fn fold_ty(&mut self, ty: Ty<'db>) -> Ty<'db> { + if !ty.references_error() { + return ty; + } + + if ty.is_ty_error() { self.infcx.next_ty_var() } else { ty.super_fold_with(self) } + } + + fn fold_const(&mut self, ct: Const<'db>) -> Const<'db> { + if !ct.references_error() { + return ct; + } + + if ct.is_ct_error() { + self.infcx.next_const_var() + } else { + ct.super_fold_with(self) + } + } + + fn fold_region(&mut self, r: Region<'db>) -> Region<'db> { + if r.is_error() { self.infcx.next_region_var() } else { r } + } + } + + ty.fold_with(&mut Folder { infcx: self }) + } + /// Evaluates whether the predicate can be satisfied in the given /// `ParamEnv`, and returns `false` if not certain. However, this is /// not entirely accurate if inference variables are involved. diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/infer/opaque_types/mod.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/infer/opaque_types/mod.rs index 06d998488e15b..6b6104b2d9035 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/infer/opaque_types/mod.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/infer/opaque_types/mod.rs @@ -4,9 +4,11 @@ pub(crate) mod table; pub(crate) use table::{OpaqueTypeStorage, OpaqueTypeTable}; +use macros::{TypeFoldable, TypeVisitable}; + use crate::next_solver::{OpaqueTypeKey, Ty, infer::InferCtxt}; -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, TypeVisitable, TypeFoldable)] pub struct OpaqueHiddenType<'db> { pub ty: Ty<'db>, } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/infer/opaque_types/table.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/infer/opaque_types/table.rs index 0f8b23870fd02..00177d21ac760 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/infer/opaque_types/table.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/infer/opaque_types/table.rs @@ -122,14 +122,6 @@ impl<'db> OpaqueTypeStorage<'db> { } } -impl<'db> Drop for OpaqueTypeStorage<'db> { - fn drop(&mut self) { - if !self.opaque_types.is_empty() { - panic!("{:?}", self.opaque_types) - } - } -} - pub(crate) struct OpaqueTypeTable<'a, 'db> { storage: &'a mut OpaqueTypeStorage<'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 c1ccbaf78a70b..b18e08bea49b5 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 @@ -7,8 +7,8 @@ pub use tls_db::{attach_db, attach_db_allow_change, with_attached_db}; use base_db::Crate; use hir_def::{ - AdtId, AttrDefId, BlockId, CallableDefId, EnumVariantId, ItemContainerId, StructId, UnionId, - VariantId, + AdtId, AttrDefId, BlockId, CallableDefId, DefWithBodyId, EnumVariantId, ItemContainerId, + StructId, UnionId, VariantId, lang_item::LangItem, signatures::{FieldData, FnFlags, ImplFlags, StructFlags, TraitFlags}, }; @@ -29,7 +29,7 @@ use rustc_type_ir::{ use crate::{ FnAbi, - db::{HirDatabase, InternedCoroutine}, + db::{HirDatabase, InternedCoroutine, InternedCoroutineId}, method_resolution::{ALL_FLOAT_FPS, ALL_INT_FPS, TyFingerprint}, next_solver::{ AdtIdWrapper, BoundConst, CallableIdWrapper, CanonicalVarKind, ClosureIdWrapper, @@ -96,7 +96,7 @@ macro_rules! _interned_vec_nolifetime_salsa { } }; ($name:ident, $ty:ty, nofold) => { - #[salsa::interned(constructor = new_, debug)] + #[salsa::interned(constructor = new_)] pub struct $name { #[returns(ref)] inner_: smallvec::SmallVec<[$ty; 2]>, @@ -119,6 +119,12 @@ macro_rules! _interned_vec_nolifetime_salsa { } } + impl<'db> std::fmt::Debug for $name<'db> { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.as_slice().fmt(fmt) + } + } + impl<'db> rustc_type_ir::inherent::SliceLike for $name<'db> { type Item = $ty; @@ -1866,9 +1872,42 @@ impl<'db> Interner for DbInterner<'db> { Binder::bind_with_vars(inner, bound_vars) } - fn opaque_types_defined_by(self, _defining_anchor: Self::LocalDefId) -> Self::LocalDefIds { - // FIXME(next-solver) - SolverDefIds::new_from_iter(self, []) + fn opaque_types_defined_by(self, def_id: Self::LocalDefId) -> Self::LocalDefIds { + let Ok(def_id) = DefWithBodyId::try_from(def_id) else { + return SolverDefIds::default(); + }; + let mut result = Vec::new(); + crate::opaques::opaque_types_defined_by(self.db, def_id, &mut result); + SolverDefIds::new_from_iter(self, result) + } + + fn opaque_types_and_coroutines_defined_by(self, def_id: Self::LocalDefId) -> Self::LocalDefIds { + let Ok(def_id) = DefWithBodyId::try_from(def_id) else { + return SolverDefIds::default(); + }; + let mut result = Vec::new(); + + crate::opaques::opaque_types_defined_by(self.db, def_id, &mut result); + + // Collect coroutines. + let body = self.db.body(def_id); + body.exprs().for_each(|(expr_id, expr)| { + if matches!( + expr, + hir_def::hir::Expr::Async { .. } + | hir_def::hir::Expr::Closure { + closure_kind: hir_def::hir::ClosureKind::Async + | hir_def::hir::ClosureKind::Coroutine(_), + .. + } + ) { + let coroutine = + InternedCoroutineId::new(self.db, InternedCoroutine(def_id, expr_id)); + result.push(coroutine.into()); + } + }); + + SolverDefIds::new_from_iter(self, result) } fn alias_has_const_conditions(self, _def_id: Self::DefId) -> bool { @@ -1913,12 +1952,10 @@ impl<'db> Interner for DbInterner<'db> { let impl_trait_id = self.db().lookup_intern_impl_trait_id(opaque); match impl_trait_id { crate::ImplTraitId::ReturnTypeImplTrait(func, idx) => { - let infer = self.db().infer(func.into()); - EarlyBinder::bind(infer.type_of_rpit[idx]) + crate::opaques::rpit_hidden_types(self.db, func)[idx] } - crate::ImplTraitId::TypeAliasImplTrait(..) => { - // FIXME(next-solver) - EarlyBinder::bind(Ty::new_error(self, ErrorGuaranteed)) + crate::ImplTraitId::TypeAliasImplTrait(type_alias, idx) => { + crate::opaques::tait_hidden_types(self.db, type_alias)[idx] } } } @@ -1969,13 +2006,6 @@ impl<'db> Interner for DbInterner<'db> { true } - fn opaque_types_and_coroutines_defined_by( - self, - _defining_anchor: Self::LocalDefId, - ) -> Self::LocalDefIds { - Default::default() - } - type Probe = rustc_type_ir::solve::inspect::Probe>; fn mk_probe(self, probe: rustc_type_ir::solve::inspect::Probe) -> Self::Probe { probe diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/solver.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/solver.rs index 487d164f8691c..7b96b4008feca 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/solver.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/solver.rs @@ -2,17 +2,22 @@ use hir_def::{AssocItemId, GeneralConstId}; use rustc_next_trait_solver::delegate::SolverDelegate; -use rustc_type_ir::GenericArgKind; -use rustc_type_ir::lang_items::SolverTraitLangItem; use rustc_type_ir::{ - InferCtxtLike, Interner, PredicatePolarity, TypeFlags, TypeVisitableExt, - inherent::{IntoKind, Term as _, Ty as _}, + AliasTyKind, GenericArgKind, InferCtxtLike, Interner, PredicatePolarity, TypeFlags, + TypeVisitableExt, + inherent::{IntoKind, SliceLike, Term as _, Ty as _}, + lang_items::SolverTraitLangItem, solve::{Certainty, NoSolution}, }; +use tracing::debug; -use crate::next_solver::{CanonicalVarKind, ImplIdWrapper}; -use crate::next_solver::{ - ClauseKind, CoercePredicate, PredicateKind, SubtypePredicate, util::sizedness_fast_path, +use crate::{ + ImplTraitId, + next_solver::{ + AliasTy, CanonicalVarKind, Clause, ClauseKind, CoercePredicate, GenericArgs, ImplIdWrapper, + ParamEnv, Predicate, PredicateKind, SubtypePredicate, Ty, TyKind, fold::fold_tys, + util::sizedness_fast_path, + }, }; use super::{ @@ -76,7 +81,7 @@ impl<'db> SolverDelegate for SolverContext<'db> { fn well_formed_goals( &self, - _param_env: ::ParamEnv, + _param_env: ParamEnv<'db>, _arg: ::Term, ) -> Option< Vec< @@ -125,18 +130,60 @@ impl<'db> SolverDelegate for SolverContext<'db> { fn add_item_bounds_for_hidden_type( &self, - _def_id: ::DefId, - _args: ::GenericArgs, - _param_env: ::ParamEnv, - _hidden_ty: ::Ty, - _goals: &mut Vec< - rustc_type_ir::solve::Goal< - Self::Interner, - ::Predicate, - >, - >, + def_id: SolverDefId, + args: GenericArgs<'db>, + param_env: ParamEnv<'db>, + hidden_ty: Ty<'db>, + goals: &mut Vec>>, ) { - unimplemented!() + let interner = self.interner; + let opaque_id = def_id.expect_opaque_ty(); + // Require that the hidden type is well-formed. We have to + // make sure we wf-check the hidden type to fix #114728. + // + // However, we don't check that all types are well-formed. + // We only do so for types provided by the user or if they are + // "used", e.g. for method selection. + // + // This means we never check the wf requirements of the hidden + // type during MIR borrowck, causing us to infer the wrong + // lifetime for its member constraints which then results in + // unexpected region errors. + goals.push(Goal::new(interner, param_env, ClauseKind::WellFormed(hidden_ty.into()))); + + let replace_opaques_in = |clause: Clause<'db>| { + fold_tys(interner, clause, |ty| match ty.kind() { + // Replace all other mentions of the same opaque type with the hidden type, + // as the bounds must hold on the hidden type after all. + TyKind::Alias( + AliasTyKind::Opaque, + AliasTy { def_id: def_id2, args: args2, .. }, + ) if def_id == def_id2 && args == args2 => hidden_ty, + _ => ty, + }) + }; + + let db = interner.db; + let (opaques_table, opaque_idx) = match opaque_id.loc(db) { + ImplTraitId::ReturnTypeImplTrait(func, opaque_idx) => { + (db.return_type_impl_traits(func), opaque_idx) + } + ImplTraitId::TypeAliasImplTrait(type_alias, opaque_idx) => { + (db.type_alias_impl_traits(type_alias), opaque_idx) + } + }; + let item_bounds = opaques_table + .as_deref() + .unwrap() + .as_ref() + .map_bound(|table| &table.impl_traits[opaque_idx].predicates); + for predicate in item_bounds.iter_instantiated_copied(interner, args.as_slice()) { + let predicate = replace_opaques_in(predicate); + + // Require that the predicate holds for the concrete type. + debug!(?predicate); + goals.push(Goal::new(interner, param_env, predicate)); + } } fn fetch_eligible_assoc_item( @@ -190,8 +237,8 @@ impl<'db> SolverDelegate for SolverContext<'db> { fn is_transmutable( &self, - _dst: ::Ty, - _src: ::Ty, + _dst: Ty<'db>, + _src: Ty<'db>, _assume: ::Const, ) -> Result { unimplemented!() @@ -199,7 +246,7 @@ impl<'db> SolverDelegate for SolverContext<'db> { fn evaluate_const( &self, - _param_env: ::ParamEnv, + _param_env: ParamEnv<'db>, uv: rustc_type_ir::UnevaluatedConst, ) -> Option<::Const> { let c = match uv.def { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/opaques.rs b/src/tools/rust-analyzer/crates/hir-ty/src/opaques.rs new file mode 100644 index 0000000000000..8531f24377394 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-ty/src/opaques.rs @@ -0,0 +1,199 @@ +//! Handling of opaque types, detection of defining scope and hidden type. + +use hir_def::{ + AssocItemId, AssocItemLoc, DefWithBodyId, FunctionId, HasModule, ItemContainerId, TypeAliasId, +}; +use hir_expand::name::Name; +use la_arena::ArenaMap; +use rustc_type_ir::inherent::Ty as _; +use syntax::ast; +use triomphe::Arc; + +use crate::{ + ImplTraitId, + db::{HirDatabase, InternedOpaqueTyId}, + lower::{ImplTraitIdx, ImplTraits}, + next_solver::{ + DbInterner, EarlyBinder, ErrorGuaranteed, SolverDefId, Ty, TypingMode, + infer::{DbInternerInferExt, traits::ObligationCause}, + obligation_ctxt::ObligationCtxt, + }, +}; + +pub(crate) fn opaque_types_defined_by( + db: &dyn HirDatabase, + def_id: DefWithBodyId, + result: &mut Vec, +) { + if let DefWithBodyId::FunctionId(func) = def_id { + // A function may define its own RPITs. + extend_with_opaques( + db, + db.return_type_impl_traits(func), + |opaque_idx| ImplTraitId::ReturnTypeImplTrait(func, opaque_idx), + result, + ); + } + + let extend_with_taits = |type_alias| { + extend_with_opaques( + db, + db.type_alias_impl_traits(type_alias), + |opaque_idx| ImplTraitId::TypeAliasImplTrait(type_alias, opaque_idx), + result, + ); + }; + + // Collect opaques from assoc items. + let extend_with_atpit_from_assoc_items = |assoc_items: &[(Name, AssocItemId)]| { + assoc_items + .iter() + .filter_map(|&(_, assoc_id)| match assoc_id { + AssocItemId::TypeAliasId(it) => Some(it), + AssocItemId::FunctionId(_) | AssocItemId::ConstId(_) => None, + }) + .for_each(extend_with_taits); + }; + let extend_with_atpit_from_container = |container| match container { + ItemContainerId::ImplId(impl_id) => { + if db.impl_signature(impl_id).target_trait.is_some() { + extend_with_atpit_from_assoc_items(&impl_id.impl_items(db).items); + } + } + ItemContainerId::TraitId(trait_id) => { + extend_with_atpit_from_assoc_items(&trait_id.trait_items(db).items); + } + _ => {} + }; + match def_id { + DefWithBodyId::ConstId(id) => extend_with_atpit_from_container(id.loc(db).container), + DefWithBodyId::FunctionId(id) => extend_with_atpit_from_container(id.loc(db).container), + DefWithBodyId::StaticId(_) | DefWithBodyId::VariantId(_) => {} + } + + // FIXME: Collect opaques from `#[define_opaque]`. + + fn extend_with_opaques<'db>( + db: &'db dyn HirDatabase, + opaques: Option>>>, + mut make_impl_trait: impl FnMut(ImplTraitIdx<'db>) -> ImplTraitId<'db>, + 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()); + } + } + } +} + +// These are firewall queries to prevent drawing dependencies between infers: + +#[salsa::tracked(returns(ref), unsafe(non_update_return_type))] +pub(crate) fn rpit_hidden_types<'db>( + db: &'db dyn HirDatabase, + function: FunctionId, +) -> ArenaMap, EarlyBinder<'db, Ty<'db>>> { + let infer = db.infer(function.into()); + let mut result = ArenaMap::new(); + for (opaque, hidden_type) in infer.return_position_impl_trait_types(db) { + result.insert(opaque, EarlyBinder::bind(hidden_type)); + } + result.shrink_to_fit(); + result +} + +#[salsa::tracked(returns(ref), unsafe(non_update_return_type))] +pub(crate) fn tait_hidden_types<'db>( + db: &'db dyn HirDatabase, + type_alias: TypeAliasId, +) -> ArenaMap, EarlyBinder<'db, Ty<'db>>> { + let loc = type_alias.loc(db); + let module = loc.module(db); + let interner = DbInterner::new_with(db, Some(module.krate()), module.containing_block()); + let infcx = interner.infer_ctxt().build(TypingMode::non_body_analysis()); + let mut ocx = ObligationCtxt::new(&infcx); + let cause = ObligationCause::dummy(); + let param_env = db.trait_environment(type_alias.into()).env; + + let defining_bodies = tait_defining_bodies(db, &loc); + + let taits_count = db + .type_alias_impl_traits(type_alias) + .map_or(0, |taits| (*taits).as_ref().skip_binder().impl_traits.len()); + + let mut result = ArenaMap::with_capacity(taits_count); + for defining_body in defining_bodies { + let infer = db.infer(defining_body); + for (&opaque, &hidden_type) in &infer.type_of_opaque { + let ImplTraitId::TypeAliasImplTrait(opaque_owner, opaque_idx) = opaque.loc(db) else { + continue; + }; + if opaque_owner != type_alias { + continue; + } + // In the presence of errors, we attempt to create a unified type from all + // types. rustc doesn't do that, but this should improve the experience. + let hidden_type = infcx.insert_type_vars(hidden_type); + match result.entry(opaque_idx) { + la_arena::Entry::Vacant(entry) => { + entry.insert(EarlyBinder::bind(hidden_type)); + } + la_arena::Entry::Occupied(entry) => { + _ = ocx.eq(&cause, param_env, entry.get().instantiate_identity(), hidden_type); + } + } + } + } + + _ = ocx.try_evaluate_obligations(); + + // Fill missing entries. + for idx in 0..taits_count { + let idx = la_arena::Idx::from_raw(la_arena::RawIdx::from_u32(idx as u32)); + match result.entry(idx) { + la_arena::Entry::Vacant(entry) => { + entry.insert(EarlyBinder::bind(Ty::new_error(interner, ErrorGuaranteed))); + } + la_arena::Entry::Occupied(mut entry) => { + *entry.get_mut() = entry.get().map_bound(|hidden_type| { + infcx.resolve_vars_if_possible(hidden_type).replace_infer_with_error(interner) + }); + } + } + } + + result +} + +fn tait_defining_bodies( + db: &dyn HirDatabase, + loc: &AssocItemLoc, +) -> Vec { + let from_assoc_items = |assoc_items: &[(Name, AssocItemId)]| { + // Associated Type Position Impl Trait. + assoc_items + .iter() + .filter_map(|&(_, assoc_id)| match assoc_id { + AssocItemId::FunctionId(it) => Some(it.into()), + AssocItemId::ConstId(it) => Some(it.into()), + AssocItemId::TypeAliasId(_) => None, + }) + .collect() + }; + match loc.container { + ItemContainerId::ImplId(impl_id) => { + if db.impl_signature(impl_id).target_trait.is_some() { + return from_assoc_items(&impl_id.impl_items(db).items); + } + } + ItemContainerId::TraitId(trait_id) => { + return from_assoc_items(&trait_id.trait_items(db).items); + } + _ => {} + } + + // FIXME: Support general TAITs, or decisively decide not to. + Vec::new() +} 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 bc4701970c76c..14ec161c91c84 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 @@ -591,6 +591,7 @@ fn main() { "function_signature_shim", "function_signature_with_source_map_shim", "trait_environment_shim", + "return_type_impl_traits_shim", "expr_scopes_shim", "struct_signature_shim", "struct_signature_with_source_map_shim", @@ -686,6 +687,7 @@ fn main() { "return_type_impl_traits_shim", "infer_shim", "function_signature_with_source_map_shim", + "return_type_impl_traits_shim", "expr_scopes_shim", "struct_signature_with_source_map_shim", "generic_predicates_shim", diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/opaque_types.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/opaque_types.rs index 5cdd170198ba2..ca986336ff30e 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/opaque_types.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/opaque_types.rs @@ -31,7 +31,6 @@ fn test() { } #[test] -#[ignore = "FIXME(next-solver): This currently generates a type mismatch, need to switch opaque type handling to the solver"] fn associated_type_impl_traits_complex() { check_types( r#" @@ -116,6 +115,7 @@ fn foo() { ); } +#[ignore = "FIXME(next-solver): TAIT support was removed, need to rework it to work with `#[define_opaque]`"] #[test] fn type_alias_impl_trait_simple() { check_no_mismatches( @@ -135,9 +135,6 @@ static ALIAS: AliasTy = { "#, ); - // FIXME(next-solver): This should emit type mismatch error but leaving it for now - // as we should fully migrate into next-solver without chalk-ir and TAIT should be - // reworked on r-a to handle `#[define_opaque(T)]` check_infer_with_mismatches( r#" trait Trait {} 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 7c79393e65ac9..c71cd80d29895 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 @@ -725,7 +725,7 @@ fn issue_4885() { 138..146 'bar(key)': impl Future>::Bar> 142..145 'key': &'? K 162..165 'key': &'? K - 224..227 '{ }': () + 224..227 '{ }': impl Future>::Bar> "#]], ); } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression/new_solver.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression/new_solver.rs index 5983ec7647900..dfbdd25645f7a 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression/new_solver.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression/new_solver.rs @@ -180,7 +180,7 @@ impl<'a> IntoIterator for &'a Grid { "#, expect![[r#" 150..154 'self': &'a Grid - 174..181 '{ }': impl Iterator + 174..181 '{ }': <&'a Grid as IntoIterator>::IntoIter "#]], ); } 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 f72ca22fd2292..c0e439310e989 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 @@ -1211,7 +1211,7 @@ fn test(x: impl Trait, y: &impl Trait) { expect![[r#" 29..33 'self': &'? Self 54..58 'self': &'? Self - 98..100 '{}': () + 98..100 '{}': impl Trait 110..111 'x': impl Trait 130..131 'y': &'? impl Trait 151..268 '{ ...2(); }': () @@ -1373,11 +1373,11 @@ fn test() { expect![[r#" 49..53 'self': &'? mut Self 101..105 'self': &'? Self - 184..195 '{ loop {} }': ({unknown}, {unknown}) + 184..195 '{ loop {} }': (impl Iterator>, impl Trait) 186..193 'loop {}': ! 191..193 '{}': () 206..207 't': T - 268..279 '{ loop {} }': ({unknown}, {unknown}) + 268..279 '{ loop {} }': (impl Iterator>, impl Trait) 270..277 'loop {}': ! 275..277 '{}': () 291..413 '{ ...o(); }': () @@ -1419,7 +1419,7 @@ fn foo() -> (impl FnOnce(&str, T), impl Trait) { } "#, expect![[r#" - 134..165 '{ ...(C)) }': (impl FnOnce(&'? str, T), Bar) + 134..165 '{ ...(C)) }': (impl FnOnce(&'? str, T), impl Trait) 140..163 '(|inpu...ar(C))': (impl FnOnce(&'? str, T), Bar) 141..154 '|input, t| {}': impl FnOnce(&'? str, T) 142..147 'input': &'? str @@ -1441,7 +1441,7 @@ fn return_pos_impl_trait_in_projection() { trait Future { type Output; } impl Future for () { type Output = i32; } type Foo = (::Output, F); -fn foo() -> Foo> { +fn foo() -> Foo> { (0, ()) } "#, diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/traits.rs b/src/tools/rust-analyzer/crates/hir-ty/src/traits.rs index 7f6d4ff17f9fc..00c8eb7745800 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/traits.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/traits.rs @@ -107,24 +107,26 @@ pub fn next_trait_solve_canonical_in_ctxt<'db>( infer_ctxt: &InferCtxt<'db>, goal: Canonical<'db, Goal<'db, Predicate<'db>>>, ) -> NextTraitSolveResult { - let context = SolverContext(infer_ctxt.clone()); + infer_ctxt.probe(|_| { + let context = <&SolverContext<'db>>::from(infer_ctxt); - tracing::info!(?goal); + tracing::info!(?goal); - let (goal, var_values) = context.instantiate_canonical(&goal); - tracing::info!(?var_values); + let (goal, var_values) = context.instantiate_canonical(&goal); + tracing::info!(?var_values); - let res = context.evaluate_root_goal(goal, Span::dummy(), None); + let res = context.evaluate_root_goal(goal, Span::dummy(), None); - let res = res.map(|r| (r.has_changed, r.certainty)); + let res = res.map(|r| (r.has_changed, r.certainty)); - tracing::debug!("solve_nextsolver({:?}) => {:?}", goal, res); + tracing::debug!("solve_nextsolver({:?}) => {:?}", goal, res); - match res { - Err(_) => NextTraitSolveResult::NoSolution, - Ok((_, Certainty::Yes)) => NextTraitSolveResult::Certain, - Ok((_, Certainty::Maybe { .. })) => NextTraitSolveResult::Uncertain, - } + match res { + Err(_) => NextTraitSolveResult::NoSolution, + Ok((_, Certainty::Yes)) => NextTraitSolveResult::Certain, + Ok((_, Certainty::Maybe { .. })) => NextTraitSolveResult::Uncertain, + } + }) } /// Solve a trait goal using next trait solver. diff --git a/src/tools/rust-analyzer/crates/hir/src/lib.rs b/src/tools/rust-analyzer/crates/hir/src/lib.rs index 2bb2f80ecc05b..f2faf99fc9e8c 100644 --- a/src/tools/rust-analyzer/crates/hir/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir/src/lib.rs @@ -5136,10 +5136,7 @@ impl<'db> Type<'db> { AliasTy::new(interner, alias.id.into(), args), ); - // FIXME(next-solver): This needs to be `PostAnalysis`, but this currently causes errors due to our incorrect - // handling of opaques. `non_body_analysis()` will also cause errors (from not revealing opaques inside their - // defining places), so we choose between two bad options. - let infcx = interner.infer_ctxt().build(TypingMode::non_body_analysis()); + let infcx = interner.infer_ctxt().build(TypingMode::PostAnalysis); let ty = structurally_normalize_ty(&infcx, projection, self.env.clone()); if ty.is_ty_error() { None } else { Some(self.derived(ty)) } } @@ -5758,8 +5755,7 @@ impl<'db> Type<'db> { pub fn drop_glue(&self, db: &'db dyn HirDatabase) -> DropGlue { let interner = DbInterner::new_with(db, Some(self.env.krate), self.env.block); - // FIXME: This should be `PostAnalysis` I believe. - let infcx = interner.infer_ctxt().build(TypingMode::non_body_analysis()); + let infcx = interner.infer_ctxt().build(TypingMode::PostAnalysis); hir_ty::drop::has_drop_glue(&infcx, self.ty, self.env.clone()) } } 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 91fb4d0a67153..3a195314a7813 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs @@ -10809,7 +10809,7 @@ type Foo$0 = impl Sized; --- - needs Drop + no Drop "#]], ); check( diff --git a/src/tools/rust-analyzer/crates/ide/src/signature_help.rs b/src/tools/rust-analyzer/crates/ide/src/signature_help.rs index 5f7e12cf53f84..e927fd57ae961 100644 --- a/src/tools/rust-analyzer/crates/ide/src/signature_help.rs +++ b/src/tools/rust-analyzer/crates/ide/src/signature_help.rs @@ -1996,6 +1996,12 @@ fn f &T>(f: F) { #[test] fn regression_13579() { + // FIXME(next-solver): There should be signature help available here. + // The reason it is not is because of a trait solver bug. Since `Error` is not provided + // nor it can be inferred, it becomes an error type. The bug is that the solver ignores + // predicates on error types, and they do not guide infer vars, not allowing us to infer + // that `take`'s return type is callable. + // https://github.com/rust-lang/rust/pull/146602 should fix the solver bug. check( r#" fn f() { @@ -2008,9 +2014,7 @@ fn take( move || count } "#, - expect![[r#" - impl Fn() -> i32 - "#]], + expect![""], ); } From 023c4ef74fccdc2711f1a97514978ea214cf41ca Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Sat, 2 Aug 2025 23:46:56 +0300 Subject: [PATCH 03/26] When renaming `self` to other name, change callers method method call syntax to assoc fn syntax --- src/tools/rust-analyzer/crates/ide/src/lib.rs | 5 +- .../rust-analyzer/crates/ide/src/rename.rs | 350 +++++++++++++++++- .../crates/rust-analyzer/src/config.rs | 10 +- .../rust-analyzer/src/handlers/request.rs | 8 +- 4 files changed, 358 insertions(+), 15 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide/src/lib.rs b/src/tools/rust-analyzer/crates/ide/src/lib.rs index 857252832ffe1..ece5bac6df4b8 100644 --- a/src/tools/rust-analyzer/crates/ide/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide/src/lib.rs @@ -106,7 +106,7 @@ pub use crate::{ move_item::Direction, navigation_target::{NavigationTarget, TryToNav, UpmappingResult}, references::{FindAllRefsConfig, ReferenceSearchResult}, - rename::RenameError, + rename::{RenameConfig, RenameError}, runnables::{Runnable, RunnableKind, TestId, UpdateTest}, signature_help::SignatureHelp, static_index::{ @@ -830,8 +830,9 @@ impl Analysis { &self, position: FilePosition, new_name: &str, + config: &RenameConfig, ) -> Cancellable> { - self.with_db(|db| rename::rename(db, position, new_name)) + self.with_db(|db| rename::rename(db, position, new_name, config)) } pub fn prepare_rename( diff --git a/src/tools/rust-analyzer/crates/ide/src/rename.rs b/src/tools/rust-analyzer/crates/ide/src/rename.rs index 8922a8eb48580..ce5963919d9a9 100644 --- a/src/tools/rust-analyzer/crates/ide/src/rename.rs +++ b/src/tools/rust-analyzer/crates/ide/src/rename.rs @@ -4,7 +4,7 @@ //! tests. This module also implements a couple of magic tricks, like renaming //! `self` and to `self` (to switch between associated function and method). -use hir::{AsAssocItem, InFile, Name, Semantics, sym}; +use hir::{AsAssocItem, FindPathConfig, HasContainer, HirDisplay, InFile, Name, Semantics, sym}; use ide_db::{ FileId, FileRange, RootDatabase, defs::{Definition, NameClass, NameRefClass}, @@ -27,6 +27,23 @@ pub use ide_db::rename::RenameError; type RenameResult = Result; +pub struct RenameConfig { + pub prefer_no_std: bool, + pub prefer_prelude: bool, + pub prefer_absolute: bool, +} + +impl RenameConfig { + fn find_path_config(&self) -> FindPathConfig { + FindPathConfig { + prefer_no_std: self.prefer_no_std, + prefer_prelude: self.prefer_prelude, + prefer_absolute: self.prefer_absolute, + allow_unstable: true, + } + } +} + /// This is similar to `collect::, _>>`, but unlike it, it succeeds if there is *any* `Ok` item. fn ok_if_any(iter: impl Iterator>) -> Result, E> { let mut err = None; @@ -100,6 +117,7 @@ pub(crate) fn rename( db: &RootDatabase, position: FilePosition, new_name: &str, + config: &RenameConfig, ) -> RenameResult { let sema = Semantics::new(db); let file_id = sema @@ -158,7 +176,14 @@ pub(crate) fn rename( if let Definition::Local(local) = def { if let Some(self_param) = local.as_self_param(sema.db) { cov_mark::hit!(rename_self_to_param); - return rename_self_to_param(&sema, local, self_param, &new_name, kind); + return rename_self_to_param( + &sema, + local, + self_param, + &new_name, + kind, + config.find_path_config(), + ); } if kind == IdentifierKind::LowercaseSelf { cov_mark::hit!(rename_to_self); @@ -360,7 +385,7 @@ fn transform_assoc_fn_into_method_call( f: hir::Function, ) { let calls = Definition::Function(f).usages(sema).all(); - for (file_id, calls) in calls { + for (_file_id, calls) in calls { for call in calls { let Some(fn_name) = call.name.as_name_ref() else { continue }; let Some(path) = fn_name.syntax().parent().and_then(ast::PathSegment::cast) else { @@ -409,6 +434,12 @@ fn transform_assoc_fn_into_method_call( .unwrap_or_else(|| arg_list.syntax().text_range().end()), }; let replace_range = TextRange::new(replace_start, replace_end); + let macro_file = sema.hir_file_for(fn_name.syntax()); + let Some((replace_range, _)) = + InFile::new(macro_file, replace_range).original_node_file_range_opt(sema.db) + else { + continue; + }; let Some(macro_mapped_self) = sema.original_range_opt(self_arg.syntax()) else { continue; @@ -426,8 +457,8 @@ fn transform_assoc_fn_into_method_call( replacement.push('('); source_change.insert_source_edit( - file_id.file_id(sema.db), - TextEdit::replace(replace_range, replacement), + replace_range.file_id.file_id(sema.db), + TextEdit::replace(replace_range.range, replacement), ); } } @@ -514,12 +545,189 @@ fn rename_to_self( Ok(source_change) } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum CallReceiverAdjust { + Deref, + Ref, + RefMut, + None, +} + +fn method_to_assoc_fn_call_self_adjust( + sema: &Semantics<'_, RootDatabase>, + self_arg: &ast::Expr, +) -> CallReceiverAdjust { + let mut result = CallReceiverAdjust::None; + let self_adjust = sema.expr_adjustments(self_arg); + if let Some(self_adjust) = self_adjust { + let mut i = 0; + while i < self_adjust.len() { + if matches!(self_adjust[i].kind, hir::Adjust::Deref(..)) + && matches!( + self_adjust.get(i + 1), + Some(hir::Adjustment { kind: hir::Adjust::Borrow(..), .. }) + ) + { + // Deref then ref (reborrow), skip them. + i += 2; + continue; + } + + match self_adjust[i].kind { + hir::Adjust::Deref(_) if result == CallReceiverAdjust::None => { + // Autoref takes precedence over deref, because if given a `&Type` the compiler will deref + // it automatically. + result = CallReceiverAdjust::Deref; + } + hir::Adjust::Borrow(hir::AutoBorrow::Ref(mutability)) => { + match (result, mutability) { + (CallReceiverAdjust::RefMut, hir::Mutability::Shared) => {} + (_, hir::Mutability::Mut) => result = CallReceiverAdjust::RefMut, + (_, hir::Mutability::Shared) => result = CallReceiverAdjust::Ref, + } + } + _ => {} + } + + i += 1; + } + } + result +} + +fn transform_method_call_into_assoc_fn( + sema: &Semantics<'_, RootDatabase>, + source_change: &mut SourceChange, + f: hir::Function, + find_path_config: FindPathConfig, +) { + let calls = Definition::Function(f).usages(sema).all(); + for (_file_id, calls) in calls { + for call in calls { + let Some(fn_name) = call.name.as_name_ref() else { continue }; + let Some(method_call) = fn_name.syntax().parent().and_then(ast::MethodCallExpr::cast) + else { + continue; + }; + let Some(mut self_arg) = method_call.receiver() else { + continue; + }; + + let Some(scope) = sema.scope(fn_name.syntax()) else { + continue; + }; + let self_adjust = method_to_assoc_fn_call_self_adjust(sema, &self_arg); + + // Strip parentheses, function arguments have higher precedence than any operator. + while let ast::Expr::ParenExpr(it) = &self_arg { + self_arg = match it.expr() { + Some(it) => it, + None => break, + }; + } + + let needs_comma = method_call.arg_list().is_some_and(|it| it.args().next().is_some()); + + let self_needs_parens = self_adjust != CallReceiverAdjust::None + && self_arg.precedence().needs_parentheses_in(ExprPrecedence::Prefix); + + let replace_start = method_call.syntax().text_range().start(); + let replace_end = method_call + .arg_list() + .and_then(|it| it.l_paren_token()) + .map(|it| it.text_range().end()) + .unwrap_or_else(|| method_call.syntax().text_range().end()); + let replace_range = TextRange::new(replace_start, replace_end); + let macro_file = sema.hir_file_for(fn_name.syntax()); + let Some((replace_range, _)) = + InFile::new(macro_file, replace_range).original_node_file_range_opt(sema.db) + else { + continue; + }; + + let fn_container_path = match f.container(sema.db) { + hir::ItemContainer::Trait(trait_) => { + // FIXME: We always put it as `Trait::function`. Is it better to use `Type::function` (but + // that could conflict with an inherent method)? Or maybe `::function`? + // Or let the user decide? + let Some(path) = scope.module().find_path( + sema.db, + hir::ItemInNs::Types(trait_.into()), + find_path_config, + ) else { + continue; + }; + path.display(sema.db, replace_range.file_id.edition(sema.db)).to_string() + } + hir::ItemContainer::Impl(impl_) => { + let ty = impl_.self_ty(sema.db); + match ty.as_adt() { + Some(adt) => { + let Some(path) = scope.module().find_path( + sema.db, + hir::ItemInNs::Types(adt.into()), + find_path_config, + ) else { + continue; + }; + path.display(sema.db, replace_range.file_id.edition(sema.db)) + .to_string() + } + None => { + let Ok(mut ty) = + ty.display_source_code(sema.db, scope.module().into(), false) + else { + continue; + }; + ty.insert(0, '<'); + ty.push('>'); + ty + } + } + } + _ => continue, + }; + + let Some(macro_mapped_self) = sema.original_range_opt(self_arg.syntax()) else { + continue; + }; + let mut replacement = String::new(); + replacement.push_str(&fn_container_path); + replacement.push_str("::"); + format_to!(replacement, "{fn_name}"); + replacement.push('('); + replacement.push_str(match self_adjust { + CallReceiverAdjust::Deref => "*", + CallReceiverAdjust::Ref => "&", + CallReceiverAdjust::RefMut => "&mut ", + CallReceiverAdjust::None => "", + }); + if self_needs_parens { + replacement.push('('); + } + replacement.push_str(macro_mapped_self.text(sema.db)); + if self_needs_parens { + replacement.push(')'); + } + if needs_comma { + replacement.push_str(", "); + } + + source_change.insert_source_edit( + replace_range.file_id.file_id(sema.db), + TextEdit::replace(replace_range.range, replacement), + ); + } + } +} + fn rename_self_to_param( sema: &Semantics<'_, RootDatabase>, local: hir::Local, self_param: hir::SelfParam, new_name: &Name, identifier_kind: IdentifierKind, + find_path_config: FindPathConfig, ) -> RenameResult { if identifier_kind == IdentifierKind::LowercaseSelf { // Let's do nothing rather than complain. @@ -527,6 +735,11 @@ fn rename_self_to_param( return Ok(SourceChange::default()); } + let fn_def = match local.parent(sema.db) { + hir::DefWithBody::Function(func) => func, + _ => bail!("Cannot rename local to self outside of function"), + }; + let InFile { file_id, value: self_param } = sema.source(self_param).ok_or_else(|| format_err!("cannot find function source"))?; @@ -554,6 +767,7 @@ fn rename_self_to_param( ), ) })); + transform_method_call_into_assoc_fn(sema, &mut source_change, fn_def, find_path_config); Ok(source_change) } @@ -587,7 +801,10 @@ mod tests { use crate::fixture; - use super::{RangeInfo, RenameError}; + use super::{RangeInfo, RenameConfig, RenameError}; + + const TEST_CONFIG: RenameConfig = + RenameConfig { prefer_no_std: false, prefer_prelude: true, prefer_absolute: false }; #[track_caller] fn check( @@ -603,7 +820,7 @@ mod tests { panic!("Prepare rename to '{new_name}' was failed: {err}") } let rename_result = analysis - .rename(position, new_name) + .rename(position, new_name, &TEST_CONFIG) .unwrap_or_else(|err| panic!("Rename to '{new_name}' was cancelled: {err}")); match rename_result { Ok(source_change) => { @@ -635,7 +852,7 @@ mod tests { #[track_caller] fn check_conflicts(new_name: &str, #[rust_analyzer::rust_fixture] ra_fixture: &str) { let (analysis, position, conflicts) = fixture::annotations(ra_fixture); - let source_change = analysis.rename(position, new_name).unwrap().unwrap(); + let source_change = analysis.rename(position, new_name, &TEST_CONFIG).unwrap().unwrap(); let expected_conflicts = conflicts .into_iter() .map(|(file_range, _)| (file_range.file_id, file_range.range)) @@ -662,8 +879,10 @@ mod tests { expect: Expect, ) { let (analysis, position) = fixture::position(ra_fixture); - let source_change = - analysis.rename(position, new_name).unwrap().expect("Expect returned a RenameError"); + let source_change = analysis + .rename(position, new_name, &TEST_CONFIG) + .unwrap() + .expect("Expect returned a RenameError"); expect.assert_eq(&filter_expect(source_change)) } @@ -3585,6 +3804,117 @@ impl Foo { fn bar(v: Foo) { v.foo(123); +} + "#, + ); + } + + #[test] + fn rename_to_self_callers_in_macro() { + check( + "self", + r#" +struct Foo; + +impl Foo { + fn foo(th$0is: &Self, v: i32) {} +} + +macro_rules! m { ($it:expr) => { $it } } +fn bar(v: Foo) { + m!(Foo::foo(&v, 123)); +} + "#, + r#" +struct Foo; + +impl Foo { + fn foo(&self, v: i32) {} +} + +macro_rules! m { ($it:expr) => { $it } } +fn bar(v: Foo) { + m!(v.foo( 123)); +} + "#, + ); + } + + #[test] + fn rename_from_self_callers() { + check( + "this", + r#" +//- minicore: add +struct Foo; +impl Foo { + fn foo(&sel$0f) {} +} +impl core::ops::Add for Foo { + type Output = Foo; + + fn add(self, _rhs: Self) -> Self::Output { + Foo + } +} + +fn bar(v: &Foo) { + v.foo(); + (Foo + Foo).foo(); +} + +mod baz { + fn baz(v: super::Foo) { + v.foo(); + } +} + "#, + r#" +struct Foo; +impl Foo { + fn foo(this: &Self) {} +} +impl core::ops::Add for Foo { + type Output = Foo; + + fn add(self, _rhs: Self) -> Self::Output { + Foo + } +} + +fn bar(v: &Foo) { + Foo::foo(v); + Foo::foo(&(Foo + Foo)); +} + +mod baz { + fn baz(v: super::Foo) { + crate::Foo::foo(&v); + } +} + "#, + ); + // Multiple args: + check( + "this", + r#" +struct Foo; +impl Foo { + fn foo(&sel$0f, _v: i32) {} +} + +fn bar() { + Foo.foo(1); +} + "#, + r#" +struct Foo; +impl Foo { + fn foo(this: &Self, _v: i32) {} +} + +fn bar() { + Foo::foo(&Foo, 1); } "#, ); 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 652c2e32ffa68..6d2907ee56aac 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs @@ -12,7 +12,7 @@ use ide::{ CompletionFieldsToResolve, DiagnosticsConfig, GenericParameterHints, GotoDefinitionConfig, HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayFieldsToResolve, InlayHintsConfig, JoinLinesConfig, MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind, - Snippet, SnippetScope, SourceRootId, + RenameConfig, Snippet, SnippetScope, SourceRootId, }; use ide_db::{ MiniCore, SnippetCap, @@ -1705,6 +1705,14 @@ impl Config { } } + pub fn rename(&self, source_root: Option) -> RenameConfig { + RenameConfig { + prefer_no_std: self.imports_preferNoStd(source_root).to_owned(), + prefer_prelude: self.imports_preferPrelude(source_root).to_owned(), + prefer_absolute: self.imports_prefixExternPrelude(source_root).to_owned(), + } + } + pub fn call_hierarchy<'a>(&self, minicore: MiniCore<'a>) -> CallHierarchyConfig<'a> { CallHierarchyConfig { exclude_tests: self.references_excludeTests().to_owned(), minicore } } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs index 55d092f30f6b6..2976441d762ac 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs @@ -1325,8 +1325,12 @@ pub(crate) fn handle_rename( let _p = tracing::info_span!("handle_rename").entered(); let position = try_default!(from_proto::file_position(&snap, params.text_document_position)?); - let mut change = - snap.analysis.rename(position, ¶ms.new_name)?.map_err(to_proto::rename_error)?; + let source_root = snap.analysis.source_root_id(position.file_id).ok(); + let config = snap.config.rename(source_root); + let mut change = snap + .analysis + .rename(position, ¶ms.new_name, &config)? + .map_err(to_proto::rename_error)?; // this is kind of a hack to prevent double edits from happening when moving files // When a module gets renamed by renaming the mod declaration this causes the file to move From 74b2a94b5ebb1e07db8eac629ce4f6915a877052 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Mon, 27 Oct 2025 19:13:00 +0200 Subject: [PATCH 04/26] Consider all matches for flyimport even when searched with a qualifier --- .../ide-completion/src/tests/flyimport.rs | 22 ++++ .../ide-db/src/imports/import_assets.rs | 116 ++++++++++-------- 2 files changed, 86 insertions(+), 52 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs index 0cd42089b4875..e139a5e27088f 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs @@ -1953,3 +1953,25 @@ fn foo() { expect![""], ); } + +#[test] +fn multiple_matches_with_qualifier() { + check( + r#" +//- /foo.rs crate:foo +pub mod env { + pub fn var() {} + pub fn _var() {} +} + +//- /bar.rs crate:bar deps:foo +fn main() { + env::var$0 +} + "#, + expect![[r#" + fn _var() (use foo::env) fn() + fn var() (use foo::env) fn() + "#]], + ); +} 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 0c235c8d9a57a..50edfcaa78ee4 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 @@ -1,6 +1,6 @@ //! Look up accessible paths for items. -use std::ops::ControlFlow; +use std::{convert::Infallible, ops::ControlFlow}; use hir::{ AsAssocItem, AssocItem, AssocItemContainer, Complete, Crate, FindPathConfig, HasCrate, @@ -9,6 +9,7 @@ use hir::{ }; use itertools::Itertools; use rustc_hash::{FxHashMap, FxHashSet}; +use smallvec::SmallVec; use syntax::{ AstNode, SyntaxNode, ast::{self, HasName, make}, @@ -416,7 +417,7 @@ fn path_applicable_imports( NameToImport::Exact(first_qsegment.as_str().to_owned(), true), AssocSearchMode::Exclude, ) - .filter_map(|(item, do_not_complete)| { + .flat_map(|(item, do_not_complete)| { // we found imports for `first_qsegment`, now we need to filter these imports by whether // they result in resolving the rest of the path successfully validate_resolvable( @@ -446,10 +447,10 @@ fn validate_resolvable( resolved_qualifier: ItemInNs, unresolved_qualifier: &[Name], complete_in_flyimport: CompleteInFlyimport, -) -> Option { +) -> SmallVec<[LocatedImport; 1]> { let _p = tracing::info_span!("ImportAssets::import_for_item").entered(); - let qualifier = { + let qualifier = (|| { let mut adjusted_resolved_qualifier = resolved_qualifier; if !unresolved_qualifier.is_empty() { match resolved_qualifier { @@ -464,69 +465,80 @@ fn validate_resolvable( } match adjusted_resolved_qualifier { - ItemInNs::Types(def) => def, - _ => return None, + ItemInNs::Types(def) => Some(def), + _ => None, } - }; - let import_path_candidate = mod_path(resolved_qualifier)?; + })(); + let Some(qualifier) = qualifier else { return SmallVec::new() }; + let Some(import_path_candidate) = mod_path(resolved_qualifier) else { return SmallVec::new() }; + let mut result = SmallVec::new(); let ty = match qualifier { ModuleDef::Module(module) => { - return items_locator::items_with_name_in_module( + items_locator::items_with_name_in_module::( db, module, candidate.clone(), AssocSearchMode::Exclude, - |it| match scope_filter(it) { - true => ControlFlow::Break(it), - false => ControlFlow::Continue(()), + |item| { + if scope_filter(item) { + result.push(LocatedImport::new( + import_path_candidate.clone(), + resolved_qualifier, + item, + complete_in_flyimport, + )); + } + ControlFlow::Continue(()) }, - ) - .map(|item| { - LocatedImport::new( - import_path_candidate, - resolved_qualifier, - item, - complete_in_flyimport, - ) - }); + ); + return result; } // FIXME - ModuleDef::Trait(_) => return None, + ModuleDef::Trait(_) => return SmallVec::new(), ModuleDef::TypeAlias(alias) => alias.ty(db), ModuleDef::BuiltinType(builtin) => builtin.ty(db), ModuleDef::Adt(adt) => adt.ty(db), - _ => return None, + _ => return SmallVec::new(), }; - ty.iterate_path_candidates(db, scope, &FxHashSet::default(), None, None, |assoc| { - // FIXME: Support extra trait imports - if assoc.container_or_implemented_trait(db).is_some() { - return None; - } - let name = assoc.name(db)?; - let is_match = match candidate { - NameToImport::Prefix(text, true) => name.as_str().starts_with(text), - NameToImport::Prefix(text, false) => { - name.as_str().chars().zip(text.chars()).all(|(name_char, candidate_char)| { - name_char.eq_ignore_ascii_case(&candidate_char) - }) + ty.iterate_path_candidates::( + db, + scope, + &FxHashSet::default(), + None, + None, + |assoc| { + // FIXME: Support extra trait imports + if assoc.container_or_implemented_trait(db).is_some() { + return None; } - NameToImport::Exact(text, true) => name.as_str() == text, - NameToImport::Exact(text, false) => name.as_str().eq_ignore_ascii_case(text), - NameToImport::Fuzzy(text, true) => text.chars().all(|c| name.as_str().contains(c)), - NameToImport::Fuzzy(text, false) => text - .chars() - .all(|c| name.as_str().chars().any(|name_char| name_char.eq_ignore_ascii_case(&c))), - }; - if !is_match { - return None; - } - Some(LocatedImport::new( - import_path_candidate.clone(), - resolved_qualifier, - assoc_to_item(assoc), - complete_in_flyimport, - )) - }) + let name = assoc.name(db)?; + let is_match = match candidate { + NameToImport::Prefix(text, true) => name.as_str().starts_with(text), + NameToImport::Prefix(text, false) => { + name.as_str().chars().zip(text.chars()).all(|(name_char, candidate_char)| { + name_char.eq_ignore_ascii_case(&candidate_char) + }) + } + NameToImport::Exact(text, true) => name.as_str() == text, + NameToImport::Exact(text, false) => name.as_str().eq_ignore_ascii_case(text), + NameToImport::Fuzzy(text, true) => text.chars().all(|c| name.as_str().contains(c)), + NameToImport::Fuzzy(text, false) => text.chars().all(|c| { + name.as_str().chars().any(|name_char| name_char.eq_ignore_ascii_case(&c)) + }), + }; + if !is_match { + return None; + } + result.push(LocatedImport::new( + import_path_candidate.clone(), + resolved_qualifier, + assoc_to_item(assoc), + complete_in_flyimport, + )); + None + }, + ); + result } pub fn item_for_path_search(db: &RootDatabase, item: ItemInNs) -> Option { From 3009a73b8a839805d47f4a30af40a8ed9f5f3f16 Mon Sep 17 00:00:00 2001 From: Shoyu Vanilla Date: Tue, 28 Oct 2025 03:53:50 +0900 Subject: [PATCH 05/26] fix: Resolve `target-dir` more precisely --- .../project-model/src/build_dependencies.rs | 10 ++- .../project-model/src/cargo_workspace.rs | 34 ++++++--- .../crates/project-model/src/lib.rs | 2 +- .../crates/project-model/src/sysroot.rs | 7 +- .../crates/project-model/src/tests.rs | 8 +-- .../crates/project-model/src/workspace.rs | 72 ++++--------------- .../rust-analyzer/src/cli/rustc_tests.rs | 9 +-- .../crates/rust-analyzer/src/config.rs | 61 +++++++++------- .../crates/rust-analyzer/src/flycheck.rs | 46 ++++++++---- .../rust-analyzer/src/handlers/request.rs | 1 + .../crates/rust-analyzer/src/reload.rs | 13 +++- .../crates/rust-analyzer/src/test_runner.rs | 5 +- 12 files changed, 132 insertions(+), 136 deletions(-) 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 3a682d5a4d834..fedc6944f5f86 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 @@ -86,6 +86,7 @@ impl WorkspaceBuildScripts { config, &allowed_features, workspace.manifest_path(), + workspace.target_directory().as_ref(), current_dir, sysroot, toolchain, @@ -106,8 +107,9 @@ impl WorkspaceBuildScripts { let (_guard, cmd) = Self::build_command( config, &Default::default(), - // This is not gonna be used anyways, so just construct a dummy here + // These are not gonna be used anyways, so just construct a dummy here &ManifestPath::try_from(working_directory.clone()).unwrap(), + working_directory.as_ref(), working_directory, &Sysroot::empty(), None, @@ -430,6 +432,7 @@ impl WorkspaceBuildScripts { config: &CargoConfig, allowed_features: &FxHashSet, manifest_path: &ManifestPath, + target_dir: &Utf8Path, current_dir: &AbsPath, sysroot: &Sysroot, toolchain: Option<&semver::Version>, @@ -450,8 +453,9 @@ impl WorkspaceBuildScripts { cmd.arg("--manifest-path"); cmd.arg(manifest_path); - if let Some(target_dir) = &config.target_dir { - cmd.arg("--target-dir").arg(target_dir); + if let Some(target_dir) = config.target_dir_config.target_dir(Some(target_dir)) { + cmd.arg("--target-dir"); + cmd.arg(target_dir.as_ref()); } if let Some(target) = &config.target { 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 76ba01f3a2633..731104981ac7f 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 @@ -1,7 +1,6 @@ //! See [`CargoWorkspace`]. -use std::ops; -use std::str::from_utf8; +use std::{borrow::Cow, ops, str::from_utf8}; use anyhow::Context; use base_db::Env; @@ -95,6 +94,29 @@ impl Default for CargoFeatures { } } +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub enum TargetDirectoryConfig { + #[default] + None, + UseSubdirectory, + Directory(Utf8PathBuf), +} + +impl TargetDirectoryConfig { + pub fn target_dir<'a>( + &'a self, + ws_target_dir: Option<&'a Utf8Path>, + ) -> Option> { + match self { + TargetDirectoryConfig::None => None, + TargetDirectoryConfig::UseSubdirectory => { + Some(Cow::Owned(ws_target_dir?.join("rust-analyzer"))) + } + TargetDirectoryConfig::Directory(dir) => Some(Cow::Borrowed(dir)), + } + } +} + #[derive(Default, Clone, Debug, PartialEq, Eq)] pub struct CargoConfig { /// Whether to pass `--all-targets` to cargo invocations. @@ -121,7 +143,7 @@ pub struct CargoConfig { pub extra_env: FxHashMap>, pub invocation_strategy: InvocationStrategy, /// Optional path to use instead of `target` when building - pub target_dir: Option, + pub target_dir_config: TargetDirectoryConfig, /// Gate `#[test]` behind `#[cfg(test)]` pub set_test: bool, /// Load the project without any dependencies @@ -715,21 +737,15 @@ impl FetchMetadata { } } - pub(crate) fn no_deps_metadata(&self) -> Option<&cargo_metadata::Metadata> { - self.no_deps_result.as_ref().ok() - } - /// Executes the metadata-fetching command. /// /// A successful result may still contain a metadata error if the full fetch failed, /// but the fallback `--no-deps` pre-fetch succeeded during command construction. pub(crate) fn exec( self, - target_dir: &Utf8Path, locked: bool, progress: &dyn Fn(String), ) -> anyhow::Result<(cargo_metadata::Metadata, Option)> { - _ = target_dir; let Self { mut command, manifest_path: _, diff --git a/src/tools/rust-analyzer/crates/project-model/src/lib.rs b/src/tools/rust-analyzer/crates/project-model/src/lib.rs index e36b904881513..910bc0a96be56 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/lib.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/lib.rs @@ -62,7 +62,7 @@ pub use crate::{ build_dependencies::{ProcMacroDylibPath, WorkspaceBuildScripts}, cargo_workspace::{ CargoConfig, CargoFeatures, CargoMetadataConfig, CargoWorkspace, Package, PackageData, - PackageDependency, RustLibSource, Target, TargetData, TargetKind, + PackageDependency, RustLibSource, Target, TargetData, TargetDirectoryConfig, TargetKind, }, manifest_path::ManifestPath, project_json::{ProjectJson, ProjectJsonData}, diff --git a/src/tools/rust-analyzer/crates/project-model/src/sysroot.rs b/src/tools/rust-analyzer/crates/project-model/src/sysroot.rs index 5cc399bfe76d6..920afe65d7cd3 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/sysroot.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/sysroot.rs @@ -9,7 +9,7 @@ use std::{env, fs, ops::Not, path::Path, process::Command}; use anyhow::{Result, format_err}; use itertools::Itertools; -use paths::{AbsPath, AbsPathBuf, Utf8Path, Utf8PathBuf}; +use paths::{AbsPath, AbsPathBuf, Utf8PathBuf}; use rustc_hash::FxHashMap; use stdx::format_to; use toolchain::{Tool, probe_for_binary}; @@ -219,7 +219,6 @@ impl Sysroot { &self, sysroot_source_config: &RustSourceWorkspaceConfig, no_deps: bool, - target_dir: &Utf8Path, progress: &dyn Fn(String), ) -> Option { assert!(matches!(self.workspace, RustLibSrcWorkspace::Empty), "workspace already loaded"); @@ -233,7 +232,6 @@ impl Sysroot { match self.load_library_via_cargo( &library_manifest, src_root, - target_dir, cargo_config, no_deps, progress, @@ -328,7 +326,6 @@ impl Sysroot { &self, library_manifest: &ManifestPath, current_dir: &AbsPath, - target_dir: &Utf8Path, cargo_config: &CargoMetadataConfig, no_deps: bool, progress: &dyn Fn(String), @@ -345,7 +342,7 @@ impl Sysroot { let locked = true; let (mut res, err) = FetchMetadata::new(library_manifest, current_dir, &cargo_config, self, no_deps) - .exec(target_dir, locked, progress)?; + .exec(locked, progress)?; // Patch out `rustc-std-workspace-*` crates to point to the real crates. // This is done prior to `CrateGraph` construction to prevent de-duplication logic from failing. diff --git a/src/tools/rust-analyzer/crates/project-model/src/tests.rs b/src/tools/rust-analyzer/crates/project-model/src/tests.rs index 711cdd11b9a89..1908fc02904a8 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/tests.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/tests.rs @@ -238,12 +238,8 @@ fn smoke_test_real_sysroot_cargo() { ); let cwd = AbsPathBuf::assert_utf8(temp_dir().join("smoke_test_real_sysroot_cargo")); std::fs::create_dir_all(&cwd).unwrap(); - let loaded_sysroot = sysroot.load_workspace( - &RustSourceWorkspaceConfig::default_cargo(), - false, - &Utf8PathBuf::default(), - &|_| (), - ); + let loaded_sysroot = + sysroot.load_workspace(&RustSourceWorkspaceConfig::default_cargo(), false, &|_| ()); if let Some(loaded_sysroot) = loaded_sysroot { sysroot.set_workspace(loaded_sysroot); } diff --git a/src/tools/rust-analyzer/crates/project-model/src/workspace.rs b/src/tools/rust-analyzer/crates/project-model/src/workspace.rs index b88db419574db..3c696256854cd 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/workspace.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/workspace.rs @@ -16,7 +16,7 @@ use paths::{AbsPath, AbsPathBuf, Utf8PathBuf}; use rustc_hash::{FxHashMap, FxHashSet}; use semver::Version; use span::{Edition, FileId}; -use toolchain::{NO_RUSTUP_AUTO_INSTALL_ENV, Tool}; +use toolchain::Tool; use tracing::instrument; use tracing::{debug, error, info}; use triomphe::Arc; @@ -295,11 +295,6 @@ impl ProjectWorkspace { &sysroot, *no_deps, ); - let target_dir = config - .target_dir - .clone() - .or_else(|| fetch_metadata.no_deps_metadata().map(|m| m.target_directory.clone())) - .unwrap_or_else(|| workspace_dir.join("target").into()); // We spawn a bunch of processes to query various information about the workspace's // toolchain and sysroot @@ -345,7 +340,7 @@ impl ProjectWorkspace { }, &sysroot, *no_deps, - ).exec(&target_dir, true, progress) { + ).exec(true, progress) { Ok((meta, _error)) => { let workspace = CargoWorkspace::new( meta, @@ -374,7 +369,7 @@ impl ProjectWorkspace { }) }); - let cargo_metadata = s.spawn(|| fetch_metadata.exec(&target_dir, false, progress)); + let cargo_metadata = s.spawn(|| fetch_metadata.exec(false, progress)); let loaded_sysroot = s.spawn(|| { sysroot.load_workspace( &RustSourceWorkspaceConfig::CargoMetadata(sysroot_metadata_config( @@ -383,7 +378,6 @@ impl ProjectWorkspace { toolchain.clone(), )), config.no_deps, - &target_dir, progress, ) }); @@ -463,12 +457,6 @@ impl ProjectWorkspace { let targets = target_tuple::get(query_config, config.target.as_deref(), &config.extra_env) .unwrap_or_default(); let toolchain = version::get(query_config, &config.extra_env).ok().flatten(); - let project_root = project_json.project_root(); - let target_dir = config - .target_dir - .clone() - .or_else(|| cargo_target_dir(project_json.manifest()?, &config.extra_env, &sysroot)) - .unwrap_or_else(|| project_root.join("target").into()); // We spawn a bunch of processes to query various information about the workspace's // toolchain and sysroot @@ -486,7 +474,6 @@ impl ProjectWorkspace { sysroot.load_workspace( &RustSourceWorkspaceConfig::Json(*sysroot_project), config.no_deps, - &target_dir, progress, ) } else { @@ -497,7 +484,6 @@ impl ProjectWorkspace { toolchain.clone(), )), config.no_deps, - &target_dir, progress, ) } @@ -545,11 +531,6 @@ impl ProjectWorkspace { .unwrap_or_default(); let rustc_cfg = rustc_cfg::get(query_config, None, &config.extra_env); let target_data = target_data::get(query_config, None, &config.extra_env); - let target_dir = config - .target_dir - .clone() - .or_else(|| cargo_target_dir(detached_file, &config.extra_env, &sysroot)) - .unwrap_or_else(|| dir.join("target").into()); let loaded_sysroot = sysroot.load_workspace( &RustSourceWorkspaceConfig::CargoMetadata(sysroot_metadata_config( @@ -558,7 +539,6 @@ impl ProjectWorkspace { toolchain.clone(), )), config.no_deps, - &target_dir, &|_| (), ); if let Some(loaded_sysroot) = loaded_sysroot { @@ -579,21 +559,15 @@ impl ProjectWorkspace { &sysroot, config.no_deps, ); - let target_dir = config - .target_dir - .clone() - .or_else(|| fetch_metadata.no_deps_metadata().map(|m| m.target_directory.clone())) - .unwrap_or_else(|| dir.join("target").into()); - let cargo_script = - fetch_metadata.exec(&target_dir, false, &|_| ()).ok().map(|(ws, error)| { - let cargo_config_extra_env = - cargo_config_env(detached_file, &config_file, &config.extra_env); - ( - CargoWorkspace::new(ws, detached_file.clone(), cargo_config_extra_env, false), - WorkspaceBuildScripts::default(), - error.map(Arc::new), - ) - }); + let cargo_script = fetch_metadata.exec(false, &|_| ()).ok().map(|(ws, error)| { + let cargo_config_extra_env = + cargo_config_env(detached_file, &config_file, &config.extra_env); + ( + CargoWorkspace::new(ws, detached_file.clone(), cargo_config_extra_env, false), + WorkspaceBuildScripts::default(), + error.map(Arc::new), + ) + }); Ok(ProjectWorkspace { kind: ProjectWorkspaceKind::DetachedFile { @@ -1902,25 +1876,3 @@ fn sysroot_metadata_config( kind: "sysroot", } } - -fn cargo_target_dir( - manifest: &ManifestPath, - extra_env: &FxHashMap>, - sysroot: &Sysroot, -) -> Option { - let cargo = sysroot.tool(Tool::Cargo, manifest.parent(), extra_env); - let mut meta = cargo_metadata::MetadataCommand::new(); - meta.env(NO_RUSTUP_AUTO_INSTALL_ENV.0, NO_RUSTUP_AUTO_INSTALL_ENV.1); - meta.cargo_path(cargo.get_program()); - meta.manifest_path(manifest); - // `--no-deps` doesn't (over)write lockfiles as it doesn't do any package resolve. - // So we can use it to get `target_directory` before copying lockfiles - meta.no_deps(); - let mut other_options = vec![]; - if manifest.is_rust_manifest() { - meta.env("RUSTC_BOOTSTRAP", "1"); - other_options.push("-Zscript".to_owned()); - } - meta.other_options(other_options); - meta.exec().map(|m| m.target_directory).ok() -} diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs index 20567149bb4ba..eb28a47ec0ad7 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/rustc_tests.rs @@ -9,7 +9,6 @@ use hir::{ChangeWithProcMacros, Crate}; use ide::{AnalysisHost, DiagnosticCode, DiagnosticsConfig}; use ide_db::base_db; use itertools::Either; -use paths::Utf8PathBuf; use profile::StopWatch; use project_model::toolchain_info::{QueryConfig, target_data}; use project_model::{ @@ -75,12 +74,8 @@ impl Tester { }; let mut sysroot = Sysroot::discover(tmp_file.parent().unwrap(), &cargo_config.extra_env); - let loaded_sysroot = sysroot.load_workspace( - &RustSourceWorkspaceConfig::default_cargo(), - false, - &Utf8PathBuf::default(), - &|_| (), - ); + let loaded_sysroot = + sysroot.load_workspace(&RustSourceWorkspaceConfig::default_cargo(), false, &|_| ()); if let Some(loaded_sysroot) = loaded_sysroot { sysroot.set_workspace(loaded_sysroot); } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs index 6d2907ee56aac..10a392a5b7fc0 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs @@ -23,7 +23,7 @@ use itertools::{Either, Itertools}; use paths::{Utf8Path, Utf8PathBuf}; use project_model::{ CargoConfig, CargoFeatures, ProjectJson, ProjectJsonData, ProjectJsonFromCommand, - ProjectManifest, RustLibSource, + ProjectManifest, RustLibSource, TargetDirectoryConfig, }; use rustc_hash::{FxHashMap, FxHashSet}; use semver::Version; @@ -2285,7 +2285,7 @@ impl Config { run_build_script_command: self.cargo_buildScripts_overrideCommand(source_root).clone(), extra_args: self.cargo_extraArgs(source_root).clone(), extra_env: self.cargo_extraEnv(source_root).clone(), - target_dir: self.target_dir_from_config(source_root), + target_dir_config: self.target_dir_from_config(source_root), set_test: *self.cfg_setTest(source_root), no_deps: *self.cargo_noDeps(source_root), } @@ -2373,7 +2373,7 @@ impl Config { extra_args: self.extra_args(source_root).clone(), extra_test_bin_args: self.runnables_extraTestBinaryArgs(source_root).clone(), extra_env: self.extra_env(source_root).clone(), - target_dir: self.target_dir_from_config(source_root), + target_dir_config: self.target_dir_from_config(source_root), set_test: true, } } @@ -2431,7 +2431,7 @@ impl Config { extra_args: self.check_extra_args(source_root), extra_test_bin_args: self.runnables_extraTestBinaryArgs(source_root).clone(), extra_env: self.check_extra_env(source_root), - target_dir: self.target_dir_from_config(source_root), + target_dir_config: self.target_dir_from_config(source_root), set_test: *self.cfg_setTest(source_root), }, ansi_color_output: self.color_diagnostic_output(), @@ -2439,17 +2439,12 @@ impl Config { } } - fn target_dir_from_config(&self, source_root: Option) -> Option { - self.cargo_targetDir(source_root).as_ref().and_then(|target_dir| match target_dir { - TargetDirectory::UseSubdirectory(true) => { - let env_var = env::var("CARGO_TARGET_DIR").ok(); - let mut path = Utf8PathBuf::from(env_var.as_deref().unwrap_or("target")); - path.push("rust-analyzer"); - Some(path) - } - TargetDirectory::UseSubdirectory(false) => None, - TargetDirectory::Directory(dir) => Some(dir.clone()), - }) + fn target_dir_from_config(&self, source_root: Option) -> TargetDirectoryConfig { + match &self.cargo_targetDir(source_root) { + Some(TargetDirectory::UseSubdirectory(true)) => TargetDirectoryConfig::UseSubdirectory, + Some(TargetDirectory::UseSubdirectory(false)) | None => TargetDirectoryConfig::None, + Some(TargetDirectory::Directory(dir)) => TargetDirectoryConfig::Directory(dir.clone()), + } } pub fn check_on_save(&self, source_root: Option) -> bool { @@ -3966,7 +3961,7 @@ fn doc_comment_to_string(doc: &[&str]) -> String { #[cfg(test)] mod tests { - use std::fs; + use std::{borrow::Cow, fs}; use test_utils::{ensure_file_contents, project_root}; @@ -4101,9 +4096,13 @@ mod tests { (config, _, _) = config.apply_change(change); assert_eq!(config.cargo_targetDir(None), &None); - assert!( - matches!(config.flycheck(None), FlycheckConfig::CargoCommand { options, .. } if options.target_dir.is_none()) - ); + assert!(matches!( + config.flycheck(None), + FlycheckConfig::CargoCommand { + options: CargoOptions { target_dir_config: TargetDirectoryConfig::None, .. }, + .. + } + )); } #[test] @@ -4119,11 +4118,16 @@ mod tests { (config, _, _) = config.apply_change(change); assert_eq!(config.cargo_targetDir(None), &Some(TargetDirectory::UseSubdirectory(true))); - let target = + let ws_target_dir = Utf8PathBuf::from(std::env::var("CARGO_TARGET_DIR").unwrap_or("target".to_owned())); - assert!( - matches!(config.flycheck(None), FlycheckConfig::CargoCommand { options, .. } if options.target_dir == Some(target.join("rust-analyzer"))) - ); + assert!(matches!( + config.flycheck(None), + FlycheckConfig::CargoCommand { + options: CargoOptions { target_dir_config, .. }, + .. + } if target_dir_config.target_dir(Some(&ws_target_dir)).map(Cow::into_owned) + == Some(ws_target_dir.join("rust-analyzer")) + )); } #[test] @@ -4142,8 +4146,13 @@ mod tests { config.cargo_targetDir(None), &Some(TargetDirectory::Directory(Utf8PathBuf::from("other_folder"))) ); - assert!( - matches!(config.flycheck(None), FlycheckConfig::CargoCommand { options, .. } if options.target_dir == Some(Utf8PathBuf::from("other_folder"))) - ); + assert!(matches!( + config.flycheck(None), + FlycheckConfig::CargoCommand { + options: CargoOptions { target_dir_config, .. }, + .. + } if target_dir_config.target_dir(None).map(Cow::into_owned) + == Some(Utf8PathBuf::from("other_folder")) + )); } } 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 73a51bba3d9a9..db6743d4e5b64 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs @@ -13,6 +13,7 @@ use crossbeam_channel::{Receiver, Sender, select_biased, unbounded}; use ide_db::FxHashSet; use itertools::Itertools; use paths::{AbsPath, AbsPathBuf, Utf8Path, Utf8PathBuf}; +use project_model::TargetDirectoryConfig; use rustc_hash::FxHashMap; use serde::Deserialize as _; use serde_derive::Deserialize; @@ -46,7 +47,7 @@ pub(crate) struct CargoOptions { pub(crate) extra_args: Vec, pub(crate) extra_test_bin_args: Vec, pub(crate) extra_env: FxHashMap>, - pub(crate) target_dir: Option, + pub(crate) target_dir_config: TargetDirectoryConfig, } #[derive(Clone, Debug)] @@ -58,7 +59,7 @@ pub(crate) enum Target { } impl CargoOptions { - pub(crate) fn apply_on_command(&self, cmd: &mut Command) { + pub(crate) fn apply_on_command(&self, cmd: &mut Command, ws_target_dir: Option<&Utf8Path>) { for target in &self.target_tuples { cmd.args(["--target", target.as_str()]); } @@ -82,8 +83,8 @@ impl CargoOptions { cmd.arg(self.features.join(" ")); } } - if let Some(target_dir) = &self.target_dir { - cmd.arg("--target-dir").arg(target_dir); + if let Some(target_dir) = self.target_dir_config.target_dir(ws_target_dir) { + cmd.arg("--target-dir").arg(target_dir.as_ref()); } } } @@ -158,6 +159,7 @@ impl FlycheckHandle { sysroot_root: Option, workspace_root: AbsPathBuf, manifest_path: Option, + ws_target_dir: Option, ) -> FlycheckHandle { let actor = FlycheckActor::new( id, @@ -167,6 +169,7 @@ impl FlycheckHandle { sysroot_root, workspace_root, manifest_path, + ws_target_dir, ); let (sender, receiver) = unbounded::(); let thread = @@ -314,6 +317,7 @@ struct FlycheckActor { sender: Sender, config: FlycheckConfig, manifest_path: Option, + ws_target_dir: Option, /// Either the workspace root of the workspace we are flychecking, /// or the project root of the project. root: Arc, @@ -355,6 +359,7 @@ impl FlycheckActor { sysroot_root: Option, workspace_root: AbsPathBuf, manifest_path: Option, + ws_target_dir: Option, ) -> FlycheckActor { tracing::info!(%id, ?workspace_root, "Spawning flycheck"); FlycheckActor { @@ -366,6 +371,7 @@ impl FlycheckActor { root: Arc::new(workspace_root), scope: FlycheckScope::Workspace, manifest_path, + ws_target_dir, command_handle: None, command_receiver: None, diagnostics_cleared_for: Default::default(), @@ -428,15 +434,24 @@ impl FlycheckActor { CargoCheckParser, sender, match &self.config { - FlycheckConfig::CargoCommand { options, .. } => Some( - options - .target_dir - .as_deref() - .unwrap_or( - Utf8Path::new("target").join("rust-analyzer").as_path(), - ) - .join(format!("flycheck{}", self.id)), - ), + FlycheckConfig::CargoCommand { options, .. } => { + let ws_target_dir = + self.ws_target_dir.as_ref().map(Utf8PathBuf::as_path); + let target_dir = + options.target_dir_config.target_dir(ws_target_dir); + + // If `"rust-analyzer.cargo.targetDir": null`, we should use + // workspace's target dir instead of hard-coded fallback. + let target_dir = target_dir.as_deref().or(ws_target_dir); + + Some( + target_dir + .unwrap_or( + Utf8Path::new("target").join("rust-analyzer").as_path(), + ) + .join(format!("flycheck{}", self.id)), + ) + } _ => None, }, ) { @@ -672,7 +687,10 @@ impl FlycheckActor { cmd.arg("--keep-going"); - options.apply_on_command(&mut cmd); + options.apply_on_command( + &mut cmd, + self.ws_target_dir.as_ref().map(Utf8PathBuf::as_path), + ); cmd.args(&options.extra_args); Some(cmd) } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs index 2976441d762ac..14817df376679 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs @@ -264,6 +264,7 @@ pub(crate) fn handle_run_test( path, state.config.cargo_test_options(None), cargo.workspace_root(), + Some(cargo.target_directory().as_ref()), target, state.test_run_sender.clone(), )?; diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs index 1475f02447d23..bb971eb13bed6 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs @@ -23,6 +23,7 @@ use ide_db::{ use itertools::Itertools; use load_cargo::{ProjectFolders, load_proc_macro}; use lsp_types::FileSystemWatcher; +use paths::Utf8Path; use proc_macro_api::ProcMacroClient; use project_model::{ManifestPath, ProjectWorkspace, ProjectWorkspaceKind, WorkspaceBuildScripts}; use stdx::{format_to, thread::ThreadIntent}; @@ -876,6 +877,7 @@ impl GlobalState { None, self.config.root_path().clone(), None, + None, )] } crate::flycheck::InvocationStrategy::PerWorkspace => { @@ -890,13 +892,17 @@ impl GlobalState { | ProjectWorkspaceKind::DetachedFile { cargo: Some((cargo, _, _)), .. - } => (cargo.workspace_root(), Some(cargo.manifest_path())), + } => ( + cargo.workspace_root(), + Some(cargo.manifest_path()), + Some(cargo.target_directory()), + ), ProjectWorkspaceKind::Json(project) => { // Enable flychecks for json projects if a custom flycheck command was supplied // in the workspace configuration. match config { FlycheckConfig::CustomCommand { .. } => { - (project.path(), None) + (project.path(), None, None) } _ => return None, } @@ -906,7 +912,7 @@ impl GlobalState { ws.sysroot.root().map(ToOwned::to_owned), )) }) - .map(|(id, (root, manifest_path), sysroot_root)| { + .map(|(id, (root, manifest_path, target_dir), sysroot_root)| { FlycheckHandle::spawn( id, next_gen, @@ -915,6 +921,7 @@ impl GlobalState { sysroot_root, root.to_path_buf(), manifest_path.map(|it| it.to_path_buf()), + target_dir.map(|it| AsRef::::as_ref(it).to_path_buf()), ) }) .collect() 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 0c8658c75df0a..9a65e708a056e 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 @@ -2,7 +2,7 @@ //! thread and report the result of each test in a channel. use crossbeam_channel::Sender; -use paths::AbsPath; +use paths::{AbsPath, Utf8Path}; use project_model::TargetKind; use serde::Deserialize as _; use serde_derive::Deserialize; @@ -98,6 +98,7 @@ impl CargoTestHandle { path: Option<&str>, options: CargoOptions, root: &AbsPath, + ws_target_dir: Option<&Utf8Path>, test_target: TestTarget, sender: Sender, ) -> std::io::Result { @@ -123,7 +124,7 @@ impl CargoTestHandle { cmd.arg("--no-fail-fast"); cmd.arg("--manifest-path"); cmd.arg(root.join("Cargo.toml")); - options.apply_on_command(&mut cmd); + options.apply_on_command(&mut cmd, ws_target_dir); cmd.arg("--"); if let Some(path) = path { cmd.arg(path); From 506341bcdd269738195bc12753f7a17e22687973 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Tue, 28 Oct 2025 00:22:44 +0200 Subject: [PATCH 06/26] Avoid calling `specializes()` query on crates that do not define `#![feature(specialization)]` To save memory. --- .../crates/hir-ty/src/specialization.rs | 57 +++++++++++-------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/specialization.rs b/src/tools/rust-analyzer/crates/hir-ty/src/specialization.rs index 611947b96b713..f4ee4de446394 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/specialization.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/specialization.rs @@ -20,7 +20,7 @@ use crate::{ // and indeed I was unable to cause cycles even with erroneous code. However, in r-a we can // create a cycle if there is an error in the impl's where clauses. I believe well formed code // cannot create a cycle, but a cycle handler is required nevertheless. -fn specializes_cycle( +fn specializes_query_cycle( _db: &dyn HirDatabase, _specializing_impl_def_id: ImplId, _parent_impl_def_id: ImplId, @@ -39,31 +39,14 @@ fn specializes_cycle( /// `parent_impl_def_id` is a const impl (conditionally based off of some `[const]` /// bounds), then `specializing_impl_def_id` must also be const for the same /// set of types. -#[salsa::tracked(cycle_result = specializes_cycle)] -pub(crate) fn specializes( +#[salsa::tracked(cycle_result = specializes_query_cycle)] +fn specializes_query( db: &dyn HirDatabase, specializing_impl_def_id: ImplId, parent_impl_def_id: ImplId, ) -> bool { - let module = specializing_impl_def_id.loc(db).container; - - // We check that the specializing impl comes from a crate that has specialization enabled. - // - // We don't really care if the specialized impl (the parent) is in a crate that has - // specialization enabled, since it's not being specialized. - // - // rustc also checks whether the specializing impls comes from a macro marked - // `#[allow_internal_unstable(specialization)]`, but `#[allow_internal_unstable]` - // is an internal feature, std is not using it for specialization nor is likely to - // ever use it, and we don't have the span information necessary to replicate that. - let def_map = crate_def_map(db, module.krate()); - if !def_map.is_unstable_feature_enabled(&sym::specialization) - && !def_map.is_unstable_feature_enabled(&sym::min_specialization) - { - return false; - } - - let interner = DbInterner::new_with(db, Some(module.krate()), module.containing_block()); + let trait_env = db.trait_environment(specializing_impl_def_id.into()); + let interner = DbInterner::new_with(db, Some(trait_env.krate), trait_env.block); let specializing_impl_signature = db.impl_signature(specializing_impl_def_id); let parent_impl_signature = db.impl_signature(parent_impl_def_id); @@ -87,7 +70,7 @@ pub(crate) fn specializes( // create a parameter environment corresponding to an identity instantiation of the specializing impl, // i.e. the most generic instantiation of the specializing impl. - let param_env = db.trait_environment(specializing_impl_def_id.into()).env; + let param_env = trait_env.env; // Create an infcx, taking the predicates of the specializing impl as assumptions: let infcx = interner.infer_ctxt().build(TypingMode::non_body_analysis()); @@ -148,3 +131,31 @@ pub(crate) fn specializes( true } + +// This function is used to avoid creating the query for crates that does not define `#![feature(specialization)]`, +// as the solver is calling this a lot, and creating the query consumes a lot of memory. +pub(crate) fn specializes( + db: &dyn HirDatabase, + specializing_impl_def_id: ImplId, + parent_impl_def_id: ImplId, +) -> bool { + let module = specializing_impl_def_id.loc(db).container; + + // We check that the specializing impl comes from a crate that has specialization enabled. + // + // We don't really care if the specialized impl (the parent) is in a crate that has + // specialization enabled, since it's not being specialized. + // + // rustc also checks whether the specializing impls comes from a macro marked + // `#[allow_internal_unstable(specialization)]`, but `#[allow_internal_unstable]` + // is an internal feature, std is not using it for specialization nor is likely to + // ever use it, and we don't have the span information necessary to replicate that. + let def_map = crate_def_map(db, module.krate()); + if !def_map.is_unstable_feature_enabled(&sym::specialization) + && !def_map.is_unstable_feature_enabled(&sym::min_specialization) + { + return false; + } + + specializes_query(db, specializing_impl_def_id, parent_impl_def_id) +} From 0c108b1d8348c9ff3e28af2f41d41c957fcc1720 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Mon, 27 Oct 2025 23:46:04 +0100 Subject: [PATCH 07/26] perf: Reduce `client_commands` allocations in proto conversion --- .../rust-analyzer/src/handlers/request.rs | 56 +++++++++++++------ .../crates/rust-analyzer/src/lsp/to_proto.rs | 9 ++- 2 files changed, 45 insertions(+), 20 deletions(-) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs index 2976441d762ac..66a7a0b825f16 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs @@ -33,7 +33,9 @@ use triomphe::Arc; use vfs::{AbsPath, AbsPathBuf, FileId, VfsPath}; use crate::{ - config::{Config, RustfmtConfig, WorkspaceSymbolConfig}, + config::{ + ClientCommandsConfig, Config, HoverActionsConfig, RustfmtConfig, WorkspaceSymbolConfig, + }, diagnostics::convert_diagnostic, global_state::{FetchWorkspaceRequest, GlobalState, GlobalStateSnapshot}, line_index::LineEndings, @@ -1463,13 +1465,14 @@ pub(crate) fn handle_code_action( resolve, frange, )?; + let client_commands = snap.config.client_commands(); for (index, assist) in assists.into_iter().enumerate() { let resolve_data = if code_action_resolve_cap { Some((index, params.clone(), snap.file_version(file_id))) } else { None }; - let code_action = to_proto::code_action(&snap, assist, resolve_data)?; + let code_action = to_proto::code_action(&snap, &client_commands, assist, resolve_data)?; // Check if the client supports the necessary `ResourceOperation`s. let changes = code_action.edit.as_ref().and_then(|it| it.document_changes.as_ref()); @@ -1570,7 +1573,7 @@ pub(crate) fn handle_code_action_resolve( )) .into()); } - let ca = to_proto::code_action(&snap, assist.clone(), None)?; + let ca = to_proto::code_action(&snap, &snap.config.client_commands(), assist.clone(), None)?; code_action.edit = ca.edit; code_action.command = ca.command; @@ -2134,9 +2137,11 @@ fn to_command_link(command: lsp_types::Command, tooltip: String) -> lsp_ext::Com fn show_impl_command_link( snap: &GlobalStateSnapshot, position: &FilePosition, + implementations: bool, + show_references: bool, ) -> Option { - if snap.config.hover_actions().implementations - && snap.config.client_commands().show_reference + if implementations + && show_references && let Some(nav_data) = snap.analysis.goto_implementation(*position).unwrap_or(None) { let uri = to_proto::url(snap, position.file_id); @@ -2161,9 +2166,11 @@ fn show_impl_command_link( fn show_ref_command_link( snap: &GlobalStateSnapshot, position: &FilePosition, + references: bool, + show_reference: bool, ) -> Option { - if snap.config.hover_actions().references - && snap.config.client_commands().show_reference + if references + && show_reference && let Some(ref_search_res) = snap .analysis .find_all_refs( @@ -2198,8 +2205,9 @@ fn show_ref_command_link( fn runnable_action_links( snap: &GlobalStateSnapshot, runnable: Runnable, + hover_actions_config: &HoverActionsConfig, + client_commands_config: &ClientCommandsConfig, ) -> Option { - let hover_actions_config = snap.config.hover_actions(); if !hover_actions_config.runnable() { return None; } @@ -2209,7 +2217,6 @@ fn runnable_action_links( return None; } - let client_commands_config = snap.config.client_commands(); if !(client_commands_config.run_single || client_commands_config.debug_single) { return None; } @@ -2244,11 +2251,10 @@ fn runnable_action_links( fn goto_type_action_links( snap: &GlobalStateSnapshot, nav_targets: &[HoverGotoTypeData], + hover_actions: &HoverActionsConfig, + client_commands: &ClientCommandsConfig, ) -> Option { - if !snap.config.hover_actions().goto_type_def - || nav_targets.is_empty() - || !snap.config.client_commands().goto_location - { + if !hover_actions.goto_type_def || nav_targets.is_empty() || !client_commands.goto_location { return None; } @@ -2268,13 +2274,29 @@ fn prepare_hover_actions( snap: &GlobalStateSnapshot, actions: &[HoverAction], ) -> Vec { + let hover_actions = snap.config.hover_actions(); + let client_commands = snap.config.client_commands(); actions .iter() .filter_map(|it| match it { - HoverAction::Implementation(position) => show_impl_command_link(snap, position), - HoverAction::Reference(position) => show_ref_command_link(snap, position), - HoverAction::Runnable(r) => runnable_action_links(snap, r.clone()), - HoverAction::GoToType(targets) => goto_type_action_links(snap, targets), + HoverAction::Implementation(position) => show_impl_command_link( + snap, + position, + hover_actions.implementations, + client_commands.show_reference, + ), + HoverAction::Reference(position) => show_ref_command_link( + snap, + position, + hover_actions.references, + client_commands.show_reference, + ), + HoverAction::Runnable(r) => { + runnable_action_links(snap, r.clone(), &hover_actions, &client_commands) + } + HoverAction::GoToType(targets) => { + goto_type_action_links(snap, targets, &hover_actions, &client_commands) + } }) .collect() } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs index 024c13e1918d4..995e6c4cc0201 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs @@ -26,7 +26,7 @@ use serde_json::to_value; use vfs::AbsPath; use crate::{ - config::{CallInfoConfig, Config}, + config::{CallInfoConfig, ClientCommandsConfig, Config}, global_state::GlobalStateSnapshot, line_index::{LineEndings, LineIndex, PositionEncoding}, lsp::{ @@ -258,10 +258,12 @@ pub(crate) fn completion_items( let max_relevance = items.iter().map(|it| it.relevance.score()).max().unwrap_or_default(); let mut res = Vec::with_capacity(items.len()); + let client_commands = config.client_commands(); for item in items { completion_item( &mut res, config, + &client_commands, fields_to_resolve, line_index, version, @@ -283,6 +285,7 @@ pub(crate) fn completion_items( fn completion_item( acc: &mut Vec, config: &Config, + client_commands: &ClientCommandsConfig, fields_to_resolve: &CompletionFieldsToResolve, line_index: &LineIndex, version: Option, @@ -342,7 +345,7 @@ fn completion_item( } else { item.deprecated.then(|| vec![lsp_types::CompletionItemTag::DEPRECATED]) }; - let command = if item.trigger_call_info && config.client_commands().trigger_parameter_hints { + let command = if item.trigger_call_info && client_commands.trigger_parameter_hints { if fields_to_resolve.resolve_command { something_to_resolve |= true; None @@ -1500,6 +1503,7 @@ pub(crate) fn code_action_kind(kind: AssistKind) -> lsp_types::CodeActionKind { pub(crate) fn code_action( snap: &GlobalStateSnapshot, + commands: &ClientCommandsConfig, assist: Assist, resolve_data: Option<(usize, lsp_types::CodeActionParams, Option)>, ) -> Cancellable { @@ -1513,7 +1517,6 @@ pub(crate) fn code_action( command: None, }; - let commands = snap.config.client_commands(); res.command = match assist.command { Some(assists::Command::TriggerParameterHints) if commands.trigger_parameter_hints => { Some(command::trigger_parameter_hints()) From 540df3d1d999fc192473e9b6903956823bffc033 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Sun, 6 Jul 2025 16:10:11 +0300 Subject: [PATCH 08/26] Provide an option to not show derives near the ADT for "Goto Implementations" or "Implementations" codelens I don't do it by default, for three reasons: (1) it's more expensive, (2) I actually quite like seeing the derives, and they may expand to no impl/more than one impl, (3) if #19130 will ever be merged this will become even more useful. Even a config might be too much, but it was fun and easy to code so I did that. --- .../rust-analyzer/crates/hir/src/semantics.rs | 16 ++++++ .../crates/ide/src/annotations.rs | 11 +++- .../crates/ide/src/goto_implementation.rs | 53 +++++++++++++++++-- src/tools/rust-analyzer/crates/ide/src/lib.rs | 4 +- .../rust-analyzer/src/cli/analysis_stats.rs | 1 + .../crates/rust-analyzer/src/config.rs | 20 +++++-- .../rust-analyzer/src/handlers/request.rs | 14 +++-- .../docs/book/src/configuration_generated.md | 7 +++ .../rust-analyzer/editors/code/package.json | 10 ++++ 9 files changed, 122 insertions(+), 14 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs index 62ce3daab75df..ec43442c9b74f 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs @@ -2105,6 +2105,22 @@ impl<'db> SemanticsImpl<'db> { parent = parent_; } } + + pub fn impl_generated_from_derive(&self, impl_: Impl) -> Option { + let source = hir_def::src::HasSource::ast_ptr(&impl_.id.loc(self.db), self.db); + let mut file_id = source.file_id; + let adt_ast_id = loop { + let macro_call = file_id.macro_file()?; + match macro_call.loc(self.db).kind { + hir_expand::MacroCallKind::Derive { ast_id, .. } => break ast_id, + hir_expand::MacroCallKind::FnLike { ast_id, .. } => file_id = ast_id.file_id, + hir_expand::MacroCallKind::Attr { ast_id, .. } => file_id = ast_id.file_id, + } + }; + let adt_source = adt_ast_id.to_in_file_node(self.db); + self.cache(adt_source.value.syntax().ancestors().last().unwrap(), adt_source.file_id); + ToDef::to_def(self, adt_source.as_ref()) + } } // FIXME This can't be the best way to do this diff --git a/src/tools/rust-analyzer/crates/ide/src/annotations.rs b/src/tools/rust-analyzer/crates/ide/src/annotations.rs index 36c44044bb5da..6fb8dedea47cf 100644 --- a/src/tools/rust-analyzer/crates/ide/src/annotations.rs +++ b/src/tools/rust-analyzer/crates/ide/src/annotations.rs @@ -9,7 +9,7 @@ use syntax::{AstNode, TextRange, ast::HasName}; use crate::{ NavigationTarget, RunnableKind, annotations::fn_references::find_all_methods, - goto_implementation::goto_implementation, + goto_implementation::{GotoImplementationConfig, goto_implementation}, navigation_target, references::{FindAllRefsConfig, find_all_refs}, runnables::{Runnable, runnables}, @@ -44,6 +44,7 @@ pub struct AnnotationConfig<'a> { pub annotate_method_references: bool, pub annotate_enum_variant_references: bool, pub location: AnnotationLocation, + pub filter_adjacent_derive_implementations: bool, pub minicore: MiniCore<'a>, } @@ -204,7 +205,12 @@ pub(crate) fn resolve_annotation( ) -> Annotation { match annotation.kind { AnnotationKind::HasImpls { pos, ref mut data } => { - *data = goto_implementation(db, pos).map(|range| range.info); + let goto_implementation_config = GotoImplementationConfig { + filter_adjacent_derive_implementations: config + .filter_adjacent_derive_implementations, + }; + *data = + goto_implementation(db, &goto_implementation_config, pos).map(|range| range.info); } AnnotationKind::HasReferences { pos, ref mut data } => { *data = find_all_refs( @@ -253,6 +259,7 @@ mod tests { annotate_enum_variant_references: true, location: AnnotationLocation::AboveName, minicore: MiniCore::default(), + filter_adjacent_derive_implementations: false, }; fn check_with_config( diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_implementation.rs b/src/tools/rust-analyzer/crates/ide/src/goto_implementation.rs index 875403c4e32a4..0572bca445840 100644 --- a/src/tools/rust-analyzer/crates/ide/src/goto_implementation.rs +++ b/src/tools/rust-analyzer/crates/ide/src/goto_implementation.rs @@ -8,6 +8,10 @@ use syntax::{AstNode, SyntaxKind::*, T, ast}; use crate::{FilePosition, NavigationTarget, RangeInfo, TryToNav}; +pub struct GotoImplementationConfig { + pub filter_adjacent_derive_implementations: bool, +} + // Feature: Go to Implementation // // Navigates to the impl items of types. @@ -19,6 +23,7 @@ use crate::{FilePosition, NavigationTarget, RangeInfo, TryToNav}; // ![Go to Implementation](https://user-images.githubusercontent.com/48062697/113065566-02f85480-91b1-11eb-9288-aaad8abd8841.gif) pub(crate) fn goto_implementation( db: &RootDatabase, + config: &GotoImplementationConfig, FilePosition { file_id, offset }: FilePosition, ) -> Option>> { let sema = Semantics::new(db); @@ -55,7 +60,19 @@ pub(crate) fn goto_implementation( .and_then(|def| { let navs = match def { Definition::Trait(trait_) => impls_for_trait(&sema, trait_), - Definition::Adt(adt) => impls_for_ty(&sema, adt.ty(sema.db)), + Definition::Adt(adt) => { + let mut impls = Impl::all_for_type(db, adt.ty(sema.db)); + if config.filter_adjacent_derive_implementations { + impls.retain(|impl_| { + sema.impl_generated_from_derive(*impl_) != Some(adt) + }); + } + impls + .into_iter() + .filter_map(|imp| imp.try_to_nav(&sema)) + .flatten() + .collect() + } Definition::TypeAlias(alias) => impls_for_ty(&sema, alias.ty(sema.db)), Definition::BuiltinType(builtin) => { impls_for_ty(&sema, builtin.ty(sema.db)) @@ -125,12 +142,24 @@ mod tests { use ide_db::FileRange; use itertools::Itertools; - use crate::fixture; + use crate::{GotoImplementationConfig, fixture}; + const TEST_CONFIG: &GotoImplementationConfig = + &GotoImplementationConfig { filter_adjacent_derive_implementations: false }; + + #[track_caller] fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str) { + check_with_config(TEST_CONFIG, ra_fixture); + } + + #[track_caller] + fn check_with_config( + config: &GotoImplementationConfig, + #[rust_analyzer::rust_fixture] ra_fixture: &str, + ) { let (analysis, position, expected) = fixture::annotations(ra_fixture); - let navs = analysis.goto_implementation(position).unwrap().unwrap().info; + let navs = analysis.goto_implementation(config, position).unwrap().unwrap().info; let cmp = |frange: &FileRange| (frange.file_id, frange.range.start()); @@ -416,4 +445,22 @@ fn test() { "#, ); } + + #[test] + fn filter_adjacent_derives() { + check_with_config( + &GotoImplementationConfig { filter_adjacent_derive_implementations: true }, + r#" +//- minicore: clone, copy, derive + +#[derive(Clone, Copy)] +struct Foo$0; + +trait Bar {} + +impl Bar for Foo {} + // ^^^ + "#, + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide/src/lib.rs b/src/tools/rust-analyzer/crates/ide/src/lib.rs index ece5bac6df4b8..260945757348a 100644 --- a/src/tools/rust-analyzer/crates/ide/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide/src/lib.rs @@ -86,6 +86,7 @@ pub use crate::{ file_structure::{FileStructureConfig, StructureNode, StructureNodeKind}, folding_ranges::{Fold, FoldKind}, goto_definition::GotoDefinitionConfig, + goto_implementation::GotoImplementationConfig, highlight_related::{HighlightRelatedConfig, HighlightedRange}, hover::{ HoverAction, HoverConfig, HoverDocFormat, HoverGotoTypeData, HoverResult, @@ -537,9 +538,10 @@ impl Analysis { /// Returns the impls from the symbol at `position`. pub fn goto_implementation( &self, + config: &GotoImplementationConfig, position: FilePosition, ) -> Cancellable>>> { - self.with_db(|db| goto_implementation::goto_implementation(db, position)) + self.with_db(|db| goto_implementation::goto_implementation(db, config, position)) } /// Returns the type definitions for the symbol at `position`. 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 de24bc09ff0fa..5e4a277f38af4 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 @@ -1214,6 +1214,7 @@ impl flags::AnalysisStats { annotate_method_references: false, annotate_enum_variant_references: false, location: ide::AnnotationLocation::AboveName, + filter_adjacent_derive_implementations: false, minicore: MiniCore::default(), }; for &file_id in file_ids { 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 6d2907ee56aac..185df4dd73f5c 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs @@ -10,9 +10,9 @@ use hir::Symbol; use ide::{ AnnotationConfig, AssistConfig, CallHierarchyConfig, CallableSnippets, CompletionConfig, CompletionFieldsToResolve, DiagnosticsConfig, GenericParameterHints, GotoDefinitionConfig, - HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayFieldsToResolve, - InlayHintsConfig, JoinLinesConfig, MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind, - RenameConfig, Snippet, SnippetScope, SourceRootId, + GotoImplementationConfig, HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat, + InlayFieldsToResolve, InlayHintsConfig, JoinLinesConfig, MemoryLayoutHoverConfig, + MemoryLayoutHoverRenderKind, RenameConfig, Snippet, SnippetScope, SourceRootId, }; use ide_db::{ MiniCore, SnippetCap, @@ -98,6 +98,9 @@ config_data! { /// Code's `files.watcherExclude`. files_exclude | files_excludeDirs: Vec = vec![], + /// If this is `true`, when "Goto Implementations" and in "Implementations" lens, are triggered on a `struct` or `enum` or `union`, we filter out trait implementations that originate from `derive`s above the type. + gotoImplementations_filterAdjacentDerives: bool = false, + /// Highlight related return values while the cursor is on any `match`, `if`, or match arm /// arrow (`=>`). highlightRelated_branchExitPoints_enable: bool = true, @@ -1413,6 +1416,7 @@ pub struct LensConfig { // annotations pub location: AnnotationLocation, + pub filter_adjacent_derive_implementations: bool, } #[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -1469,6 +1473,7 @@ impl LensConfig { annotate_enum_variant_references: self.enum_variant_refs, location: self.location.into(), minicore, + filter_adjacent_derive_implementations: self.filter_adjacent_derive_implementations, } } } @@ -2503,6 +2508,15 @@ impl Config { refs_trait: *self.lens_enable() && *self.lens_references_trait_enable(), enum_variant_refs: *self.lens_enable() && *self.lens_references_enumVariant_enable(), location: *self.lens_location(), + filter_adjacent_derive_implementations: *self + .gotoImplementations_filterAdjacentDerives(), + } + } + + pub fn goto_implementation(&self) -> GotoImplementationConfig { + GotoImplementationConfig { + filter_adjacent_derive_implementations: *self + .gotoImplementations_filterAdjacentDerives(), } } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs index 66a7a0b825f16..ab463533d70fe 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs @@ -849,10 +849,11 @@ pub(crate) fn handle_goto_implementation( let _p = tracing::info_span!("handle_goto_implementation").entered(); let position = try_default!(from_proto::file_position(&snap, params.text_document_position_params)?); - let nav_info = match snap.analysis.goto_implementation(position)? { - None => return Ok(None), - Some(it) => it, - }; + let nav_info = + match snap.analysis.goto_implementation(&snap.config.goto_implementation(), position)? { + None => return Ok(None), + Some(it) => it, + }; let src = FileRange { file_id: position.file_id, range: nav_info.range }; let res = to_proto::goto_definition_response(&snap, Some(src), nav_info.info)?; Ok(Some(res)) @@ -2142,7 +2143,10 @@ fn show_impl_command_link( ) -> Option { if implementations && show_references - && let Some(nav_data) = snap.analysis.goto_implementation(*position).unwrap_or(None) + && let Some(nav_data) = snap + .analysis + .goto_implementation(&snap.config.goto_implementation(), *position) + .unwrap_or(None) { let uri = to_proto::url(snap, position.file_id); let line_index = snap.file_line_index(position.file_id).ok()?; 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 d768993f501fc..7ec7c379c64ff 100644 --- a/src/tools/rust-analyzer/docs/book/src/configuration_generated.md +++ b/src/tools/rust-analyzer/docs/book/src/configuration_generated.md @@ -635,6 +635,13 @@ Default: `"client"` Controls file watching implementation. +## rust-analyzer.gotoImplementations.filterAdjacentDerives {#gotoImplementations.filterAdjacentDerives} + +Default: `false` + +If this is `true`, when "Goto Implementations" and in "Implementations" lens, are triggered on a `struct` or `enum` or `union`, we filter out trait implementations that originate from `derive`s above the type. + + ## rust-analyzer.highlightRelated.branchExitPoints.enable {#highlightRelated.branchExitPoints.enable} Default: `true` diff --git a/src/tools/rust-analyzer/editors/code/package.json b/src/tools/rust-analyzer/editors/code/package.json index d659421a0299b..0269494da96d0 100644 --- a/src/tools/rust-analyzer/editors/code/package.json +++ b/src/tools/rust-analyzer/editors/code/package.json @@ -1627,6 +1627,16 @@ } } }, + { + "title": "Goto Implementations", + "properties": { + "rust-analyzer.gotoImplementations.filterAdjacentDerives": { + "markdownDescription": "If this is `true`, when \"Goto Implementations\" and in \"Implementations\" lens, are triggered on a `struct` or `enum` or `union`, we filter out trait implementations that originate from `derive`s above the type.", + "default": false, + "type": "boolean" + } + } + }, { "title": "Highlight Related", "properties": { From ae2818dc574d271a4896257e3f19e30a3c8402b4 Mon Sep 17 00:00:00 2001 From: "Shoyu Vanilla (Flint)" Date: Tue, 28 Oct 2025 15:49:38 +0900 Subject: [PATCH 09/26] fix: Canonicalize flycheck output path --- .../crates/rust-analyzer/src/flycheck.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs index db6743d4e5b64..68337ddf1c9d1 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs @@ -445,11 +445,19 @@ impl FlycheckActor { let target_dir = target_dir.as_deref().or(ws_target_dir); Some( - target_dir - .unwrap_or( + // As `CommandHandle::spawn`'s working directory is + // rust-analyzer's working directory, which might be different + // from the flycheck's working directory, we should canonicalize + // the output directory, otherwise we might write it into the + // wrong target dir. + // If `target_dir` is an absolute path, it will replace + // `self.root` and that's an intended behavior. + self.root + .join(target_dir.unwrap_or( Utf8Path::new("target").join("rust-analyzer").as_path(), - ) - .join(format!("flycheck{}", self.id)), + )) + .join(format!("flycheck{}", self.id)) + .into(), ) } _ => None, From 13834a011e0e14196a64097e6308f75f6bf6c6c3 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Tue, 28 Oct 2025 10:59:33 +0200 Subject: [PATCH 10/26] Fix handling of blocks modules that are not the root module --- .../crates/hir-def/src/item_tree/lower.rs | 28 ++++++++----------- .../crates/hir-def/src/resolver.rs | 23 ++++++++------- .../crates/hir-def/src/visibility.rs | 13 +++++---- .../crates/hir-ty/src/display.rs | 5 ++-- .../crates/hir-ty/src/tests/regression.rs | 16 +++++++++++ .../crates/hir/src/source_analyzer.rs | 14 ++++------ .../rust-analyzer/crates/ide-db/src/search.rs | 14 ++++++---- .../test_data/highlight_block_mod_items.html | 2 +- 8 files changed, 64 insertions(+), 51 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/lower.rs index 454e06399583c..db50e6585d848 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/lower.rs @@ -370,18 +370,13 @@ impl<'a> Ctx<'a> { }); match &vis { RawVisibility::Public => RawVisibilityId::PUB, - RawVisibility::Module(path, explicitness) if path.segments().is_empty() => { - match (path.kind, explicitness) { - (PathKind::SELF, VisibilityExplicitness::Explicit) => { - RawVisibilityId::PRIV_EXPLICIT - } - (PathKind::SELF, VisibilityExplicitness::Implicit) => { - RawVisibilityId::PRIV_IMPLICIT - } - (PathKind::Crate, _) => RawVisibilityId::PUB_CRATE, - _ => RawVisibilityId(self.visibilities.insert_full(vis).0 as u32), - } + RawVisibility::PubSelf(VisibilityExplicitness::Explicit) => { + RawVisibilityId::PRIV_EXPLICIT } + RawVisibility::PubSelf(VisibilityExplicitness::Implicit) => { + RawVisibilityId::PRIV_IMPLICIT + } + RawVisibility::PubCrate => RawVisibilityId::PUB_CRATE, _ => RawVisibilityId(self.visibilities.insert_full(vis).0 as u32), } } @@ -466,10 +461,7 @@ pub(crate) fn lower_use_tree( } fn private_vis() -> RawVisibility { - RawVisibility::Module( - Interned::new(ModPath::from_kind(PathKind::SELF)), - VisibilityExplicitness::Implicit, - ) + RawVisibility::PubSelf(VisibilityExplicitness::Implicit) } pub(crate) fn visibility_from_ast( @@ -486,9 +478,11 @@ pub(crate) fn visibility_from_ast( Some(path) => path, } } - ast::VisibilityKind::PubCrate => ModPath::from_kind(PathKind::Crate), + ast::VisibilityKind::PubCrate => return RawVisibility::PubCrate, ast::VisibilityKind::PubSuper => ModPath::from_kind(PathKind::Super(1)), - ast::VisibilityKind::PubSelf => ModPath::from_kind(PathKind::SELF), + ast::VisibilityKind::PubSelf => { + return RawVisibility::PubSelf(VisibilityExplicitness::Explicit); + } ast::VisibilityKind::Pub => return RawVisibility::Public, }; RawVisibility::Module(Interned::new(path), VisibilityExplicitness::Explicit) 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 698292c2fbea4..abcf0a397cdf9 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs @@ -1075,7 +1075,9 @@ fn resolver_for_scope_<'db>( if let Some(block) = scopes.block(scope) { let def_map = block_def_map(db, block); let local_def_map = block.lookup(db).module.only_local_def_map(db); - r = r.push_block_scope(def_map, local_def_map); + // Using `DefMap::ROOT` is okay here since inside modules other than the root, + // there can't directly be expressions. + r = r.push_block_scope(def_map, local_def_map, DefMap::ROOT); // FIXME: This adds as many module scopes as there are blocks, but resolving in each // already traverses all parents, so this is O(n²). I think we could only store the // innermost module scope instead? @@ -1108,12 +1110,9 @@ impl<'db> Resolver<'db> { self, def_map: &'db DefMap, local_def_map: &'db LocalDefMap, + module_id: LocalModuleId, ) -> Resolver<'db> { - self.push_scope(Scope::BlockScope(ModuleItemMap { - def_map, - local_def_map, - module_id: DefMap::ROOT, - })) + self.push_scope(Scope::BlockScope(ModuleItemMap { def_map, local_def_map, module_id })) } fn push_expr_scope( @@ -1273,7 +1272,7 @@ impl HasResolver for ModuleId { let (mut def_map, local_def_map) = self.local_def_map(db); let mut module_id = self.local_id; - if !self.is_block_module() { + if !self.is_within_block() { return Resolver { scopes: vec![], module_scope: ModuleItemMap { def_map, local_def_map, module_id }, @@ -1283,9 +1282,9 @@ impl HasResolver for ModuleId { let mut modules: SmallVec<[_; 1]> = smallvec![]; while let Some(parent) = def_map.parent() { let block_def_map = mem::replace(&mut def_map, parent.def_map(db)); - modules.push(block_def_map); - if !parent.is_block_module() { - module_id = parent.local_id; + let block_module_id = mem::replace(&mut module_id, parent.local_id); + modules.push((block_def_map, block_module_id)); + if !parent.is_within_block() { break; } } @@ -1293,8 +1292,8 @@ impl HasResolver for ModuleId { scopes: Vec::with_capacity(modules.len()), module_scope: ModuleItemMap { def_map, local_def_map, module_id }, }; - for def_map in modules.into_iter().rev() { - resolver = resolver.push_block_scope(def_map, local_def_map); + for (def_map, module_id) in modules.into_iter().rev() { + resolver = resolver.push_block_scope(def_map, local_def_map, module_id); } resolver } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/visibility.rs b/src/tools/rust-analyzer/crates/hir-def/src/visibility.rs index b5eb84c25f2b6..948f6ed8c32bd 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/visibility.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/visibility.rs @@ -289,18 +289,21 @@ pub(crate) fn field_visibilities_query( pub fn visibility_from_ast( db: &dyn DefDatabase, - has_resolver: impl HasResolver, + has_resolver: impl HasResolver + HasModule, ast_vis: InFile>, ) -> Visibility { let mut span_map = None; let raw_vis = crate::item_tree::visibility_from_ast(db, ast_vis.value, &mut |range| { span_map.get_or_insert_with(|| db.span_map(ast_vis.file_id)).span_for_range(range).ctx }); - if raw_vis == RawVisibility::Public { - return Visibility::Public; + match raw_vis { + RawVisibility::PubSelf(explicitness) => { + Visibility::Module(has_resolver.module(db), explicitness) + } + RawVisibility::PubCrate => Visibility::PubCrate(has_resolver.krate(db)), + RawVisibility::Public => Visibility::Public, + RawVisibility::Module(..) => Visibility::resolve(db, &has_resolver.resolver(db), &raw_vis), } - - Visibility::resolve(db, &has_resolver.resolver(db), &raw_vis) } /// Resolve visibility of a type alias. diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/display.rs b/src/tools/rust-analyzer/crates/hir-ty/src/display.rs index dd1b212d4c294..0a3796687f038 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/display.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/display.rs @@ -2078,9 +2078,10 @@ pub fn write_visibility<'db>( if vis_id == module_id { // pub(self) or omitted Ok(()) - } else if root_module_id == vis_id { + } else if root_module_id == vis_id && !root_module_id.is_within_block() { write!(f, "pub(crate) ") - } else if module_id.containing_module(f.db) == Some(vis_id) { + } else if module_id.containing_module(f.db) == Some(vis_id) && !vis_id.is_block_module() + { write!(f, "pub(super) ") } else { write!(f, "pub(in ...) ") 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 7c79393e65ac9..118ea88f7b6a0 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 @@ -2506,3 +2506,19 @@ fn main() { "#, ); } + +#[test] +fn foo() { + check_types( + r#" +fn foo() { + mod my_mod { + pub type Bool = bool; + } + + let _: my_mod::Bool; + // ^ bool +} + "#, + ); +} 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 15eab14b88dfd..f994ed26cab65 100644 --- a/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs +++ b/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs @@ -1594,14 +1594,12 @@ fn resolve_hir_path_( Some(unresolved) => resolver .generic_def() .and_then(|def| { - hir_ty::attach_db(db, || { - hir_ty::associated_type_shorthand_candidates( - db, - def, - res.in_type_ns()?, - |name, _| name == unresolved.name, - ) - }) + hir_ty::associated_type_shorthand_candidates( + db, + def, + res.in_type_ns()?, + |name, _| name == unresolved.name, + ) }) .map(TypeAlias::from) .map(Into::into) 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 f1d076e874d5c..018c84189775b 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/search.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/search.rs @@ -387,12 +387,14 @@ impl Definition { return SearchScope::reverse_dependencies(db, module.krate()); } - let vis = self.visibility(db); - if let Some(Visibility::Public) = vis { - return SearchScope::reverse_dependencies(db, module.krate()); - } - if let Some(Visibility::Module(module, _)) = vis { - return SearchScope::module_and_children(db, module.into()); + if let Some(vis) = self.visibility(db) { + return match vis { + Visibility::Module(module, _) => { + SearchScope::module_and_children(db, module.into()) + } + Visibility::PubCrate(krate) => SearchScope::krate(db, krate.into()), + Visibility::Public => SearchScope::reverse_dependencies(db, module.krate()), + }; } let range = match module_source { diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_block_mod_items.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_block_mod_items.html index 3beda396da80c..711f5344ae7d7 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_block_mod_items.html +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_block_mod_items.html @@ -53,7 +53,7 @@ foo!(Bar); fn func(_: y::Bar) { mod inner { - struct Innerest<const C: usize> { field: [(); {C}] } + struct Innerest<const C: usize> { field: [(); {C}] } } } } From 4d338cbb2f01d9641f330211b3cbc7e130494da3 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Tue, 28 Oct 2025 10:27:28 +0200 Subject: [PATCH 11/26] Support memory profiling with dhat Unfortunately, this requires a custom build of r-a, and it's quite slow. --- src/tools/rust-analyzer/Cargo.lock | 31 ++++++++++++++++- .../crates/rust-analyzer/Cargo.toml | 2 ++ .../crates/rust-analyzer/src/config.rs | 11 ++++++ .../rust-analyzer/src/handlers/request.rs | 34 ++++++++++++++----- .../crates/rust-analyzer/src/lib.rs | 7 ++++ .../crates/rust-analyzer/src/main_loop.rs | 8 +++++ .../docs/book/src/configuration_generated.md | 10 ++++++ .../rust-analyzer/editors/code/package.json | 13 +++++++ .../editors/code/src/commands.ts | 27 ++------------- .../editors/code/src/snippets.ts | 4 ++- src/tools/rust-analyzer/xtask/src/dist.rs | 21 ++++++++++-- src/tools/rust-analyzer/xtask/src/flags.rs | 19 ++++++++++- 12 files changed, 148 insertions(+), 39 deletions(-) diff --git a/src/tools/rust-analyzer/Cargo.lock b/src/tools/rust-analyzer/Cargo.lock index 535833d9e2a95..b4754df16d096 100644 --- a/src/tools/rust-analyzer/Cargo.lock +++ b/src/tools/rust-analyzer/Cargo.lock @@ -418,6 +418,22 @@ dependencies = [ "syn", ] +[[package]] +name = "dhat" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cd11d84628e233de0ce467de10b8633f4ddaecafadefc86e13b84b8739b827" +dependencies = [ + "backtrace", + "lazy_static", + "mintex", + "parking_lot", + "rustc-hash 1.1.0", + "serde", + "serde_json", + "thousands", +] + [[package]] name = "dirs" version = "6.0.0" @@ -1383,6 +1399,12 @@ dependencies = [ "adler2", ] +[[package]] +name = "mintex" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c505b3e17ed6b70a7ed2e67fbb2c560ee327353556120d6e72f5232b6880d536" + [[package]] name = "mio" version = "1.1.0" @@ -1452,7 +1474,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -2011,6 +2033,7 @@ dependencies = [ "cargo_metadata 0.21.0", "cfg", "crossbeam-channel", + "dhat", "dirs", "dissimilar", "expect-test", @@ -2528,6 +2551,12 @@ dependencies = [ "syn", ] +[[package]] +name = "thousands" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" + [[package]] name = "thread_local" version = "1.1.9" diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/Cargo.toml b/src/tools/rust-analyzer/crates/rust-analyzer/Cargo.toml index c746f848b6a06..5fdab458bb8e7 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/Cargo.toml +++ b/src/tools/rust-analyzer/crates/rust-analyzer/Cargo.toml @@ -53,6 +53,7 @@ semver.workspace = true memchr = "2.7.5" cargo_metadata.workspace = true process-wrap.workspace = true +dhat = { version = "0.3.3", optional = true } cfg.workspace = true hir-def.workspace = true @@ -105,6 +106,7 @@ in-rust-tree = [ "hir-ty/in-rust-tree", "load-cargo/in-rust-tree", ] +dhat = ["dep:dhat"] [lints] workspace = true diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs index 6d2907ee56aac..75eacdbab355b 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs @@ -378,6 +378,12 @@ config_data! { /// Internal config, path to proc-macro server executable. procMacro_server: Option = None, + /// The path where to save memory profiling output. + /// + /// **Note:** Memory profiling is not enabled by default in rust-analyzer builds, you need to build + /// from source for it. + profiling_memoryProfile: Option = None, + /// Exclude imports from find-all-references. references_excludeImports: bool = false, @@ -2165,6 +2171,11 @@ impl Config { Some(AbsPathBuf::try_from(path).unwrap_or_else(|path| self.root_path.join(path))) } + pub fn dhat_output_file(&self) -> Option { + let path = self.profiling_memoryProfile().clone()?; + Some(AbsPathBuf::try_from(path).unwrap_or_else(|path| self.root_path.join(path))) + } + pub fn ignored_proc_macros( &self, source_root: Option, diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs index 66a7a0b825f16..528ec70cb6e94 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs @@ -126,17 +126,35 @@ pub(crate) fn handle_analyzer_status( Ok(buf) } -pub(crate) fn handle_memory_usage(state: &mut GlobalState, _: ()) -> anyhow::Result { +pub(crate) fn handle_memory_usage(_state: &mut GlobalState, _: ()) -> anyhow::Result { let _p = tracing::info_span!("handle_memory_usage").entered(); - let mem = state.analysis_host.per_query_memory_usage(); - let mut out = String::new(); - for (name, bytes, entries) in mem { - format_to!(out, "{:>8} {:>6} {}\n", bytes, entries, name); + #[cfg(not(feature = "dhat"))] + { + Err(anyhow::anyhow!( + "Memory profiling is not enabled for this build of rust-analyzer.\n\n\ + To build rust-analyzer with profiling support, pass `--features dhat --profile dev-rel` to `cargo build` + when building from source, or pass `--enable-profiling` to `cargo xtask`." + )) + } + #[cfg(feature = "dhat")] + { + if let Some(dhat_output_file) = _state.config.dhat_output_file() { + let mutprofiler = crate::DHAT_PROFILER.lock().unwrap(); + let old_profiler = profiler.take(); + // Need to drop the old profiler before creating a new one. + drop(old_profiler); + *profiler = Some(dhat::Profiler::builder().file_name(&dhat_output_file).build()); + Ok(format!( + "Memory profile was saved successfully to {dhat_output_file}.\n\n\ + See https://docs.rs/dhat/latest/dhat/#viewing for how to inspect the profile." + )) + } else { + Err(anyhow::anyhow!( + "Please set `rust-analyzer.profiling.memoryProfile` to the path where you want to save the profile." + )) + } } - format_to!(out, "{:>8} Remaining\n", profile::memory_usage().allocated); - - Ok(out) } pub(crate) fn handle_view_syntax_tree( diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs index 44af8fbddf300..6ae527abb1ff1 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs @@ -82,3 +82,10 @@ macro_rules! try_default_ { }; } pub(crate) use try_default_ as try_default; + +#[cfg(feature = "dhat")] +#[global_allocator] +static ALLOC: dhat::Alloc = dhat::Alloc; + +#[cfg(feature = "dhat")] +static DHAT_PROFILER: std::sync::Mutex> = std::sync::Mutex::new(None); 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 c0947b2a291ec..f57e0fe15313f 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 @@ -60,6 +60,14 @@ pub fn main_loop(config: Config, connection: Connection) -> anyhow::Result<()> { SetThreadPriority(thread, thread_priority_above_normal); } + #[cfg(feature = "dhat")] + { + if let Some(dhat_output_file) = config.dhat_output_file() { + *crate::DHAT_PROFILER.lock().unwrap() = + Some(dhat::Profiler::builder().file_name(&dhat_output_file).build()); + } + } + GlobalState::new(connection.sender, config).run(connection.receiver) } 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 d768993f501fc..4f456555a2e63 100644 --- a/src/tools/rust-analyzer/docs/book/src/configuration_generated.md +++ b/src/tools/rust-analyzer/docs/book/src/configuration_generated.md @@ -1289,6 +1289,16 @@ Default: `null` Internal config, path to proc-macro server executable. +## rust-analyzer.profiling.memoryProfile {#profiling.memoryProfile} + +Default: `null` + +The path where to save memory profiling output. + +**Note:** Memory profiling is not enabled by default in rust-analyzer builds, you need to build +from source for it. + + ## rust-analyzer.references.excludeImports {#references.excludeImports} Default: `false` diff --git a/src/tools/rust-analyzer/editors/code/package.json b/src/tools/rust-analyzer/editors/code/package.json index d659421a0299b..099397eafa97a 100644 --- a/src/tools/rust-analyzer/editors/code/package.json +++ b/src/tools/rust-analyzer/editors/code/package.json @@ -2749,6 +2749,19 @@ } } }, + { + "title": "Profiling", + "properties": { + "rust-analyzer.profiling.memoryProfile": { + "markdownDescription": "The path where to save memory profiling output.\n\n**Note:** Memory profiling is not enabled by default in rust-analyzer builds, you need to build\nfrom source for it.", + "default": null, + "type": [ + "null", + "string" + ] + } + } + }, { "title": "References", "properties": { diff --git a/src/tools/rust-analyzer/editors/code/src/commands.ts b/src/tools/rust-analyzer/editors/code/src/commands.ts index 25b30013fa1c1..16fc586d5df00 100644 --- a/src/tools/rust-analyzer/editors/code/src/commands.ts +++ b/src/tools/rust-analyzer/editors/code/src/commands.ts @@ -71,32 +71,9 @@ export function analyzerStatus(ctx: CtxInit): Cmd { } export function memoryUsage(ctx: CtxInit): Cmd { - const tdcp = new (class implements vscode.TextDocumentContentProvider { - readonly uri = vscode.Uri.parse("rust-analyzer-memory://memory"); - readonly eventEmitter = new vscode.EventEmitter(); - - provideTextDocumentContent(_uri: vscode.Uri): vscode.ProviderResult { - if (!vscode.window.activeTextEditor) return ""; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return ctx.client.sendRequest(ra.memoryUsage).then((mem: any) => { - return "Per-query memory usage:\n" + mem + "\n(note: database has been cleared)"; - }); - } - - get onDidChange(): vscode.Event { - return this.eventEmitter.event; - } - })(); - - ctx.pushExtCleanup( - vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-memory", tdcp), - ); - return async () => { - tdcp.eventEmitter.fire(tdcp.uri); - const document = await vscode.workspace.openTextDocument(tdcp.uri); - return vscode.window.showTextDocument(document, vscode.ViewColumn.Two, true); + const response = await ctx.client.sendRequest(ra.memoryUsage); + vscode.window.showInformationMessage(response); }; } diff --git a/src/tools/rust-analyzer/editors/code/src/snippets.ts b/src/tools/rust-analyzer/editors/code/src/snippets.ts index e3f43a80670a9..a469a9cd1f45c 100644 --- a/src/tools/rust-analyzer/editors/code/src/snippets.ts +++ b/src/tools/rust-analyzer/editors/code/src/snippets.ts @@ -24,7 +24,9 @@ export async function applySnippetWorkspaceEdit( for (const indel of edits) { assert( !(indel instanceof vscode.SnippetTextEdit), - `bad ws edit: snippet received with multiple edits: ${JSON.stringify(edit)}`, + `bad ws edit: snippet received with multiple edits: ${JSON.stringify( + edit, + )}`, ); builder.replace(indel.range, indel.newText); } diff --git a/src/tools/rust-analyzer/xtask/src/dist.rs b/src/tools/rust-analyzer/xtask/src/dist.rs index dbfecdbe1121f..1b1fb532cae96 100644 --- a/src/tools/rust-analyzer/xtask/src/dist.rs +++ b/src/tools/rust-analyzer/xtask/src/dist.rs @@ -45,11 +45,22 @@ impl flags::Dist { allocator, self.zig, self.pgo, + // Profiling requires debug information. + self.enable_profiling, )?; let release_tag = if stable { date_iso(sh)? } else { "nightly".to_owned() }; dist_client(sh, &version, &release_tag, &target)?; } else { - dist_server(sh, "0.0.0-standalone", &target, allocator, self.zig, self.pgo)?; + dist_server( + sh, + "0.0.0-standalone", + &target, + allocator, + self.zig, + self.pgo, + // Profiling requires debug information. + self.enable_profiling, + )?; } Ok(()) } @@ -92,9 +103,11 @@ fn dist_server( allocator: Malloc, zig: bool, pgo: Option, + dev_rel: bool, ) -> anyhow::Result<()> { let _e = sh.push_env("CFG_RELEASE", release); let _e = sh.push_env("CARGO_PROFILE_RELEASE_LTO", "thin"); + let _e = sh.push_env("CARGO_PROFILE_DEV_REL_LTO", "thin"); // Uncomment to enable debug info for releases. Note that: // * debug info is split on windows and macs, so it does nothing for those platforms, @@ -120,7 +133,7 @@ fn dist_server( None }; - let mut cmd = build_command(sh, command, &target_name, features); + let mut cmd = build_command(sh, command, &target_name, features, dev_rel); if let Some(profile) = pgo_profile { cmd = cmd.env("RUSTFLAGS", format!("-Cprofile-use={}", profile.to_str().unwrap())); } @@ -141,10 +154,12 @@ fn build_command<'a>( command: &str, target_name: &str, features: &[&str], + dev_rel: bool, ) -> Cmd<'a> { + let profile = if dev_rel { "dev-rel" } else { "release" }; cmd!( sh, - "cargo {command} --manifest-path ./crates/rust-analyzer/Cargo.toml --bin rust-analyzer --target {target_name} {features...} --release" + "cargo {command} --manifest-path ./crates/rust-analyzer/Cargo.toml --bin rust-analyzer --target {target_name} {features...} --profile {profile}" ) } diff --git a/src/tools/rust-analyzer/xtask/src/flags.rs b/src/tools/rust-analyzer/xtask/src/flags.rs index 8f70a1861893a..e72d8f22e4f0a 100644 --- a/src/tools/rust-analyzer/xtask/src/flags.rs +++ b/src/tools/rust-analyzer/xtask/src/flags.rs @@ -42,6 +42,10 @@ xflags::xflags! { optional --mimalloc /// Use jemalloc allocator for server. optional --jemalloc + // Enable memory profiling support. + // + // **Warning:** This will produce a slower build of rust-analyzer, use only for profiling. + optional --enable-profiling /// Install the proc-macro server. optional --proc-macro-server @@ -67,6 +71,10 @@ xflags::xflags! { optional --mimalloc /// Use jemalloc allocator for server optional --jemalloc + // Enable memory profiling support. + // + // **Warning:** This will produce a slower build of rust-analyzer, use only for profiling. + optional --enable-profiling optional --client-patch-version version: String /// Use cargo-zigbuild optional --zig @@ -125,6 +133,7 @@ pub struct Install { pub server: bool, pub mimalloc: bool, pub jemalloc: bool, + pub enable_profiling: bool, pub proc_macro_server: bool, pub dev_rel: bool, pub force_always_assert: bool, @@ -143,6 +152,7 @@ pub struct Release { pub struct Dist { pub mimalloc: bool, pub jemalloc: bool, + pub enable_profiling: bool, pub client_patch_version: Option, pub zig: bool, pub pgo: Option, @@ -280,6 +290,7 @@ pub(crate) enum Malloc { System, Mimalloc, Jemalloc, + Dhat, } impl Malloc { @@ -288,6 +299,7 @@ impl Malloc { Malloc::System => &[][..], Malloc::Mimalloc => &["--features", "mimalloc"], Malloc::Jemalloc => &["--features", "jemalloc"], + Malloc::Dhat => &["--features", "dhat"], } } } @@ -301,12 +313,15 @@ impl Install { Malloc::Mimalloc } else if self.jemalloc { Malloc::Jemalloc + } else if self.enable_profiling { + Malloc::Dhat } else { Malloc::System }; Some(ServerOpt { malloc, - dev_rel: self.dev_rel, + // Profiling requires debug information. + dev_rel: self.dev_rel || self.enable_profiling, pgo: self.pgo.clone(), force_always_assert: self.force_always_assert, }) @@ -331,6 +346,8 @@ impl Dist { Malloc::Mimalloc } else if self.jemalloc { Malloc::Jemalloc + } else if self.enable_profiling { + Malloc::Dhat } else { Malloc::System } From 0ceb66838b5084cebd290565fa08b112cdad9aca Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 27 Oct 2025 18:27:20 +0200 Subject: [PATCH 12/26] Show proper async function signatures in the signature help Co-authored-by: Lukas Wirth --- .../crates/ide/src/signature_help.rs | 157 ++++++++++++++++-- 1 file changed, 144 insertions(+), 13 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide/src/signature_help.rs b/src/tools/rust-analyzer/crates/ide/src/signature_help.rs index e927fd57ae961..f9ec44813a6bc 100644 --- a/src/tools/rust-analyzer/crates/ide/src/signature_help.rs +++ b/src/tools/rust-analyzer/crates/ide/src/signature_help.rs @@ -175,6 +175,9 @@ fn signature_help_for_call( match callable.kind() { hir::CallableKind::Function(func) => { res.doc = func.docs(db); + if func.is_async(db) { + format_to!(res.signature, "async "); + } format_to!(res.signature, "fn {}", func.name(db).display(db, edition)); let generic_params = GenericDef::Function(func) @@ -283,13 +286,16 @@ fn signature_help_for_call( } }; match callable.kind() { - hir::CallableKind::Function(func) if callable.return_type().contains_unknown() => { - render(func.ret_type(db)) + hir::CallableKind::Function(func) => render(func.async_ret_type(db).unwrap_or_else(|| { + if callable.return_type().contains_unknown() { + func.ret_type(db) + } else { + callable.return_type() + } + })), + hir::CallableKind::Closure(_) | hir::CallableKind::FnPtr | hir::CallableKind::FnImpl(_) => { + render(callable.return_type()) } - hir::CallableKind::Function(_) - | hir::CallableKind::Closure(_) - | hir::CallableKind::FnPtr - | hir::CallableKind::FnImpl(_) => render(callable.return_type()), hir::CallableKind::TupleStruct(_) | hir::CallableKind::TupleEnumVariant(_) => {} } Some(res) @@ -751,13 +757,7 @@ mod tests { #[track_caller] fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) { - let fixture = format!( - r#" -//- minicore: sized, fn -{ra_fixture} - "# - ); - let (db, position) = position(&fixture); + let (db, position) = position(ra_fixture); let sig_help = hir::attach_db(&db, || crate::signature_help::signature_help(&db, position)); let actual = match sig_help { Some(sig_help) => { @@ -795,6 +795,7 @@ mod tests { fn test_fn_signature_two_args() { check( r#" +//- minicore: sized, fn fn foo(x: u32, y: u32) -> u32 {x + y} fn bar() { foo($03, ); } "#, @@ -805,6 +806,7 @@ fn bar() { foo($03, ); } ); check( r#" +//- minicore: sized, fn fn foo(x: u32, y: u32) -> u32 {x + y} fn bar() { foo(3$0, ); } "#, @@ -815,6 +817,7 @@ fn bar() { foo(3$0, ); } ); check( r#" +//- minicore: sized, fn fn foo(x: u32, y: u32) -> u32 {x + y} fn bar() { foo(3,$0 ); } "#, @@ -825,6 +828,7 @@ fn bar() { foo(3,$0 ); } ); check( r#" +//- minicore: sized, fn fn foo(x: u32, y: u32) -> u32 {x + y} fn bar() { foo(3, $0); } "#, @@ -839,6 +843,7 @@ fn bar() { foo(3, $0); } fn test_fn_signature_two_args_empty() { check( r#" +//- minicore: sized, fn fn foo(x: u32, y: u32) -> u32 {x + y} fn bar() { foo($0); } "#, @@ -853,6 +858,7 @@ fn bar() { foo($0); } fn test_fn_signature_two_args_first_generics() { check( r#" +//- minicore: sized, fn fn foo(x: T, y: U) -> u32 where T: Copy + Display, U: Debug { x + y } @@ -870,6 +876,7 @@ fn bar() { foo($03, ); } fn test_fn_signature_no_params() { check( r#" +//- minicore: sized, fn fn foo() -> T where T: Copy + Display {} fn bar() { foo($0); } "#, @@ -883,6 +890,7 @@ fn bar() { foo($0); } fn test_fn_signature_for_impl() { check( r#" +//- minicore: sized, fn struct F; impl F { pub fn new() { } } fn bar() { @@ -899,6 +907,7 @@ fn bar() { fn test_fn_signature_for_method_self() { check( r#" +//- minicore: sized, fn struct S; impl S { pub fn do_it(&self) {} } @@ -917,6 +926,7 @@ fn bar() { fn test_fn_signature_for_method_with_arg() { check( r#" +//- minicore: sized, fn struct S; impl S { fn foo(&self, x: i32) {} @@ -935,6 +945,7 @@ fn main() { S.foo($0); } fn test_fn_signature_for_generic_method() { check( r#" +//- minicore: sized, fn struct S(T); impl S { fn foo(&self, x: T) {} @@ -953,6 +964,7 @@ fn main() { S(1u32).foo($0); } fn test_fn_signature_for_method_with_arg_as_assoc_fn() { check( r#" +//- minicore: sized, fn struct S; impl S { fn foo(&self, x: i32) {} @@ -971,6 +983,7 @@ fn main() { S::foo($0); } fn test_fn_signature_with_docs_simple() { check( r#" +//- minicore: sized, fn /// test // non-doc-comment fn foo(j: u32) -> u32 { @@ -994,6 +1007,7 @@ fn bar() { fn test_fn_signature_with_docs() { check( r#" +//- minicore: sized, fn /// Adds one to the number given. /// /// # Examples @@ -1031,6 +1045,7 @@ pub fn r#do() { fn test_fn_signature_with_docs_impl() { check( r#" +//- minicore: sized, fn struct addr; impl addr { /// Adds one to the number given. @@ -1073,6 +1088,7 @@ pub fn do_it() { fn test_fn_signature_with_docs_from_actix() { check( r#" +//- minicore: sized, fn trait Actor { /// Actor execution context type type Context; @@ -1106,6 +1122,7 @@ fn foo(mut r: impl WriteHandler<()>) { fn call_info_bad_offset() { check( r#" +//- minicore: sized, fn fn foo(x: u32, y: u32) -> u32 {x + y} fn bar() { foo $0 (3, ); } "#, @@ -1117,6 +1134,7 @@ fn bar() { foo $0 (3, ); } fn outside_of_arg_list() { check( r#" +//- minicore: sized, fn fn foo(a: u8) {} fn f() { foo(123)$0 @@ -1126,6 +1144,7 @@ fn f() { ); check( r#" +//- minicore: sized, fn fn foo(a: u8) {} fn f() { foo::$0() @@ -1135,6 +1154,7 @@ fn f() { ); check( r#" +//- minicore: sized, fn fn foo(a: u8) -> u8 {a} fn bar(a: u8) -> u8 {a} fn f() { @@ -1148,6 +1168,7 @@ fn f() { ); check( r#" +//- minicore: sized, fn struct Vec(T); struct Vec2(T); fn f() { @@ -1165,6 +1186,7 @@ fn f() { fn test_nested_method_in_lambda() { check( r#" +//- minicore: sized, fn struct Foo; impl Foo { fn bar(&self, _: u32) { } } @@ -1186,6 +1208,7 @@ fn main() { fn works_for_tuple_structs() { check( r#" +//- minicore: sized, fn /// A cool tuple struct struct S(u32, i32); fn main() { @@ -1205,6 +1228,7 @@ fn main() { fn tuple_struct_pat() { check( r#" +//- minicore: sized, fn /// A cool tuple struct struct S(u32, i32); fn main() { @@ -1224,6 +1248,7 @@ fn main() { fn tuple_struct_pat_rest() { check( r#" +//- minicore: sized, fn /// A cool tuple struct struct S(u32, i32, f32, u16); fn main() { @@ -1239,6 +1264,7 @@ fn main() { ); check( r#" +//- minicore: sized, fn /// A cool tuple struct struct S(u32, i32, f32, u16, u8); fn main() { @@ -1254,6 +1280,7 @@ fn main() { ); check( r#" +//- minicore: sized, fn /// A cool tuple struct struct S(u32, i32, f32, u16); fn main() { @@ -1269,6 +1296,7 @@ fn main() { ); check( r#" +//- minicore: sized, fn /// A cool tuple struct struct S(u32, i32, f32, u16, u8); fn main() { @@ -1284,6 +1312,7 @@ fn main() { ); check( r#" +//- minicore: sized, fn /// A cool tuple struct struct S(u32, i32, f32, u16); fn main() { @@ -1299,6 +1328,7 @@ fn main() { ); check( r#" +//- minicore: sized, fn /// A cool tuple struct struct S(u32, i32, f32, u16); fn main() { @@ -1318,6 +1348,7 @@ fn main() { fn generic_struct() { check( r#" +//- minicore: sized, fn struct S(T); fn main() { let s = S($0); @@ -1334,6 +1365,7 @@ fn main() { fn works_for_enum_variants() { check( r#" +//- minicore: sized, fn enum E { /// A Variant A(i32), @@ -1360,6 +1392,7 @@ fn main() { fn cant_call_struct_record() { check( r#" +//- minicore: sized, fn struct S { x: u32, y: i32 } fn main() { let s = S($0); @@ -1373,6 +1406,7 @@ fn main() { fn cant_call_enum_record() { check( r#" +//- minicore: sized, fn enum E { /// A Variant A(i32), @@ -1394,6 +1428,7 @@ fn main() { fn fn_signature_for_call_in_macro() { check( r#" +//- minicore: sized, fn macro_rules! id { ($($tt:tt)*) => { $($tt)* } } fn foo() { } id! { @@ -1410,6 +1445,7 @@ id! { fn fn_signature_for_method_call_defined_in_macro() { check( r#" +//- minicore: sized, fn macro_rules! id { ($($tt:tt)*) => { $($tt)* } } struct S; id! { @@ -1429,6 +1465,7 @@ fn test() { S.foo($0); } fn call_info_for_lambdas() { check( r#" +//- minicore: sized, fn struct S; fn foo(s: S) -> i32 { 92 } fn main() { @@ -1443,6 +1480,7 @@ fn main() { ); check( r#" +//- minicore: sized, fn struct S; fn foo(s: S) -> i32 { 92 } fn main() { @@ -1456,6 +1494,7 @@ fn main() { ); check( r#" +//- minicore: sized, fn struct S; fn foo(s: S) -> i32 { 92 } fn main() { @@ -1474,6 +1513,7 @@ fn main() { fn call_info_for_fn_def_over_reference() { check( r#" +//- minicore: sized, fn struct S; fn foo(s: S) -> i32 { 92 } fn main() { @@ -1492,6 +1532,7 @@ fn main() { fn call_info_for_fn_ptr() { check( r#" +//- minicore: sized, fn fn main(f: fn(i32, f64) -> char) { f(0, $0) } @@ -1507,6 +1548,7 @@ fn main(f: fn(i32, f64) -> char) { fn call_info_for_fn_impl() { check( r#" +//- minicore: sized, fn struct S; impl core::ops::FnOnce<(i32, f64)> for S { type Output = char; @@ -1524,6 +1566,7 @@ fn main() { ); check( r#" +//- minicore: sized, fn struct S; impl core::ops::FnOnce<(i32, f64)> for S { type Output = char; @@ -1541,6 +1584,7 @@ fn main() { ); check( r#" +//- minicore: sized, fn struct S; impl core::ops::FnOnce<(i32, f64)> for S { type Output = char; @@ -1556,6 +1600,7 @@ fn main() { ); check( r#" +//- minicore: sized, fn struct S; impl core::ops::FnOnce<(i32, f64)> for S { type Output = char; @@ -1576,6 +1621,7 @@ fn main() { fn call_info_for_unclosed_call() { check( r#" +//- minicore: sized, fn fn foo(foo: u32, bar: u32) {} fn main() { foo($0 @@ -1588,6 +1634,7 @@ fn main() { // check with surrounding space check( r#" +//- minicore: sized, fn fn foo(foo: u32, bar: u32) {} fn main() { foo( $0 @@ -1603,6 +1650,7 @@ fn main() { fn test_multiline_argument() { check( r#" +//- minicore: sized, fn fn callee(a: u8, b: u8) {} fn main() { callee(match 0 { @@ -1613,6 +1661,7 @@ fn main() { ); check( r#" +//- minicore: sized, fn fn callee(a: u8, b: u8) {} fn main() { callee(match 0 { @@ -1626,6 +1675,7 @@ fn main() { ); check( r#" +//- minicore: sized, fn fn callee(a: u8, b: u8) {} fn main() { callee($0match 0 { @@ -1643,6 +1693,7 @@ fn main() { fn test_generics_simple() { check( r#" +//- minicore: sized, fn /// Option docs. enum Option { Some(T), @@ -1666,6 +1717,7 @@ fn f() { fn test_generics_on_variant() { check( r#" +//- minicore: sized, fn /// Option docs. enum Option { /// Some docs. @@ -1693,6 +1745,7 @@ fn f() { fn test_lots_of_generics() { check( r#" +//- minicore: sized, fn trait Tr {} struct S(T); @@ -1716,6 +1769,7 @@ fn f() { fn test_generics_in_trait_ufcs() { check( r#" +//- minicore: sized, fn trait Tr { fn f() {} } @@ -1739,6 +1793,7 @@ fn f() { fn test_generics_in_method_call() { check( r#" +//- minicore: sized, fn struct S; impl S { @@ -1760,6 +1815,7 @@ fn f() { fn test_generic_param_in_method_call() { check( r#" +//- minicore: sized, fn struct Foo; impl Foo { fn test(&mut self, val: V) {} @@ -1779,6 +1835,7 @@ fn sup() { fn test_generic_kinds() { check( r#" +//- minicore: sized, fn fn callee<'a, const A: u8, T, const C: u8>() {} fn f() { @@ -1792,6 +1849,7 @@ fn f() { ); check( r#" +//- minicore: sized, fn fn callee<'a, const A: u8, T, const C: u8>() {} fn f() { @@ -1809,6 +1867,7 @@ fn f() { fn test_trait_assoc_types() { check( r#" +//- minicore: sized, fn trait Trait<'a, T> { type Assoc; } @@ -1821,6 +1880,7 @@ fn f() -> impl Trait<(), $0 ); check( r#" +//- minicore: sized, fn trait Iterator { type Item; } @@ -1833,6 +1893,7 @@ fn f() -> impl Iterator<$0 ); check( r#" +//- minicore: sized, fn trait Iterator { type Item; } @@ -1845,6 +1906,7 @@ fn f() -> impl Iterator impl Tr<$0 ); check( r#" +//- minicore: sized, fn trait Tr { type A; type B; @@ -1871,6 +1934,7 @@ fn f() -> impl Tr impl Tr impl Tr impl Sub<$0 fn no_assoc_types_outside_type_bounds() { check( r#" +//- minicore: sized, fn trait Tr { type Assoc; } @@ -1938,6 +2005,7 @@ impl Tr<$0 // FIXME: Substitute type vars in impl trait (`U` -> `i8`) check( r#" +//- minicore: sized, fn trait Trait {} struct Wrap(T); fn foo(x: Wrap>) {} @@ -1956,6 +2024,7 @@ fn f() { fn fully_qualified_syntax() { check( r#" +//- minicore: sized, fn fn f() { trait A { fn foo(&self, other: Self); } A::foo(&self$0, other); @@ -1972,6 +2041,7 @@ fn f() { fn help_for_generic_call() { check( r#" +//- minicore: sized, fn fn f i32>(f: F) { f($0) } @@ -1983,6 +2053,7 @@ fn f i32>(f: F) { ); check( r#" +//- minicore: sized, fn fn f &T>(f: F) { f($0) } @@ -2004,6 +2075,7 @@ fn f &T>(f: F) { // https://github.com/rust-lang/rust/pull/146602 should fix the solver bug. check( r#" +//- minicore: sized, fn fn f() { take(2)($0); } @@ -2022,6 +2094,7 @@ fn take( fn record_literal() { check( r#" +//- minicore: sized, fn struct Strukt { t: T, u: U, @@ -2045,6 +2118,7 @@ fn f() { fn record_literal_nonexistent_field() { check( r#" +//- minicore: sized, fn struct Strukt { a: u8, } @@ -2066,6 +2140,7 @@ fn f() { fn tuple_variant_record_literal() { check( r#" +//- minicore: sized, fn enum Opt { Some(u8), } @@ -2080,6 +2155,7 @@ fn f() { ); check( r#" +//- minicore: sized, fn enum Opt { Some(u8), } @@ -2098,6 +2174,7 @@ fn f() { fn record_literal_self() { check( r#" +//- minicore: sized, fn struct S { t: u8 } impl S { fn new() -> Self { @@ -2116,6 +2193,7 @@ impl S { fn record_pat() { check( r#" +//- minicore: sized, fn struct Strukt { t: T, u: U, @@ -2139,6 +2217,7 @@ fn f() { fn test_enum_in_nested_method_in_lambda() { check( r#" +//- minicore: sized, fn enum A { A, B @@ -2162,6 +2241,7 @@ fn main() { fn test_tuple_expr_free() { check( r#" +//- minicore: sized, fn fn main() { (0$0, 1, 3); } @@ -2173,6 +2253,7 @@ fn main() { ); check( r#" +//- minicore: sized, fn fn main() { ($0 1, 3); } @@ -2184,6 +2265,7 @@ fn main() { ); check( r#" +//- minicore: sized, fn fn main() { (1, 3 $0); } @@ -2195,6 +2277,7 @@ fn main() { ); check( r#" +//- minicore: sized, fn fn main() { (1, 3 $0,); } @@ -2210,6 +2293,7 @@ fn main() { fn test_tuple_expr_expected() { check( r#" +//- minicore: sized, fn fn main() { let _: (&str, u32, u32)= ($0, 1, 3); } @@ -2222,6 +2306,7 @@ fn main() { // FIXME: Should typeck report a 4-ary tuple for the expression here? check( r#" +//- minicore: sized, fn fn main() { let _: (&str, u32, u32, u32) = ($0, 1, 3); } @@ -2233,6 +2318,7 @@ fn main() { ); check( r#" +//- minicore: sized, fn fn main() { let _: (&str, u32, u32)= ($0, 1, 3, 5); } @@ -2248,6 +2334,7 @@ fn main() { fn test_tuple_pat_free() { check( r#" +//- minicore: sized, fn fn main() { let ($0, 1, 3); } @@ -2259,6 +2346,7 @@ fn main() { ); check( r#" +//- minicore: sized, fn fn main() { let (0$0, 1, 3); } @@ -2270,6 +2358,7 @@ fn main() { ); check( r#" +//- minicore: sized, fn fn main() { let ($0 1, 3); } @@ -2281,6 +2370,7 @@ fn main() { ); check( r#" +//- minicore: sized, fn fn main() { let (1, 3 $0); } @@ -2292,6 +2382,7 @@ fn main() { ); check( r#" +//- minicore: sized, fn fn main() { let (1, 3 $0,); } @@ -2303,6 +2394,7 @@ fn main() { ); check( r#" +//- minicore: sized, fn fn main() { let (1, 3 $0, ..); } @@ -2314,6 +2406,7 @@ fn main() { ); check( r#" +//- minicore: sized, fn fn main() { let (1, 3, .., $0); } @@ -2330,6 +2423,7 @@ fn main() { fn test_tuple_pat_expected() { check( r#" +//- minicore: sized, fn fn main() { let (0$0, 1, 3): (i32, i32, i32); } @@ -2341,6 +2435,7 @@ fn main() { ); check( r#" +//- minicore: sized, fn fn main() { let ($0, 1, 3): (i32, i32, i32); } @@ -2352,6 +2447,7 @@ fn main() { ); check( r#" +//- minicore: sized, fn fn main() { let (1, 3 $0): (i32,); } @@ -2363,6 +2459,7 @@ fn main() { ); check( r#" +//- minicore: sized, fn fn main() { let (1, 3 $0, ..): (i32, i32, i32, i32); } @@ -2374,6 +2471,7 @@ fn main() { ); check( r#" +//- minicore: sized, fn fn main() { let (1, 3, .., $0): (i32, i32, i32); } @@ -2388,6 +2486,7 @@ fn main() { fn test_tuple_pat_expected_inferred() { check( r#" +//- minicore: sized, fn fn main() { let (0$0, 1, 3) = (1, 2 ,3); } @@ -2399,6 +2498,7 @@ fn main() { ); check( r#" +//- minicore: sized, fn fn main() { let ($0 1, 3) = (1, 2, 3); } @@ -2411,6 +2511,7 @@ fn main() { ); check( r#" +//- minicore: sized, fn fn main() { let (1, 3 $0) = (1,); } @@ -2422,6 +2523,7 @@ fn main() { ); check( r#" +//- minicore: sized, fn fn main() { let (1, 3 $0, ..) = (1, 2, 3, 4); } @@ -2433,6 +2535,7 @@ fn main() { ); check( r#" +//- minicore: sized, fn fn main() { let (1, 3, .., $0) = (1, 2, 3); } @@ -2448,6 +2551,7 @@ fn main() { fn test_tuple_generic_param() { check( r#" +//- minicore: sized, fn struct S(T); fn main() { @@ -2465,6 +2569,7 @@ fn main() { fn test_enum_generic_param() { check( r#" +//- minicore: sized, fn enum Option { Some(T), None, @@ -2485,6 +2590,7 @@ fn main() { fn test_enum_variant_generic_param() { check( r#" +//- minicore: sized, fn enum Option { Some(T), None, @@ -2505,6 +2611,7 @@ fn main() { fn test_generic_arg_with_default() { check( r#" +//- minicore: sized, fn struct S { field: T, } @@ -2521,6 +2628,7 @@ fn main() { check( r#" +//- minicore: sized, fn struct S { field: C, } @@ -2535,4 +2643,27 @@ fn main() { "#]], ); } + + #[test] + fn test_async_function() { + check( + r#" +//- minicore: sized, fn, future, result +pub async fn conn_mut(f: F) -> Result +where + F: FnOnce() -> T, +{ + Ok(f()) +} + +fn main() { + conn_mut($0) +} + "#, + expect![[r#" + async fn conn_mut T, T>(f: F) -> Result + ^^^^ + "#]], + ); + } } From 5f0c7c2aae9195e8850d34d1d6f501bebf7820ad Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 29 Oct 2025 12:28:57 +0100 Subject: [PATCH 13/26] fix: Improve error recovery when parsing malformed function return types --- .../parser/src/grammar/expressions/atom.rs | 6 + .../crates/parser/src/grammar/items.rs | 8 ++ .../parser/test_data/generated/runner.rs | 8 ++ .../inline/err/closure_ret_recovery.rast | 52 ++++++++ .../parser/inline/err/closure_ret_recovery.rs | 1 + .../parser/inline/err/fn_ret_recovery.rast | 112 ++++++++++++++++++ .../parser/inline/err/fn_ret_recovery.rs | 2 + 7 files changed, 189 insertions(+) create mode 100644 src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/closure_ret_recovery.rast create mode 100644 src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/closure_ret_recovery.rs create mode 100644 src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/fn_ret_recovery.rast create mode 100644 src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/fn_ret_recovery.rs diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs index ed8a91c39c012..cde62e03238a4 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs @@ -588,6 +588,12 @@ fn closure_expr(p: &mut Parser<'_>) -> CompletedMarker { } params::param_list_closure(p); if opt_ret_type(p) { + // test_err closure_ret_recovery + // fn foo() { || -> A> { let x = 1; } } + while p.at(T![>]) { + // recover from unbalanced return type brackets + p.err_and_bump("expected a curly brace"); + } // test lambda_ret_block // fn main() { || -> i32 { 92 }(); } block_expr(p); diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/items.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/items.rs index 8e551b0b96110..c609f9383ee0f 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/items.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/items.rs @@ -424,6 +424,14 @@ fn fn_(p: &mut Parser<'_>, m: Marker) { // fn bar() -> () {} opt_ret_type(p); + // test_err fn_ret_recovery + // fn foo() -> A>]) { let x = 1; } + // fn foo() -> A>]) where T: Copy { let x = 1; } + while p.at(T![')']) | p.at(T![']']) | p.at(T![>]) { + // recover from unbalanced return type brackets + p.err_and_bump("expected a curly brace"); + } + // test function_where_clause // fn foo() where T: Copy {} generic_params::opt_where_clause(p); 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 9bdbe56330338..c56eb5090cee9 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 @@ -749,6 +749,10 @@ mod err { #[test] fn bad_asm_expr() { run_and_expect_errors("test_data/parser/inline/err/bad_asm_expr.rs"); } #[test] + fn closure_ret_recovery() { + run_and_expect_errors("test_data/parser/inline/err/closure_ret_recovery.rs"); + } + #[test] fn comma_after_default_values_syntax() { run_and_expect_errors("test_data/parser/inline/err/comma_after_default_values_syntax.rs"); } @@ -773,6 +777,10 @@ mod err { run_and_expect_errors("test_data/parser/inline/err/fn_pointer_type_missing_fn.rs"); } #[test] + fn fn_ret_recovery() { + run_and_expect_errors("test_data/parser/inline/err/fn_ret_recovery.rs"); + } + #[test] fn gen_fn() { run_and_expect_errors_with_edition( "test_data/parser/inline/err/gen_fn.rs", diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/closure_ret_recovery.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/closure_ret_recovery.rast new file mode 100644 index 0000000000000..f6266510bb4f9 --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/closure_ret_recovery.rast @@ -0,0 +1,52 @@ +SOURCE_FILE + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "foo" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE " " + CLOSURE_EXPR + PARAM_LIST + PIPE "|" + PIPE "|" + WHITESPACE " " + RET_TYPE + THIN_ARROW "->" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "A" + ERROR + R_ANGLE ">" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE " " + LET_STMT + LET_KW "let" + WHITESPACE " " + IDENT_PAT + NAME + IDENT "x" + WHITESPACE " " + EQ "=" + WHITESPACE " " + LITERAL + INT_NUMBER "1" + SEMICOLON ";" + WHITESPACE " " + R_CURLY "}" + WHITESPACE " " + R_CURLY "}" + WHITESPACE "\n" +error 18: expected a curly brace diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/closure_ret_recovery.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/closure_ret_recovery.rs new file mode 100644 index 0000000000000..7a758ec076d6e --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/closure_ret_recovery.rs @@ -0,0 +1 @@ +fn foo() { || -> A> { let x = 1; } } diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/fn_ret_recovery.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/fn_ret_recovery.rast new file mode 100644 index 0000000000000..0323df8bf0ffa --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/fn_ret_recovery.rast @@ -0,0 +1,112 @@ +SOURCE_FILE + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "foo" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + RET_TYPE + THIN_ARROW "->" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "A" + ERROR + R_ANGLE ">" + ERROR + R_BRACK "]" + ERROR + R_PAREN ")" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE " " + LET_STMT + LET_KW "let" + WHITESPACE " " + IDENT_PAT + NAME + IDENT "x" + WHITESPACE " " + EQ "=" + WHITESPACE " " + LITERAL + INT_NUMBER "1" + SEMICOLON ";" + WHITESPACE " " + R_CURLY "}" + WHITESPACE "\n" + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "foo" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + RET_TYPE + THIN_ARROW "->" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "A" + ERROR + R_ANGLE ">" + ERROR + R_BRACK "]" + ERROR + R_PAREN ")" + WHITESPACE " " + WHERE_CLAUSE + WHERE_KW "where" + WHITESPACE " " + WHERE_PRED + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "T" + COLON ":" + WHITESPACE " " + TYPE_BOUND_LIST + TYPE_BOUND + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "Copy" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE " " + LET_STMT + LET_KW "let" + WHITESPACE " " + IDENT_PAT + NAME + IDENT "x" + WHITESPACE " " + EQ "=" + WHITESPACE " " + LITERAL + INT_NUMBER "1" + SEMICOLON ";" + WHITESPACE " " + R_CURLY "}" + WHITESPACE "\n" +error 13: expected a curly brace +error 14: expected a curly brace +error 15: expected a curly brace +error 45: expected a curly brace +error 46: expected a curly brace +error 47: expected a curly brace diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/fn_ret_recovery.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/fn_ret_recovery.rs new file mode 100644 index 0000000000000..73e3d84d74e93 --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/fn_ret_recovery.rs @@ -0,0 +1,2 @@ +fn foo() -> A>]) { let x = 1; } +fn foo() -> A>]) where T: Copy { let x = 1; } From 008c60d48bbd852ecbe5c6c97e88a928bc2b2617 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 2 Aug 2025 17:26:34 +0200 Subject: [PATCH 14/26] minor: Cleanup `map_rust_child_diagnostics` a bit --- src/tools/rust-analyzer/Cargo.lock | 1 + .../crates/rust-analyzer/Cargo.toml | 1 + .../crates/rust-analyzer/src/diagnostics.rs | 5 +- .../{to_proto.rs => flycheck_to_proto.rs} | 371 +++++++++--------- .../test_data/macro_compiler_error.txt | 2 +- .../crates/rust-analyzer/src/main_loop.rs | 4 +- 6 files changed, 191 insertions(+), 193 deletions(-) rename src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics/{to_proto.rs => flycheck_to_proto.rs} (88%) diff --git a/src/tools/rust-analyzer/Cargo.lock b/src/tools/rust-analyzer/Cargo.lock index 535833d9e2a95..78c0238994b0b 100644 --- a/src/tools/rust-analyzer/Cargo.lock +++ b/src/tools/rust-analyzer/Cargo.lock @@ -2047,6 +2047,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", + "smallvec", "stdx", "syntax", "syntax-bridge", diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/Cargo.toml b/src/tools/rust-analyzer/crates/rust-analyzer/Cargo.toml index c746f848b6a06..721fae6ed5d8e 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/Cargo.toml +++ b/src/tools/rust-analyzer/crates/rust-analyzer/Cargo.toml @@ -42,6 +42,7 @@ tenthash = "1.1.0" num_cpus = "1.17.0" mimalloc = { version = "0.1.46", default-features = false, optional = true } lsp-server.workspace = true +smallvec.workspace = true tracing.workspace = true tracing-subscriber.workspace = true tracing-tree.workspace = true diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics.rs index 4bfad98b39976..4a247800af9d5 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics.rs @@ -1,5 +1,5 @@ //! Book keeping for keeping diagnostics easily in sync with the client. -pub(crate) mod to_proto; +pub(crate) mod flycheck_to_proto; use std::mem; @@ -8,6 +8,7 @@ use ide::FileId; use ide_db::{FxHashMap, base_db::DbPanicContext}; use itertools::Itertools; use rustc_hash::FxHashSet; +use smallvec::SmallVec; use stdx::iter_eq_by; use triomphe::Arc; @@ -57,7 +58,7 @@ pub(crate) struct DiagnosticCollection { #[derive(Debug, Clone)] pub(crate) struct Fix { // Fixes may be triggerable from multiple ranges. - pub(crate) ranges: Vec, + pub(crate) ranges: SmallVec<[lsp_types::Range; 1]>, pub(crate) action: lsp_ext::CodeAction, } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics/to_proto.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics/flycheck_to_proto.rs similarity index 88% rename from src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics/to_proto.rs rename to src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics/flycheck_to_proto.rs index 3f64628de8606..a6d7bcb9c70ea 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics/to_proto.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics/flycheck_to_proto.rs @@ -4,6 +4,7 @@ use crate::flycheck::{Applicability, DiagnosticLevel, DiagnosticSpan}; use itertools::Itertools; use rustc_hash::FxHashMap; +use smallvec::SmallVec; use stdx::format_to; use vfs::{AbsPath, AbsPathBuf}; @@ -18,12 +19,12 @@ use super::{DiagnosticsMapConfig, Fix}; fn diagnostic_severity( config: &DiagnosticsMapConfig, level: crate::flycheck::DiagnosticLevel, - code: Option, + code: Option<&crate::flycheck::DiagnosticCode>, ) -> Option { let res = match level { DiagnosticLevel::Ice => lsp_types::DiagnosticSeverity::ERROR, DiagnosticLevel::Error => lsp_types::DiagnosticSeverity::ERROR, - DiagnosticLevel::Warning => match &code { + DiagnosticLevel::Warning => match code { // HACK: special case for `warnings` rustc lint. Some(code) if config.warnings_as_hint.iter().any(|lint| { @@ -143,11 +144,11 @@ fn primary_location( fn diagnostic_related_information( config: &DiagnosticsMapConfig, workspace_root: &AbsPath, - span: &DiagnosticSpan, + span: DiagnosticSpan, snap: &GlobalStateSnapshot, ) -> Option { - let message = span.label.clone()?; - let location = location(config, workspace_root, span, snap); + let location = location(config, workspace_root, &span, snap); + let message = span.label?; Some(lsp_types::DiagnosticRelatedInformation { location, message }) } @@ -184,7 +185,7 @@ fn map_rust_child_diagnostic( rd: &crate::flycheck::Diagnostic, snap: &GlobalStateSnapshot, ) -> MappedRustChildDiagnostic { - let spans: Vec<&DiagnosticSpan> = rd.spans.iter().filter(|s| s.is_primary).collect(); + let spans: SmallVec<[&DiagnosticSpan; 1]> = rd.spans.iter().filter(|s| s.is_primary).collect(); if spans.is_empty() { // `rustc` uses these spanless children as a way to print multi-line // messages @@ -227,42 +228,37 @@ fn map_rust_child_diagnostic( message.push_str(&suggestions); } - if edit_map.is_empty() { - MappedRustChildDiagnostic::SubDiagnostic(SubDiagnostic { - related: lsp_types::DiagnosticRelatedInformation { - location: location(config, workspace_root, spans[0], snap), - message, - }, - suggested_fix: None, - }) + let suggested_fix = if edit_map.is_empty() { + None } else { - MappedRustChildDiagnostic::SubDiagnostic(SubDiagnostic { - related: lsp_types::DiagnosticRelatedInformation { - location: location(config, workspace_root, spans[0], snap), - message: message.clone(), + Some(Box::new(Fix { + ranges: spans + .iter() + .map(|&span| location(config, workspace_root, span, snap).range) + .collect(), + action: lsp_ext::CodeAction { + title: message.clone(), + group: None, + kind: Some(lsp_types::CodeActionKind::QUICKFIX), + edit: Some(lsp_ext::SnippetWorkspaceEdit { + // FIXME: there's no good reason to use edit_map here.... + changes: Some(edit_map), + document_changes: None, + change_annotations: None, + }), + is_preferred: Some(is_preferred), + data: None, + command: None, }, - suggested_fix: Some(Box::new(Fix { - ranges: spans - .iter() - .map(|&span| location(config, workspace_root, span, snap).range) - .collect(), - action: lsp_ext::CodeAction { - title: message, - group: None, - kind: Some(lsp_types::CodeActionKind::QUICKFIX), - edit: Some(lsp_ext::SnippetWorkspaceEdit { - // FIXME: there's no good reason to use edit_map here.... - changes: Some(edit_map), - document_changes: None, - change_annotations: None, - }), - is_preferred: Some(is_preferred), - data: None, - command: None, - }, - })), - }) - } + })) + }; + MappedRustChildDiagnostic::SubDiagnostic(SubDiagnostic { + related: lsp_types::DiagnosticRelatedInformation { + location: location(config, workspace_root, spans[0], snap), + message, + }, + suggested_fix, + }) } #[derive(Debug)] @@ -284,48 +280,56 @@ pub(crate) struct MappedRustDiagnostic { /// If the diagnostic has no primary span this will return `None` pub(crate) fn map_rust_diagnostic_to_lsp( config: &DiagnosticsMapConfig, - rd: &crate::flycheck::Diagnostic, + crate::flycheck::Diagnostic { + mut message, + code: diagnostic_code, + level, + spans, + children, + rendered, + .. + }: crate::flycheck::Diagnostic, workspace_root: &AbsPath, snap: &GlobalStateSnapshot, ) -> Vec { - let primary_spans: Vec<&DiagnosticSpan> = rd.spans.iter().filter(|s| s.is_primary).collect(); + let (primary_spans, secondary_spans): ( + SmallVec<[DiagnosticSpan; 1]>, + SmallVec<[DiagnosticSpan; 1]>, + ) = spans.into_iter().partition(|s| s.is_primary); if primary_spans.is_empty() { return Vec::new(); } - let severity = diagnostic_severity(config, rd.level, rd.code.clone()); + let mut code = diagnostic_code.as_ref().map(|c| &*c.code); - let mut source = String::from("rustc"); - let mut code = rd.code.as_ref().map(|c| c.code.clone()); - - if let Some(code_val) = &code + if let Some(code_val) = code && config.check_ignore.contains(code_val) { return Vec::new(); } - if let Some(code_val) = &code { + let severity = diagnostic_severity(config, level, diagnostic_code.as_ref()); + + let mut source = "rustc"; + if let Some(code_val) = code { // See if this is an RFC #2103 scoped lint (e.g. from Clippy) - let scoped_code: Vec<&str> = code_val.split("::").collect(); - if scoped_code.len() == 2 { - source = String::from(scoped_code[0]); - code = Some(String::from(scoped_code[1])); + if let Some((s, c)) = code_val.split("::").collect_tuple() { + source = s; + code = Some(c); } } let mut needs_primary_span_label = true; let mut subdiagnostics = Vec::new(); - let mut tags = Vec::new(); - for secondary_span in rd.spans.iter().filter(|s| !s.is_primary) { + for secondary_span in secondary_spans { let related = diagnostic_related_information(config, workspace_root, secondary_span, snap); if let Some(related) = related { subdiagnostics.push(SubDiagnostic { related, suggested_fix: None }); } } - let mut message = rd.message.clone(); - for child in &rd.children { + for child in &children { let child = map_rust_child_diagnostic(config, workspace_root, child, snap); match child { MappedRustChildDiagnostic::SubDiagnostic(sub) => { @@ -340,155 +344,146 @@ pub(crate) fn map_rust_diagnostic_to_lsp( } } } + let message = message; - if let Some(code) = &rd.code { - let code = code.code.as_str(); - if matches!( - code, - "dead_code" - | "unknown_lints" - | "unreachable_code" - | "unused_attributes" - | "unused_imports" - | "unused_macros" - | "unused_variables" - ) { - tags.push(lsp_types::DiagnosticTag::UNNECESSARY); - } - - if matches!(code, "deprecated") { - tags.push(lsp_types::DiagnosticTag::DEPRECATED); + let mut tag = None; + if let Some(code) = &diagnostic_code { + match &*code.code { + "dead_code" | "unknown_lints" | "unreachable_code" | "unused_attributes" + | "unused_imports" | "unused_macros" | "unused_variables" => { + tag = Some(lsp_types::DiagnosticTag::UNNECESSARY); + } + "deprecated" => { + tag = Some(lsp_types::DiagnosticTag::DEPRECATED); + } + _ => {} } } - let code_description = match source.as_str() { - "rustc" => rustc_code_description(code.as_deref()), - "clippy" => clippy_code_description(code.as_deref()), + let code_description = match source { + "rustc" => rustc_code_description(code), + "clippy" => clippy_code_description(code), _ => None, }; + // Each primary diagnostic span may result in multiple LSP diagnostics. + let mut diagnostics = Vec::new(); - primary_spans - .iter() - .flat_map(|primary_span| { - let primary_location = primary_location(config, workspace_root, primary_span, snap); - let message = { - let mut message = message.clone(); - if needs_primary_span_label && let Some(primary_span_label) = &primary_span.label { - format_to!(message, "\n{}", primary_span_label); - } - message - }; - // Each primary diagnostic span may result in multiple LSP diagnostics. - let mut diagnostics = Vec::new(); + for primary_span in primary_spans { + let primary_location = primary_location(config, workspace_root, &primary_span, snap); + let message = { + let mut message = message.clone(); + if needs_primary_span_label && let Some(primary_span_label) = &primary_span.label { + format_to!(message, "\n{}", primary_span_label); + } + message + }; - let mut related_info_macro_calls = vec![]; + let mut related_info_macro_calls = vec![]; - // If error occurs from macro expansion, add related info pointing to - // where the error originated - // Also, we would generate an additional diagnostic, so that exact place of macro - // will be highlighted in the error origin place. - let span_stack = std::iter::successors(Some(*primary_span), |span| { - Some(&span.expansion.as_ref()?.span) - }); - for (i, span) in span_stack.enumerate() { - if is_dummy_macro_file(&span.file_name) { - continue; - } + // If error occurs from macro expansion, add related info pointing to + // where the error originated + // Also, we would generate an additional diagnostic, so that exact place of macro + // will be highlighted in the error origin place. + let span_stack = + std::iter::successors(Some(&primary_span), |span| Some(&span.expansion.as_ref()?.span)) + .skip(1); + for (i, span) in span_stack.enumerate() { + if is_dummy_macro_file(&span.file_name) { + continue; + } + let secondary_location = location(config, workspace_root, span, snap); + if secondary_location == primary_location { + continue; + } - // First span is the original diagnostic, others are macro call locations that - // generated that code. - let is_in_macro_call = i != 0; + // First span is the original diagnostic, others are macro call locations that + // generated that code. + let is_in_macro_call = i != 0; - let secondary_location = location(config, workspace_root, span, snap); - if secondary_location == primary_location { - continue; - } - related_info_macro_calls.push(lsp_types::DiagnosticRelatedInformation { - location: secondary_location.clone(), - message: if is_in_macro_call { - "Error originated from macro call here".to_owned() - } else { - "Actual error occurred here".to_owned() - }, - }); - // For the additional in-macro diagnostic we add the inverse message pointing to the error location in code. - let information_for_additional_diagnostic = - vec![lsp_types::DiagnosticRelatedInformation { - location: primary_location.clone(), - message: "Exact error occurred here".to_owned(), - }]; + related_info_macro_calls.push(lsp_types::DiagnosticRelatedInformation { + location: secondary_location.clone(), + message: if is_in_macro_call { + "Error originated from macro call here".to_owned() + } else { + "Actual error occurred here".to_owned() + }, + }); + // For the additional in-macro diagnostic we add the inverse message pointing to the error location in code. + let information_for_additional_diagnostic = + vec![lsp_types::DiagnosticRelatedInformation { + location: primary_location.clone(), + message: "Exact error occurred here".to_owned(), + }]; - let diagnostic = lsp_types::Diagnostic { - range: secondary_location.range, - // downgrade to hint if we're pointing at the macro - severity: Some(lsp_types::DiagnosticSeverity::HINT), - code: code.clone().map(lsp_types::NumberOrString::String), - code_description: code_description.clone(), - source: Some(source.clone()), - message: message.clone(), - related_information: Some(information_for_additional_diagnostic), - tags: if tags.is_empty() { None } else { Some(tags.clone()) }, - data: Some(serde_json::json!({ "rendered": rd.rendered })), - }; - diagnostics.push(MappedRustDiagnostic { - url: secondary_location.uri, - diagnostic, - fix: None, - }); - } + let diagnostic = lsp_types::Diagnostic { + range: secondary_location.range, + // downgrade to hint if we're pointing at the macro + severity: Some(lsp_types::DiagnosticSeverity::HINT), + code: code.map(ToOwned::to_owned).map(lsp_types::NumberOrString::String), + code_description: code_description.clone(), + source: Some(source.to_owned()), + message: message.clone(), + related_information: Some(information_for_additional_diagnostic), + tags: tag.clone().map(|tag| vec![tag]), + data: Some(serde_json::json!({ "rendered": rendered })), + }; + diagnostics.push(MappedRustDiagnostic { + url: secondary_location.uri, + diagnostic, + fix: None, + }); + } - // Emit the primary diagnostic. + // Emit the primary diagnostic. + diagnostics.push(MappedRustDiagnostic { + url: primary_location.uri.clone(), + diagnostic: lsp_types::Diagnostic { + range: primary_location.range, + severity, + code: code.map(ToOwned::to_owned).map(lsp_types::NumberOrString::String), + code_description: code_description.clone(), + source: Some(source.to_owned()), + message, + related_information: { + let info = related_info_macro_calls + .iter() + .cloned() + .chain(subdiagnostics.iter().map(|sub| sub.related.clone())) + .collect::>(); + if info.is_empty() { None } else { Some(info) } + }, + tags: tag.clone().map(|tag| vec![tag]), + data: Some(serde_json::json!({ "rendered": rendered })), + }, + fix: None, + }); + + // Emit hint-level diagnostics for all `related_information` entries such as "help"s. + // This is useful because they will show up in the user's editor, unlike + // `related_information`, which just produces hard-to-read links, at least in VS Code. + let back_ref = lsp_types::DiagnosticRelatedInformation { + location: primary_location, + message: "original diagnostic".to_owned(), + }; + for sub in &subdiagnostics { diagnostics.push(MappedRustDiagnostic { - url: primary_location.uri.clone(), + url: sub.related.location.uri.clone(), + fix: sub.suggested_fix.clone(), diagnostic: lsp_types::Diagnostic { - range: primary_location.range, - severity, - code: code.clone().map(lsp_types::NumberOrString::String), + range: sub.related.location.range, + severity: Some(lsp_types::DiagnosticSeverity::HINT), + code: code.map(ToOwned::to_owned).map(lsp_types::NumberOrString::String), code_description: code_description.clone(), - source: Some(source.clone()), - message, - related_information: { - let info = related_info_macro_calls - .iter() - .cloned() - .chain(subdiagnostics.iter().map(|sub| sub.related.clone())) - .collect::>(); - if info.is_empty() { None } else { Some(info) } - }, - tags: if tags.is_empty() { None } else { Some(tags.clone()) }, - data: Some(serde_json::json!({ "rendered": rd.rendered })), + source: Some(source.to_owned()), + message: sub.related.message.clone(), + related_information: Some(vec![back_ref.clone()]), + tags: None, // don't apply modifiers again + data: None, }, - fix: None, }); - - // Emit hint-level diagnostics for all `related_information` entries such as "help"s. - // This is useful because they will show up in the user's editor, unlike - // `related_information`, which just produces hard-to-read links, at least in VS Code. - let back_ref = lsp_types::DiagnosticRelatedInformation { - location: primary_location, - message: "original diagnostic".to_owned(), - }; - for sub in &subdiagnostics { - diagnostics.push(MappedRustDiagnostic { - url: sub.related.location.uri.clone(), - fix: sub.suggested_fix.clone(), - diagnostic: lsp_types::Diagnostic { - range: sub.related.location.range, - severity: Some(lsp_types::DiagnosticSeverity::HINT), - code: code.clone().map(lsp_types::NumberOrString::String), - code_description: code_description.clone(), - source: Some(source.clone()), - message: sub.related.message.clone(), - related_information: Some(vec![back_ref.clone()]), - tags: None, // don't apply modifiers again - data: None, - }, - }); - } - - diagnostics - }) - .collect() + } + } + diagnostics } fn rustc_code_description(code: Option<&str>) -> Option { @@ -545,7 +540,7 @@ mod tests { ), ); let snap = state.snapshot(); - let mut actual = map_rust_diagnostic_to_lsp(&config, &diagnostic, workspace_root, &snap); + let mut actual = map_rust_diagnostic_to_lsp(&config, diagnostic, workspace_root, &snap); actual.iter_mut().for_each(|diag| diag.diagnostic.data = None); expect.assert_debug_eq(&actual) } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt b/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt index fe5cf9b3bea7b..b44569b4931dd 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/diagnostics/test_data/macro_compiler_error.txt @@ -191,7 +191,7 @@ }, }, }, - message: "Error originated from macro call here", + message: "Actual error occurred here", }, DiagnosticRelatedInformation { location: Location { 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 c0947b2a291ec..7fa4c3f1f3644 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 @@ -1023,9 +1023,9 @@ impl GlobalState { package_id, } => { let snap = self.snapshot(); - let diagnostics = crate::diagnostics::to_proto::map_rust_diagnostic_to_lsp( + let diagnostics = crate::diagnostics::flycheck_to_proto::map_rust_diagnostic_to_lsp( &self.config.diagnostics_map(None), - &diagnostic, + diagnostic, &workspace_root, &snap, ); From 2ac9a9a48c79437743b2f96082f6e44cf9900f79 Mon Sep 17 00:00:00 2001 From: Shoyu Vanilla Date: Fri, 31 Oct 2025 00:45:05 +0900 Subject: [PATCH 15/26] fix: Do not make false positive syntax errors on frontmatter --- .../rust-analyzer/crates/parser/src/grammar.rs | 10 ++++++++++ .../parser/test_data/generated/runner.rs | 2 ++ .../parser/inline/ok/frontmatter.rast | 18 ++++++++++++++++++ .../test_data/parser/inline/ok/frontmatter.rs | 8 ++++++++ 4 files changed, 38 insertions(+) create mode 100644 src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/frontmatter.rast create mode 100644 src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/frontmatter.rs diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar.rs b/src/tools/rust-analyzer/crates/parser/src/grammar.rs index 8ddf50db043a6..bf8430294110c 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar.rs @@ -94,7 +94,17 @@ pub(crate) mod entry { pub(crate) fn source_file(p: &mut Parser<'_>) { let m = p.start(); + // test frontmatter + // #!/usr/bin/env cargo + // + // --- + // [dependencies] + // clap = { version = "4.2", features = ["derive"] } + // --- + // + // fn main() {} p.eat(SHEBANG); + p.eat(FRONTMATTER); items::mod_contents(p, false); m.complete(p, SOURCE_FILE); } 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 c56eb5090cee9..7f5ff0ec07359 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 @@ -265,6 +265,8 @@ mod ok { #[test] fn for_type() { run_and_expect_no_errors("test_data/parser/inline/ok/for_type.rs"); } #[test] + fn frontmatter() { run_and_expect_no_errors("test_data/parser/inline/ok/frontmatter.rs"); } + #[test] fn full_range_expr() { run_and_expect_no_errors("test_data/parser/inline/ok/full_range_expr.rs"); } diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/frontmatter.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/frontmatter.rast new file mode 100644 index 0000000000000..bad8959293166 --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/frontmatter.rast @@ -0,0 +1,18 @@ +SOURCE_FILE + SHEBANG "#!/usr/bin/env cargo\n" + FRONTMATTER "\n---\n[dependencies]\nclap = { version = \"4.2\", features = [\"derive\"] }\n---\n" + WHITESPACE "\n" + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "main" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + R_CURLY "}" + WHITESPACE "\n" diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/frontmatter.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/frontmatter.rs new file mode 100644 index 0000000000000..1f9f7a76284c3 --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/frontmatter.rs @@ -0,0 +1,8 @@ +#!/usr/bin/env cargo + +--- +[dependencies] +clap = { version = "4.2", features = ["derive"] } +--- + +fn main() {} From fd378ce0b89f6d18f274a15738177c8b175c3305 Mon Sep 17 00:00:00 2001 From: Jacob Pratt Date: Fri, 31 Oct 2025 00:36:21 -0400 Subject: [PATCH 16/26] Fix typos, backtick errors/omissions --- .../crates/ide/src/syntax_highlighting.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs index 66895cb0b053c..531c7e1f4d384 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs @@ -114,9 +114,9 @@ pub struct HighlightConfig<'a> { // |-----------|--------------------------------| // |operator| Emitted for general operators.| // |arithmetic| Emitted for the arithmetic operators `+`, `-`, `*`, `/`, `+=`, `-=`, `*=`, `/=`.| -// |bitwise| Emitted for the bitwise operators `|`, `&`, `!`, `^`, `|=`, `&=`, `^=`.| +// |bitwise| Emitted for the bitwise operators `\|`, `&`, `!`, `^`, `\|=`, `&=`, `^=`.| // |comparison| Emitted for the comparison oerators `>`, `<`, `==`, `>=`, `<=`, `!=`.| -// |logical| Emitted for the logical operators `||`, `&&`, `!`.| +// |logical| Emitted for the logical operators `\|\|`, `&&`, `!`.| // // - For punctuation: // @@ -172,20 +172,20 @@ pub struct HighlightConfig<'a> { // |constant| Emitted for const.| // |consuming| Emitted for locals that are being consumed when use in a function call.| // |controlFlow| Emitted for control-flow related tokens, this includes th `?` operator.| -// |crateRoot| Emitted for crate names, like `serde` and `crate.| +// |crateRoot| Emitted for crate names, like `serde` and `crate`.| // |declaration| Emitted for names of definitions, like `foo` in `fn foo(){}`.| -// |defaultLibrary| Emitted for items from built-in crates (std, core, allc, test and proc_macro).| +// |defaultLibrary| Emitted for items from built-in crates (std, core, alloc, test and proc_macro).| // |documentation| Emitted for documentation comment.| // |injected| Emitted for doc-string injected highlighting like rust source blocks in documentation.| // |intraDocLink| Emitted for intra doc links in doc-string.| -// |library| Emitted for items that are defined outside of the current crae.| +// |library| Emitted for items that are defined outside of the current crate.| // |macro| Emitted for tokens inside macro call.| // |mutable| Emitted for mutable locals and statics as well as functions taking `&mut self`.| -// |public| Emitted for items that are from the current crate and are `pub.| -// |reference| Emitted for locals behind a reference and functions taking self` by reference.| -// |static| Emitted for "static" functions, also known as functions that d not take a `self` param, as well as statics and consts.| +// |public| Emitted for items that are from the current crate and are `pub`.| +// |reference| Emitted for locals behind a reference and functions taking `self` by reference.| +// |static| Emitted for "static" functions, also known as functions that do not take a `self` param, as well as statics and consts.| // |trait| Emitted for associated trait item.| -// |unsafe| Emitted for unsafe operations, like unsafe function calls, as ell as the `unsafe` token.| +// |unsafe| Emitted for unsafe operations, like unsafe function calls, as well as the `unsafe` token.| // // ![Semantic Syntax Highlighting](https://user-images.githubusercontent.com/48062697/113164457-06cfb980-9239-11eb-819b-0f93e646acf8.png) // ![Semantic Syntax Highlighting](https://user-images.githubusercontent.com/48062697/113187625-f7f50100-9250-11eb-825e-91c58f236071.png) From 599c3799471e68f2ddf2cb5dd55caa4fb0c2095e Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Sat, 1 Nov 2025 18:50:01 +0200 Subject: [PATCH 17/26] Fix building with DHAT Maybe we need to check it on CI? --- .../rust-analyzer/crates/rust-analyzer/src/handlers/request.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs index ad06a1d6c01fb..dfe98878613e3 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs @@ -140,7 +140,7 @@ pub(crate) fn handle_memory_usage(_state: &mut GlobalState, _: ()) -> anyhow::Re #[cfg(feature = "dhat")] { if let Some(dhat_output_file) = _state.config.dhat_output_file() { - let mutprofiler = crate::DHAT_PROFILER.lock().unwrap(); + let mut profiler = crate::DHAT_PROFILER.lock().unwrap(); let old_profiler = profiler.take(); // Need to drop the old profiler before creating a new one. drop(old_profiler); From 365798d36e6d6f0eca0f3a0b47725f6b4421275b Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sun, 2 Nov 2025 13:33:12 +0800 Subject: [PATCH 18/26] Fix missing other assoc items for generate_blanket_trait_impl - And migrate `edit_in_place::Indent` to `edit::AstNodeEdit`, because edit_in_place use ted Example --- ```rust trait $0Foo { type X: Sync; fn foo(&self, x: Self::X) -> T; fn print_foo(&self) { println!("{}", self.foo()); } } ``` **Before this PR** ```rust impl Foo for $0T1 { fn foo(&self, x: Self::X) -> T { todo!() } } ``` **After this PR** ```rust impl Foo for $0T1 { type X: Sync; fn foo(&self, x: Self::X) -> T { todo!() } } ``` --- .../handlers/generate_blanket_trait_impl.rs | 113 +++++++++++++++--- 1 file changed, 97 insertions(+), 16 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 c25b0bbe7cea0..b0fa9e6b3ebbc 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 @@ -13,7 +13,7 @@ use syntax::{ AstNode, ast::{ self, AssocItem, BlockExpr, GenericParam, HasAttrs, HasGenericParams, HasName, - HasTypeBounds, HasVisibility, edit_in_place::Indent, make, + HasTypeBounds, HasVisibility, edit::AstNodeEdit, make, }, syntax_editor::Position, }; @@ -75,7 +75,7 @@ pub(crate) fn generate_blanket_trait_impl( |builder| { let mut edit = builder.make_editor(traitd.syntax()); let namety = make::ty_path(make::path_from_text(&name.text())); - let trait_where_clause = traitd.where_clause().map(|it| it.clone_for_update()); + let trait_where_clause = traitd.where_clause().map(|it| it.reset_indent()); let bounds = traitd.type_bound_list().and_then(exlucde_sized); let is_unsafe = traitd.unsafe_token().is_some(); let thisname = this_name(&traitd); @@ -90,10 +90,6 @@ pub(crate) fn generate_blanket_trait_impl( let trait_gen_args = traitd.generic_param_list().map(|param_list| param_list.to_generic_args()); - if let Some(ref where_clause) = trait_where_clause { - where_clause.reindent_to(0.into()); - } - let impl_ = make::impl_trait( cfg_attrs(&traitd), is_unsafe, @@ -112,20 +108,19 @@ pub(crate) fn generate_blanket_trait_impl( if let Some(trait_assoc_list) = traitd.assoc_item_list() { let assoc_item_list = impl_.get_or_create_assoc_item_list(); - for method in trait_assoc_list.assoc_items() { - let AssocItem::Fn(method) = method else { - continue; + for item in trait_assoc_list.assoc_items() { + let item = match item { + ast::AssocItem::Fn(method) if method.body().is_none() => { + todo_fn(&method, ctx.config).into() + } + ast::AssocItem::Const(_) | ast::AssocItem::TypeAlias(_) => item, + _ => continue, }; - if method.body().is_some() { - continue; - } - let f = todo_fn(&method, ctx.config).clone_for_update(); - f.indent(1.into()); - assoc_item_list.add_item(AssocItem::Fn(f)); + assoc_item_list.add_item(item.reset_indent().indent(1.into())); } } - impl_.indent(indent); + let impl_ = impl_.indent(indent); edit.insert_all( Position::after(traitd.syntax()), @@ -506,6 +501,41 @@ impl Foo for $0I { ); } + #[test] + fn test_gen_blanket_other_assoc_items() { + check_assist( + generate_blanket_trait_impl, + r#" +trait $0Foo { + type Item; + + const N: usize; + + fn foo(&self); +} +"#, + r#" +trait Foo { + type Item; + + const N: usize; + + fn foo(&self); +} + +impl Foo for $0T { + type Item; + + const N: usize; + + fn foo(&self) { + todo!() + } +} +"#, + ); + } + #[test] fn test_gen_blanket_indent() { check_assist( @@ -739,6 +769,49 @@ mod foo { } } } +} + "#, + ); + check_assist( + generate_blanket_trait_impl, + r#" +mod foo { + mod bar { + trait $0Foo { + type Item: Bar< + Self, + >; + + const N: Baz< + Self, + >; + } + } +} + "#, + r#" +mod foo { + mod bar { + trait Foo { + type Item: Bar< + Self, + >; + + const N: Baz< + Self, + >; + } + + impl Foo for $0T { + type Item: Bar< + Self, + >; + + const N: Baz< + Self, + >; + } + } } "#, ); @@ -824,6 +897,8 @@ impl Foo for $0T1 where Self::Owned: Default, { + type X: Sync; + fn foo(&self, x: Self::X) -> T { todo!() } @@ -871,6 +946,8 @@ where Self: ToOwned, Self::Owned: Default, { + type X: Sync; + fn foo(&self, x: Self::X) -> T { todo!() } @@ -906,6 +983,8 @@ trait Foo { } impl Foo for $0T1 { + type X: Sync; + fn foo(&self, x: Self::X) -> T { todo!() } @@ -941,6 +1020,8 @@ trait Foo { } impl Foo for $0T { + type X: Sync; + fn foo(&self, x: Self::X) -> i32 { todo!() } From 449576a590ecd0dceab9e6e73eb57f996bad3b52 Mon Sep 17 00:00:00 2001 From: The rustc-josh-sync Cronjob Bot Date: Mon, 3 Nov 2025 04:15:21 +0000 Subject: [PATCH 19/26] Prepare for merging from rust-lang/rust This updates the rust-version file to c5dabe8cf798123087d094f06417f5a767ca73e8. --- src/tools/rust-analyzer/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/rust-version b/src/tools/rust-analyzer/rust-version index c9529fde2363e..0e89b4ab6ac75 100644 --- a/src/tools/rust-analyzer/rust-version +++ b/src/tools/rust-analyzer/rust-version @@ -1 +1 @@ -fb24b04b096a980bffd80154f6aba22fd07cb3d9 +c5dabe8cf798123087d094f06417f5a767ca73e8 From 236b89e4a84c8d59abec0faf6967a34942e390aa Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Mon, 3 Nov 2025 20:47:40 +0800 Subject: [PATCH 20/26] Add more expression to 'in_value' When completing some expressions, it is almost always expected to have a non unit value - ArrayExpr - ParenExpr - BreakExpr - ReturnExpr - PrefixExpr - FormatArgsArg - RecordExprField - BinExpr (rhs only) - IndexExpr (inside index only) Example --- ```rust fn main() { return $0; } ``` **Before this PR** ```rust fn main() { return if $1 { $0 }; } ``` **After this PR** ```rust fn main() { return if $1 { $2 } else { $0 }; } ``` --- .../ide-completion/src/completions/keyword.rs | 140 ++++++++++++++++++ .../ide-completion/src/context/analysis.rs | 21 ++- 2 files changed, 160 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/keyword.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/keyword.rs index 6162d98372839..eab2b9063f95b 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/keyword.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/keyword.rs @@ -531,6 +531,146 @@ fn main() { ); } + #[test] + fn if_completion_in_format() { + check_edit( + "if", + r#" +//- minicore: fmt +fn main() { + format_args!("{}", $0); +} +"#, + r#" +fn main() { + format_args!("{}", if $1 { + $2 +} else { + $0 +}); +} +"#, + ); + + check_edit( + "if", + r#" +//- minicore: fmt +fn main() { + format_args!("{}", if$0); +} +"#, + r#" +fn main() { + format_args!("{}", if $1 { + $2 +} else { + $0 +}); +} +"#, + ); + } + + #[test] + fn if_completion_in_value_expected_expressions() { + check_edit( + "if", + r#" +fn main() { + 2 + $0; +} +"#, + r#" +fn main() { + 2 + if $1 { + $2 +} else { + $0 +}; +} +"#, + ); + + check_edit( + "if", + r#" +fn main() { + -$0; +} +"#, + r#" +fn main() { + -if $1 { + $2 +} else { + $0 +}; +} +"#, + ); + + check_edit( + "if", + r#" +fn main() { + return $0; +} +"#, + r#" +fn main() { + return if $1 { + $2 +} else { + $0 +}; +} +"#, + ); + + check_edit( + "if", + r#" +fn main() { + loop { + break $0; + } +} +"#, + r#" +fn main() { + loop { + break if $1 { + $2 +} else { + $0 +}; + } +} +"#, + ); + + check_edit( + "if", + r#" +struct Foo { x: i32 } +fn main() { + Foo { x: $0 } +} +"#, + r#" +struct Foo { x: i32 } +fn main() { + Foo { x: if $1 { + $2 +} else { + $0 +} } +} +"#, + ); + } + #[test] fn completes_let_in_block() { check_edit( 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 b3d9ff0046102..d6d3978385d9b 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 @@ -1012,6 +1012,25 @@ fn classify_name_ref<'db>( .and_then(|next| next.first_token()) .is_some_and(|token| token.kind() == SyntaxKind::ELSE_KW) }; + let is_in_value = |it: &SyntaxNode| { + let Some(node) = it.parent() else { return false }; + let kind = node.kind(); + ast::LetStmt::can_cast(kind) + || ast::ArgList::can_cast(kind) + || ast::ArrayExpr::can_cast(kind) + || ast::ParenExpr::can_cast(kind) + || ast::BreakExpr::can_cast(kind) + || ast::ReturnExpr::can_cast(kind) + || ast::PrefixExpr::can_cast(kind) + || ast::FormatArgsArg::can_cast(kind) + || ast::RecordExprField::can_cast(kind) + || ast::BinExpr::cast(node.clone()) + .and_then(|expr| expr.rhs()) + .is_some_and(|expr| expr.syntax() == it) + || ast::IndexExpr::cast(node) + .and_then(|expr| expr.index()) + .is_some_and(|expr| expr.syntax() == it) + }; // We do not want to generate path completions when we are sandwiched between an item decl signature and its body. // ex. trait Foo $0 {} @@ -1307,7 +1326,7 @@ fn classify_name_ref<'db>( .and_then(ast::LetStmt::cast) .is_some_and(|it| it.semicolon_token().is_none()) || after_incomplete_let && incomplete_expr_stmt.unwrap_or(true) && !before_else_kw; - let in_value = it.parent().and_then(Either::::cast).is_some(); + let in_value = is_in_value(it); let impl_ = fetch_immediate_impl_or_trait(sema, original_file, expr.syntax()) .and_then(Either::left); From fdaf4fa6f68a30322152d3f3e76890de92ffc6cb Mon Sep 17 00:00:00 2001 From: Shoyu Vanilla Date: Tue, 4 Nov 2025 00:36:27 +0900 Subject: [PATCH 21/26] fix: Expand literals with wrong suffixes into `LitKind::Err` --- .../test_data/regression_20952.html | 45 +++++++++++++++++++ .../ide/src/syntax_highlighting/tests.rs | 14 ++++++ .../crates/proc-macro-srv/src/tests/mod.rs | 4 +- src/tools/rust-analyzer/crates/tt/src/lib.rs | 9 ++++ 4 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/regression_20952.html diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/regression_20952.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/regression_20952.html new file mode 100644 index 0000000000000..2c0250c6d4c44 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/regression_20952.html @@ -0,0 +1,45 @@ + + +
fn main() {
+    format_args!("{} {}, {} (подозрение на спам: {:.2}%)"б);
+}
\ 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 4e84127c29f82..58c613ef7c643 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 @@ -1497,3 +1497,17 @@ fn main() { false, ); } + +#[test] +fn regression_20952() { + check_highlighting( + r#" +//- minicore: fmt +fn main() { + format_args!("{} {}, {} (подозрение на спам: {:.2}%)"б); +} +"#, + expect_file!["./test_data/regression_20952.html"], + false, + ); +} diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/mod.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/mod.rs index 08495f50bff44..d4f9976c92bdf 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/mod.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/mod.rs @@ -326,7 +326,7 @@ fn test_fn_like_macro_clone_literals() { PUNCH , [alone] 1 LITERAL Str hello bridge 1 PUNCH , [alone] 1 - LITERAL Str suffixedsuffix 1 + LITERAL Err(()) "suffixed"suffix 1 PUNCH , [alone] 1 LITERAL StrRaw(2) raw 1 PUNCH , [alone] 1 @@ -372,7 +372,7 @@ fn test_fn_like_macro_clone_literals() { PUNCH , [alone] 42:Root[0000, 0]@27..28#ROOT2024 LITERAL Str hello bridge 42:Root[0000, 0]@29..43#ROOT2024 PUNCH , [alone] 42:Root[0000, 0]@43..44#ROOT2024 - LITERAL Str suffixedsuffix 42:Root[0000, 0]@45..61#ROOT2024 + LITERAL Err(()) "suffixed"suffix 42:Root[0000, 0]@45..61#ROOT2024 PUNCH , [alone] 42:Root[0000, 0]@61..62#ROOT2024 LITERAL StrRaw(2) raw 42:Root[0000, 0]@63..73#ROOT2024 PUNCH , [alone] 42:Root[0000, 0]@73..74#ROOT2024 diff --git a/src/tools/rust-analyzer/crates/tt/src/lib.rs b/src/tools/rust-analyzer/crates/tt/src/lib.rs index 243a27b83b0df..f9a547f611389 100644 --- a/src/tools/rust-analyzer/crates/tt/src/lib.rs +++ b/src/tools/rust-analyzer/crates/tt/src/lib.rs @@ -622,6 +622,15 @@ where let lit = &lit[start_offset..lit.len() - end_offset]; let suffix = match suffix { "" | "_" => None, + // ill-suffixed literals + _ if !matches!(kind, LitKind::Integer | LitKind::Float | LitKind::Err(_)) => { + return Literal { + span, + symbol: Symbol::intern(text), + kind: LitKind::Err(()), + suffix: None, + }; + } suffix => Some(Symbol::intern(suffix)), }; From f3111966a47d167a6fd2defc356bd0c0f92ac0f1 Mon Sep 17 00:00:00 2001 From: Shoyu Vanilla Date: Tue, 4 Nov 2025 01:37:13 +0900 Subject: [PATCH 22/26] fix: Canonicalize `custom-target.json` paths when fetching sysroot metadata --- .../crates/project-model/src/workspace.rs | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/project-model/src/workspace.rs b/src/tools/rust-analyzer/crates/project-model/src/workspace.rs index 3c696256854cd..aa2e15930cbb7 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/workspace.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/workspace.rs @@ -374,6 +374,7 @@ impl ProjectWorkspace { sysroot.load_workspace( &RustSourceWorkspaceConfig::CargoMetadata(sysroot_metadata_config( config, + workspace_dir, &targets, toolchain.clone(), )), @@ -480,6 +481,7 @@ impl ProjectWorkspace { sysroot.load_workspace( &RustSourceWorkspaceConfig::CargoMetadata(sysroot_metadata_config( config, + project_json.project_root(), &targets, toolchain.clone(), )), @@ -535,6 +537,7 @@ impl ProjectWorkspace { let loaded_sysroot = sysroot.load_workspace( &RustSourceWorkspaceConfig::CargoMetadata(sysroot_metadata_config( config, + dir, &targets, toolchain.clone(), )), @@ -1864,12 +1867,29 @@ fn add_dep_inner(graph: &mut CrateGraphBuilder, from: CrateBuilderId, dep: Depen fn sysroot_metadata_config( config: &CargoConfig, + current_dir: &AbsPath, targets: &[String], toolchain_version: Option, ) -> CargoMetadataConfig { + // We run `cargo metadata` on sysroot with sysroot dir as a working directory, but still pass + // the `targets` from the cargo config evaluated from the workspace's `current_dir`. + // So, we need to *canonicalize* those *might-be-relative-paths-to-custom-target-json-files*. + // + // See https://github.com/rust-lang/cargo/blob/f7acf448fc127df9a77c52cc2bba027790ac4931/src/cargo/core/compiler/compile_kind.rs#L171-L192 + let targets = targets + .iter() + .map(|target| { + if target.ends_with(".json") { + current_dir.join(target).to_string() + } else { + target.to_owned() + } + }) + .collect(); + CargoMetadataConfig { features: Default::default(), - targets: targets.to_vec(), + targets, extra_args: Default::default(), extra_env: config.extra_env.clone(), toolchain_version, From 9e9ea26d64ecf69b1149976b833cd66043fdb535 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Fri, 29 Aug 2025 12:44:26 +0800 Subject: [PATCH 23/26] Add ide-assist: convert_range_for_to_while Convert for each range into while loop. ```rust fn foo() { $0for i in 3..7 { foo(i); } } ``` -> ```rust fn foo() { let mut i = 3; while i < 7 { foo(i); i += 1; } } ``` --- .../handlers/convert_range_for_to_while.rs | 254 ++++++++++++++++++ .../crates/ide-assists/src/lib.rs | 2 + .../crates/ide-assists/src/tests/generated.rs | 23 ++ .../crates/syntax/src/ast/make.rs | 2 +- .../src/ast/syntax_factory/constructors.rs | 14 + 5 files changed, 294 insertions(+), 1 deletion(-) create mode 100644 src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_range_for_to_while.rs 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 new file mode 100644 index 0000000000000..16f9b4c411202 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_range_for_to_while.rs @@ -0,0 +1,254 @@ +use ide_db::assists::AssistId; +use itertools::Itertools; +use syntax::{ + AstNode, T, + algo::previous_non_trivia_token, + ast::{ + self, HasArgList, HasLoopBody, HasName, RangeItem, edit::AstNodeEdit, make, + syntax_factory::SyntaxFactory, + }, + syntax_editor::{Element, Position}, +}; + +use crate::assist_context::{AssistContext, Assists}; + +// Assist: convert_range_for_to_while +// +// Convert for each range into while loop. +// +// ``` +// fn foo() { +// $0for i in 3..7 { +// foo(i); +// } +// } +// ``` +// -> +// ``` +// fn foo() { +// let mut i = 3; +// while i < 7 { +// foo(i); +// i += 1; +// } +// } +// ``` +pub(crate) fn convert_range_for_to_while(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let for_kw = ctx.find_token_syntax_at_offset(T![for])?; + let for_ = ast::ForExpr::cast(for_kw.parent()?)?; + let ast::Pat::IdentPat(pat) = for_.pat()? else { return None }; + let iterable = for_.iterable()?; + let (start, end, step, inclusive) = extract_range(&iterable)?; + let name = pat.name()?; + let body = for_.loop_body()?; + let last = previous_non_trivia_token(body.stmt_list()?.r_curly_token()?)?; + + acc.add( + AssistId::refactor("convert_range_for_to_while"), + "Replace to while or loop", + for_.syntax().text_range(), + |builder| { + let mut edit = builder.make_editor(for_.syntax()); + let make = SyntaxFactory::with_mappings(); + + let indent = for_.indent_level(); + let pat = make.ident_pat(pat.ref_token().is_some(), true, name.clone()); + let let_stmt = make.let_stmt(pat.into(), None, Some(start)); + edit.insert_all( + Position::before(for_.syntax()), + vec![ + let_stmt.syntax().syntax_element(), + make.whitespace(&format!("\n{}", indent)).syntax_element(), + ], + ); + + let mut elements = vec![]; + + 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, + }); + if let Some(end) = end { + elements.extend([ + make.token(T![while]).syntax_element(), + make.whitespace(" ").syntax_element(), + make.expr_bin(var_expr.clone(), op, end).syntax().syntax_element(), + ]); + } else { + elements.push(make.token(T![loop]).syntax_element()); + } + + edit.replace_all( + for_kw.syntax_element()..=iterable.syntax().syntax_element(), + elements, + ); + + let op = ast::BinaryOp::Assignment { op: Some(ast::ArithOp::Add) }; + edit.insert_all( + Position::after(last), + vec![ + make.whitespace(&format!("\n{}", indent + 1)).syntax_element(), + make.expr_bin(var_expr, op, step).syntax().syntax_element(), + make.token(T![;]).syntax_element(), + ], + ); + + edit.add_mappings(make.finish_with_mappings()); + builder.add_file_edits(ctx.vfs_file_id(), edit); + }, + ) +} + +fn extract_range(iterable: &ast::Expr) -> Option<(ast::Expr, Option, ast::Expr, bool)> { + Some(match iterable { + ast::Expr::ParenExpr(expr) => extract_range(&expr.expr()?)?, + ast::Expr::RangeExpr(range) => { + let inclusive = range.op_kind()? == ast::RangeOp::Inclusive; + (range.start()?, range.end(), make::expr_literal("1").into(), inclusive) + } + ast::Expr::MethodCallExpr(call) if call.name_ref()?.text() == "step_by" => { + let [step] = call.arg_list()?.args().collect_array()?; + let (start, end, _, inclusive) = extract_range(&call.receiver()?)?; + (start, end, step, inclusive) + } + _ => return None, + }) +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn test_convert_range_for_to_while() { + check_assist( + convert_range_for_to_while, + " +fn foo() { + $0for i in 3..7 { + foo(i); + } +} + ", + " +fn foo() { + let mut i = 3; + while i < 7 { + foo(i); + i += 1; + } +} + ", + ); + } + + #[test] + fn test_convert_range_for_to_while_no_end_bound() { + check_assist( + convert_range_for_to_while, + " +fn foo() { + $0for i in 3.. { + foo(i); + } +} + ", + " +fn foo() { + let mut i = 3; + loop { + foo(i); + i += 1; + } +} + ", + ); + } + + #[test] + fn test_convert_range_for_to_while_with_mut_binding() { + check_assist( + convert_range_for_to_while, + " +fn foo() { + $0for mut i in 3..7 { + foo(i); + } +} + ", + " +fn foo() { + let mut i = 3; + while i < 7 { + foo(i); + i += 1; + } +} + ", + ); + } + + #[test] + fn test_convert_range_for_to_while_with_label() { + check_assist( + convert_range_for_to_while, + " +fn foo() { + 'a: $0for mut i in 3..7 { + foo(i); + } +} + ", + " +fn foo() { + let mut i = 3; + 'a: while i < 7 { + foo(i); + i += 1; + } +} + ", + ); + } + + #[test] + fn test_convert_range_for_to_while_step_by() { + check_assist( + convert_range_for_to_while, + " +fn foo() { + $0for mut i in (3..7).step_by(2) { + foo(i); + } +} + ", + " +fn foo() { + let mut i = 3; + while i < 7 { + foo(i); + i += 2; + } +} + ", + ); + } + + #[test] + fn test_convert_range_for_to_while_not_applicable_non_range() { + check_assist_not_applicable( + convert_range_for_to_while, + " +fn foo() { + let ident = 3..7; + $0for mut i in ident { + foo(i); + } +} + ", + ); + } +} diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs b/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs index e9f2d686465e2..4b4aa94279559 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs @@ -131,6 +131,7 @@ mod handlers { mod convert_match_to_let_else; mod convert_named_struct_to_tuple_struct; mod convert_nested_function_to_closure; + mod convert_range_for_to_while; mod convert_to_guarded_return; mod convert_tuple_return_type_to_struct; mod convert_tuple_struct_to_named_struct; @@ -268,6 +269,7 @@ mod handlers { convert_match_to_let_else::convert_match_to_let_else, convert_named_struct_to_tuple_struct::convert_named_struct_to_tuple_struct, convert_nested_function_to_closure::convert_nested_function_to_closure, + convert_range_for_to_while::convert_range_for_to_while, convert_to_guarded_return::convert_to_guarded_return, convert_tuple_return_type_to_struct::convert_tuple_return_type_to_struct, convert_tuple_struct_to_named_struct::convert_tuple_struct_to_named_struct, diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs b/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs index a99fe8de333db..7f0836abdf3ca 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs @@ -731,6 +731,29 @@ fn main() { ) } +#[test] +fn doctest_convert_range_for_to_while() { + check_doc_test( + "convert_range_for_to_while", + r#####" +fn foo() { + $0for i in 3..7 { + foo(i); + } +} +"#####, + r#####" +fn foo() { + let mut i = 3; + while i < 7 { + foo(i); + i += 1; + } +} +"#####, + ) +} + #[test] fn doctest_convert_to_guarded_return() { check_doc_test( diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs index 051c5835571bc..dba39204e32ee 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs @@ -1355,7 +1355,7 @@ pub mod tokens { pub(super) static SOURCE_FILE: LazyLock> = LazyLock::new(|| { SourceFile::parse( - "use crate::foo; const C: <()>::Item = ( true && true , true || true , 1 != 1, 2 == 2, 3 < 3, 4 <= 4, 5 > 5, 6 >= 6, !true, *p, &p , &mut p, async { let _ @ [] })\n;\n\nunsafe impl A for B where: {}", + "use crate::foo; const C: <()>::Item = ( true && true , true || true , 1 != 1, 2 == 2, 3 < 3, 4 <= 4, 5 > 5, 6 >= 6, !true, *p, &p , &mut p, async { let _ @ [] }, while loop {} {})\n;\n\nunsafe impl A for B where: {}", Edition::CURRENT, ) }); 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 8bf27f967482b..9695523921808 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 @@ -644,6 +644,20 @@ impl SyntaxFactory { ast } + 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!() + }; + + if let Some(mut mapping) = self.mappings() { + let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone()); + builder.map_node(body.syntax().clone(), ast.loop_body().unwrap().syntax().clone()); + builder.finish(&mut mapping); + } + + ast + } + 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(); From a925768250c80e4859610978bc138195bad23d99 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Tue, 4 Nov 2025 13:26:55 +0800 Subject: [PATCH 24/26] Add dynamic assistant description --- .../ide-assists/src/handlers/convert_range_for_to_while.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 16f9b4c411202..68cb764030956 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 @@ -43,9 +43,14 @@ pub(crate) fn convert_range_for_to_while(acc: &mut Assists, ctx: &AssistContext< let body = for_.loop_body()?; let last = previous_non_trivia_token(body.stmt_list()?.r_curly_token()?)?; + let description = if end.is_some() { + "Replace with while expression" + } else { + "Replace with loop expression" + }; acc.add( AssistId::refactor("convert_range_for_to_while"), - "Replace to while or loop", + description, for_.syntax().text_range(), |builder| { let mut edit = builder.make_editor(for_.syntax()); From fe6abc79d29f17bcafbecef7c225fd1875f13d30 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Tue, 4 Nov 2025 09:58:29 +0200 Subject: [PATCH 25/26] Fix test name --- src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 32ecf14807374..75d32035d0998 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 @@ -2508,7 +2508,7 @@ fn main() { } #[test] -fn foo() { +fn module_inside_block() { check_types( r#" fn foo() { From d81d5f0b7084cc7b131daeff2a33fbe6c6efe0a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lauren=C8=9Biu=20Nicola?= Date: Tue, 4 Nov 2025 10:09:45 +0200 Subject: [PATCH 26/26] Fix test URL --- .../crates/hir-def/src/macro_expansion_tests/mbe/meta_syntax.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/meta_syntax.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/meta_syntax.rs index 9997455432f7d..311fdc34260ea 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/meta_syntax.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/meta_syntax.rs @@ -98,7 +98,7 @@ macro_rules! m2 { () => ( ${invalid()} ) } #[test] fn test_rustc_issue_57597() { - // + // check( r#" macro_rules! m0 { ($($($i:ident)?)+) => {}; }