From 768091d99822f98e57b3e41413c819b95281ff3b Mon Sep 17 00:00:00 2001 From: Aditya Pratap Singh Date: Sat, 5 Sep 2026 01:14:27 +0530 Subject: [PATCH 01/22] fix: don't panic on doc comments attached to literal expressions --- src/tools/rust-analyzer/crates/syntax/src/ast/expr_ext.rs | 2 +- src/tools/rust-analyzer/crates/syntax/src/tests.rs | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/expr_ext.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/expr_ext.rs index ced9163f661af..2b1c91f20517d 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/expr_ext.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/expr_ext.rs @@ -335,7 +335,7 @@ impl ast::Literal { pub fn token(&self) -> SyntaxToken { self.syntax() .children_with_tokens() - .find(|e| e.kind() != ATTR && !e.kind().is_trivia()) + .find(|e| !ast::AnyAttr::can_cast(e.kind()) && !e.kind().is_trivia()) .and_then(|e| e.into_token()) .unwrap() } diff --git a/src/tools/rust-analyzer/crates/syntax/src/tests.rs b/src/tools/rust-analyzer/crates/syntax/src/tests.rs index e5beb44f42ec7..1002fdd902d29 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/tests.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/tests.rs @@ -126,6 +126,12 @@ fn self_hosting_parsing() { } } +#[test] +fn doc_comment_on_literal_expr() { + let parse = SourceFile::parse("fn f() { ///\n0..0; }", parser::Edition::CURRENT); + assert!(parse.errors().is_empty()); +} + fn test_data_dir() -> PathBuf { project_root().into_std_path_buf().join("crates/syntax/test_data") } From f8326c98fc14ad6fea7cbf8fd5e058cbc46f2df6 Mon Sep 17 00:00:00 2001 From: dimxy Date: Sun, 6 Sep 2026 19:28:10 +0500 Subject: [PATCH 02/22] ide: fix doc comment offset calculation --- .../rust-analyzer/crates/ide/src/doc_links.rs | 5 ++- .../crates/ide/src/goto_definition.rs | 31 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/ide/src/doc_links.rs b/src/tools/rust-analyzer/crates/ide/src/doc_links.rs index c152d7e9cc964..94e7db9390bc3 100644 --- a/src/tools/rust-analyzer/crates/ide/src/doc_links.rs +++ b/src/tools/rust-analyzer/crates/ide/src/doc_links.rs @@ -312,7 +312,10 @@ impl DocCommentToken { let DocCommentToken { prefix_len, doc_token } = self; // offset relative to the comments contents let original_start = doc_token.text_range().start(); - let relative_comment_offset = offset - original_start - prefix_len; + // If the cursor points inside the comment like `///` or to the first quote in `#[doc = "..."]` + // (i.e. relative_comment_offset is None) then we return w/o definition. + let relative_comment_offset = + offset.checked_sub(original_start)?.checked_sub(prefix_len)?; sema.descend_into_macros(doc_token).into_iter().find_map(|t| { let (node, descended_prefix_len, is_inner) = match_ast!{ diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs b/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs index 033de7dcc20b4..bb317525dd5d8 100644 --- a/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs +++ b/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs @@ -723,6 +723,13 @@ mod tests { assert!(navs.is_empty(), "didn't expect this to resolve anywhere: {navs:?}") } + #[track_caller] + fn check_no_definition(#[rust_analyzer::rust_fixture] ra_fixture: &str) { + let (analysis, position) = fixture::position(ra_fixture); + let navs = analysis.goto_definition(position, &TEST_CONFIG).unwrap(); + assert!(navs.is_none(), "didn't expect this to resolve anywhere: {navs:?}"); + } + fn check_name(expected_name: &str, #[rust_analyzer::rust_fixture] ra_fixture: &str) { let (analysis, position, _) = fixture::annotations(ra_fixture); let navs = analysis @@ -2132,6 +2139,30 @@ pub fn foo() { } ) } + #[test] + fn no_panic_on_offset_inside_doc_comment_prefix() { + // If the cursor (offset) points inside `///`/`//!`/the opening quote, i.e. before the docs' contents, + // this should not create navigation. + check_no_definition( + r#" +$0/// [`S`] +struct S; +"#, + ); + check_no_definition( + r#" +//$0! [`S`] +struct S; +"#, + ); + check_no_definition( + r#" +#[doc = $0"[`S`]"] +struct S; +"#, + ); + } + #[test] fn goto_def_for_intra_doc_link_outer_same_file() { check( From af3f278c5f3e84d40b81b8bcb0beb9715cdb8487 Mon Sep 17 00:00:00 2001 From: Wilfred Hughes Date: Mon, 7 Sep 2026 07:13:35 -0700 Subject: [PATCH 03/22] internal: Add regression test for 'failed to unify type errors' panic The panic was fixed in 7fbbd2039c30fa944ec534d6f54569015b36e939, but it didn't have a test. Add a test as suggested by @A4-Tacks, based on a minimised repro I'd seen (this panic was the most common I'd seen on the latest rust-analyzer version). AI disclosure: Test code with some help by Claude Opus 5, commit message by me. --- .../ide-diagnostics/src/handlers/typed_hole.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/typed_hole.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/typed_hole.rs index e2d31503f1d08..e93e74177abe8 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/typed_hole.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/typed_hole.rs @@ -464,6 +464,20 @@ fn main() { m!(generic::); } } +"#, + ); + } + + #[test] + fn term_search_lookup_const() { + check_diagnostics( + r#" +struct S { f: i32 } +const C: i32 = 0; +fn main() { + let _: S = _; + //^ 💡 error: invalid `_` expression, expected type `S` +} "#, ); } From 071f269c78d4032ebe5aea9bbb27974438f81a57 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 3 Sep 2026 15:22:22 +0200 Subject: [PATCH 04/22] fix: restore the `#[deprecated]` attr Accidentally removed during https://github.com/rust-lang/rust-analyzer/commit/492420db8aadb6535f8c75f38d6b867d2d412295 --- src/tools/rust-analyzer/crates/hir-expand/src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs index f7ac3f1c02694..e8d61a6eadf63 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs @@ -537,6 +537,12 @@ impl MacroCallId { /// query, only typing in the macro call itself changes the returned /// subtree. #[salsa::tracked(returns(ref))] + #[allow( + useless_deprecated, + unused_attributes, + reason = "salsa bug, see https://github.com/salsa-rs/salsa/issues/1307" + )] + #[deprecated = "calling this is incorrect, call `macro_arg_considering_derives` instead"] fn macro_arg(self, db: &dyn SourceDatabase) -> MacroArgResult { let loc = self.loc(db); From ccfeaac5ebe8e533a11588573000ef3961913464 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Tue, 11 Aug 2026 16:40:30 +0200 Subject: [PATCH 05/22] remove `allow`s that are no longer necessary Mostly thanks to FPs having gotten fixed --- src/tools/rust-analyzer/crates/hir-def/src/dyn_map.rs | 4 ---- .../rust-analyzer/crates/hir-def/src/hir/format_args.rs | 1 - src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs | 1 - .../crates/hir-ty/src/consteval/tests/intrinsics.rs | 2 +- src/tools/rust-analyzer/crates/hir-ty/src/infer.rs | 5 ----- src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs | 2 -- src/tools/rust-analyzer/crates/ide-ssr/src/matching.rs | 1 - src/tools/rust-analyzer/crates/ide/src/hover.rs | 1 - src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs | 2 +- .../crates/rust-analyzer/tests/slow-tests/main.rs | 1 - src/tools/rust-analyzer/lib/la-arena/src/map.rs | 2 -- src/tools/rust-analyzer/lib/text-size/src/serde_impls.rs | 1 - 12 files changed, 2 insertions(+), 21 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/dyn_map.rs b/src/tools/rust-analyzer/crates/hir-def/src/dyn_map.rs index c38ceccd1fc09..92400d0715395 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/dyn_map.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/dyn_map.rs @@ -119,10 +119,6 @@ pub struct Key { } impl Key { - #[allow( - clippy::new_without_default, - reason = "this a const fn, so it can't be default yet. See " - )] pub(crate) const fn new() -> Key { Key { _phantom: PhantomData } } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/hir/format_args.rs b/src/tools/rust-analyzer/crates/hir-def/src/hir/format_args.rs index 366857f233168..855b16bc28673 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/hir/format_args.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/hir/format_args.rs @@ -169,7 +169,6 @@ enum PositionUsedAs { } use PositionUsedAs::*; -#[allow(clippy::unnecessary_lazy_evaluations)] pub(crate) fn parse( s: &ast::String, string_ptr: AstPtr, diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs b/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs index 8781358822064..2fda00d6af8d0 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs @@ -343,7 +343,6 @@ pub(crate) fn reverse_fixups(tt: &mut TopSubtree, undo_info: &SyntaxFixupUndoInf let top_subtree = tt.top_subtree(); let open_span = top_subtree.delimiter.open; let close_span = top_subtree.delimiter.close; - #[allow(deprecated)] if never!( close_span.anchor.ast_id == FIXUP_DUMMY_AST_ID || open_span.anchor.ast_id == FIXUP_DUMMY_AST_ID diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests/intrinsics.rs b/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests/intrinsics.rs index 898d9ea8edf3d..1dad8dad73192 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests/intrinsics.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests/intrinsics.rs @@ -469,7 +469,7 @@ fn floating_point() { IsSigned::Yes, )), ); - #[allow(unknown_lints, clippy::unnecessary_min_or_max)] + #[allow(clippy::unnecessary_min_or_max)] check_number( r#" #[rustc_intrinsic] 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 0ab147096a749..f93c5a2ebbff1 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs @@ -109,12 +109,7 @@ use crate::{ utils::TargetFeatureIsSafeInTarget, }; -// This lint has a false positive here. See the link below for details. -// -// https://github.com/rust-lang/rust/issues/57411 -#[allow(unreachable_pub)] pub use coerce::could_coerce; -#[allow(unreachable_pub)] pub use unify::{could_unify, could_unify_deeply}; use cast::{CastCheck, CastError}; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs index d6db51dec52b9..32866c12e23e1 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs @@ -309,7 +309,6 @@ const STACK_OFFSET: usize = 1 << 30; const HEAP_OFFSET: usize = 1 << 29; impl Address { - #[allow(clippy::double_parens)] fn from_bytes<'db>(it: &[u8]) -> Result<'db, Self> { Ok(Address::from_usize(from_bytes!(usize, it))) } @@ -2093,7 +2092,6 @@ impl<'a, 'db> Evaluator<'a, 'db> { } } - #[allow(clippy::double_parens)] fn allocate_const_in_heap( &mut self, locals: &Locals<'a, 'db>, diff --git a/src/tools/rust-analyzer/crates/ide-ssr/src/matching.rs b/src/tools/rust-analyzer/crates/ide-ssr/src/matching.rs index ab5a0f70f5a69..c87a10d8e85b7 100644 --- a/src/tools/rust-analyzer/crates/ide-ssr/src/matching.rs +++ b/src/tools/rust-analyzer/crates/ide-ssr/src/matching.rs @@ -315,7 +315,6 @@ impl<'db, 'sema> Matcher<'db, 'sema> { Ok(()) } - #[allow(clippy::only_used_in_recursion)] fn check_constraint( &self, constraint: &Constraint, diff --git a/src/tools/rust-analyzer/crates/ide/src/hover.rs b/src/tools/rust-analyzer/crates/ide/src/hover.rs index 92473de4e634f..6f5878071aaa8 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover.rs @@ -156,7 +156,6 @@ pub(crate) fn hover( Some(res) } -#[allow(clippy::field_reassign_with_default)] fn hover_offset( sema: &Semantics<'_, RootDatabase>, FilePosition { file_id, offset }: FilePosition, diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs index 7fc04a05155f8..c3255b7c86d6c 100644 --- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs +++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs @@ -10,7 +10,7 @@ #![cfg(feature = "in-rust-tree")] #![feature(proc_macro_internals, proc_macro_diagnostic, proc_macro_span, rustc_private)] -#![expect(internal_features, clippy::disallowed_types, clippy::print_stderr)] +#![expect(internal_features)] #![allow(unused_features, unused_crate_dependencies)] #![deny(deprecated_safe, clippy::undocumented_unsafe_blocks)] #![cfg_attr(test, expect(unreachable_pub))] diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs index 56629f79ea216..4199cb29e66d2 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/tests/slow-tests/main.rs @@ -8,7 +8,6 @@ //! specific JSON shapes here -- there's little value in such tests, as we can't //! be sure without a real client anyway. -#![allow(clippy::disallowed_types)] #![cfg_attr(feature = "in-rust-tree", feature(rustc_private))] #[cfg(feature = "in-rust-tree")] diff --git a/src/tools/rust-analyzer/lib/la-arena/src/map.rs b/src/tools/rust-analyzer/lib/la-arena/src/map.rs index 6e7528c4f7fd4..5aa36e7ae74ac 100644 --- a/src/tools/rust-analyzer/lib/la-arena/src/map.rs +++ b/src/tools/rust-analyzer/lib/la-arena/src/map.rs @@ -252,8 +252,6 @@ where { /// Ensures a value is in the entry by inserting the default value if empty, and returns a mutable reference /// to the value in the entry. - // BUG this clippy lint is a false positive - #[allow(clippy::unwrap_or_default)] pub fn or_default(self) -> &'a mut V { self.or_insert_with(Default::default) } diff --git a/src/tools/rust-analyzer/lib/text-size/src/serde_impls.rs b/src/tools/rust-analyzer/lib/text-size/src/serde_impls.rs index 4cd41618c2d7a..6b555d2a99c1b 100644 --- a/src/tools/rust-analyzer/lib/text-size/src/serde_impls.rs +++ b/src/tools/rust-analyzer/lib/text-size/src/serde_impls.rs @@ -31,7 +31,6 @@ impl Serialize for TextRange { } impl<'de> Deserialize<'de> for TextRange { - #[allow(clippy::nonminimal_bool)] fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, From 1aa9a9e22ccfba52b79d64af249050af89762ab3 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Tue, 8 Sep 2026 04:51:31 +0300 Subject: [PATCH 06/22] Fix `hir::Type` owner mismatches between anon consts By not giving anon consts their own owner. This eases work and does not cause harm. I had to revert making `TypeOwnerId` lifetime'd unfortunately but it was not hard (so it won't be hard to put it back when needed). --- .../crates/hir/src/diagnostics.rs | 14 +++-- src/tools/rust-analyzer/crates/hir/src/lib.rs | 63 ++++++++----------- .../crates/hir/src/source_analyzer.rs | 2 +- .../src/handlers/type_mismatch.rs | 21 +++++++ 4 files changed, 57 insertions(+), 43 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs b/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs index 5c3b628f386d5..a1ff564edcf56 100644 --- a/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs @@ -1175,7 +1175,11 @@ impl<'a, 'db> DiagnosticsCollector<'a, 'db> { } fn collect_anon_const(&mut self, source_map: &ExpressionStoreSourceMap, def: AnonConstId<'db>) { - self.emit_inference_errors(def.into(), source_map, def.into()); + self.emit_inference_errors( + def.into(), + source_map, + TypeOwnerId::from_anon_const(def, self.db), + ); } fn collect_enum(&mut self, def: EnumId) { @@ -1232,7 +1236,7 @@ impl<'a, 'db> DiagnosticsCollector<'a, 'db> { } } - fn collect_def_with_body(&mut self, def: DefWithBodyId, type_owner: TypeOwnerId<'db>) { + fn collect_def_with_body(&mut self, def: DefWithBodyId, type_owner: TypeOwnerId) { let (body, source_map) = Body::with_source_map(self.db, def); self.collect_expr_store(body, source_map); @@ -1284,7 +1288,7 @@ impl<'a, 'db> DiagnosticsCollector<'a, 'db> { &mut self, def: InferBodyId<'db>, source_map: &ExpressionStoreSourceMap, - type_owner: TypeOwnerId<'db>, + type_owner: TypeOwnerId, ) { let infer = InferenceResult::of(self.db, def); @@ -1548,7 +1552,7 @@ impl<'db> AnyDiagnostic<'db> { edition: Edition, d: &'db InferenceDiagnostic, source_map: &ExpressionStoreSourceMap, - type_owner: TypeOwnerId<'db>, + type_owner: TypeOwnerId, ) -> Option> { let expr_syntax = |expr| Self::expr_syntax(expr, source_map); let pat_syntax = |pat| Self::pat_syntax(pat, source_map); @@ -1918,7 +1922,7 @@ impl<'db> AnyDiagnostic<'db> { db: &'db dyn HirDatabase, d: &'db SolverDiagnosticKind, span: SpanSyntax, - type_owner: TypeOwnerId<'db>, + type_owner: TypeOwnerId, ) -> Option> { let interner = DbInterner::new_no_crate(db); Some(match d { diff --git a/src/tools/rust-analyzer/crates/hir/src/lib.rs b/src/tools/rust-analyzer/crates/hir/src/lib.rs index 34b9ede4985a6..6dd85ebc8814a 100644 --- a/src/tools/rust-analyzer/crates/hir/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir/src/lib.rs @@ -1299,7 +1299,7 @@ impl<'db> AnonConst<'db> { pub fn ty(self, db: &'db dyn HirDatabase) -> Type<'db> { let loc = self.id.loc(db); - Type { owner: self.id.into(), ty: loc.ty.get() } + Type { owner: TypeOwnerId::from_anon_const(self.id, db), ty: loc.ty.get() } } pub fn eval( @@ -1550,7 +1550,7 @@ impl Function { } } - fn fn_sig<'db>(self, db: &'db dyn HirDatabase) -> (TypeOwnerId<'db>, PolyFnSig<'db>) { + fn fn_sig<'db>(self, db: &'db dyn HirDatabase) -> (TypeOwnerId, PolyFnSig<'db>) { let fn_ptr = self.fn_ptr_type(db); let TyKind::FnPtr(sig_tys, hdr) = fn_ptr.ty.skip_binder().kind() else { unreachable!(); @@ -1558,7 +1558,7 @@ impl Function { (fn_ptr.owner, sig_tys.with(hdr)) } - fn erased_fn_sig<'db>(self, db: &'db dyn HirDatabase) -> (TypeOwnerId<'db>, FnSig<'db>) { + fn erased_fn_sig<'db>(self, db: &'db dyn HirDatabase) -> (TypeOwnerId, FnSig<'db>) { let (owner, sig) = self.fn_sig(db); let sig = DbInterner::new_no_crate(db).instantiate_bound_regions_with_erased(sig); (owner, sig) @@ -3124,21 +3124,17 @@ impl GenericDef { // We cannot call this `Substitution` unfortunately... #[derive(Debug)] pub struct GenericSubstitution<'db> { - owner: TypeOwnerId<'db>, + owner: TypeOwnerId, def: GenericDefId, subst: GenericArgs<'db>, } impl<'db> GenericSubstitution<'db> { - fn new(def: GenericDefId, subst: GenericArgs<'db>, owner: TypeOwnerId<'db>) -> Self { + fn new(def: GenericDefId, subst: GenericArgs<'db>, owner: TypeOwnerId) -> Self { Self { owner, def, subst } } - fn new_from_fn( - def: Function, - subst: GenericArgs<'db>, - owner: TypeOwnerId<'db>, - ) -> Option { + fn new_from_fn(def: Function, subst: GenericArgs<'db>, owner: TypeOwnerId) -> Option { match def.id { AnyFunctionId::FunctionId(def) => Some(Self::new(def.into(), subst, owner)), AnyFunctionId::BuiltinDeriveImplMethod { .. } => None, @@ -3965,7 +3961,7 @@ impl Impl { #[derive(Clone, PartialEq, Eq, Debug, Hash)] pub struct TraitRef<'db> { - owner: TypeOwnerId<'db>, + owner: TypeOwnerId, trait_ref: hir_ty::next_solver::TraitRef<'db>, } @@ -4003,7 +3999,7 @@ enum AnyClosureId<'db> { #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Closure<'db> { - owner: TypeOwnerId<'db>, + owner: TypeOwnerId, id: AnyClosureId<'db>, subst: GenericArgs<'db>, } @@ -4347,23 +4343,26 @@ impl CaptureUsageSource { } #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] -enum TypeOwnerId<'db> { +enum TypeOwnerId { GenericDefId(GenericDefId), BuiltinDeriveImplId(BuiltinDeriveImplId), - AnonConstId(AnonConstId<'db>), // FIXME: What do when we unify two different crates? Currently we just randomly keep one. NoParams(base_db::Crate), } impl_from!( - impl<'db> GenericDefId, - BuiltinDeriveImplId, - AnonConstId<'db> - for TypeOwnerId<'db> + BuiltinDeriveImplId + for TypeOwnerId ); -impl TypeOwnerId<'_> { +impl TypeOwnerId { + /// We associated anon consts with their parent, because they can never have generics of their own. + /// It can have *less* than the parent, but providing more generic args is not a problem. + fn from_anon_const<'db>(id: AnonConstId<'db>, db: &'db dyn HirDatabase) -> TypeOwnerId { + TypeOwnerId::GenericDefId(id.loc(db).owner.generic_def(db)) + } + fn unify(self, other: Self) -> Option { match (self, other) { (TypeOwnerId::NoParams(_), owner) => Some(owner), @@ -4394,7 +4393,7 @@ impl TypeOwnerId<'_> { } let self_def = match self { TypeOwnerId::GenericDefId(def) => def, - TypeOwnerId::BuiltinDeriveImplId(_) | TypeOwnerId::AnonConstId(_) => return false, + TypeOwnerId::BuiltinDeriveImplId(_) => return false, TypeOwnerId::NoParams(_) => return true, }; let self_def = match self_def { @@ -4408,9 +4407,7 @@ impl TypeOwnerId<'_> { }; let rebase_into_def = match rebase_into { TypeOwnerId::GenericDefId(def) => def, - TypeOwnerId::BuiltinDeriveImplId(_) - | TypeOwnerId::AnonConstId(_) - | TypeOwnerId::NoParams(_) => return false, + TypeOwnerId::BuiltinDeriveImplId(_) | TypeOwnerId::NoParams(_) => return false, }; let rebase_into_parent = match rebase_into_def { GenericDefId::ConstId(def) => def.loc(db).container, @@ -4429,7 +4426,7 @@ impl TypeOwnerId<'_> { /// with types of different origins will cause errors or panics. Instead, use the `instantiate` methods. #[derive(Clone, Debug)] pub struct Type<'db> { - owner: TypeOwnerId<'db>, + owner: TypeOwnerId, ty: EarlyBinder<'db, Ty<'db>>, } @@ -4513,7 +4510,6 @@ impl<'db> Type<'db> { TypeOwnerId::BuiltinDeriveImplId(def) => { GenericArgs::error_for_item(interner, def.into()) } - TypeOwnerId::AnonConstId(def) => GenericArgs::error_for_item(interner, def.into()), TypeOwnerId::NoParams(_) => GenericArgs::empty(interner), }; Type::no_params(krate, self.ty.instantiate(interner, args).skip_norm_wip()) @@ -4527,7 +4523,6 @@ impl<'db> Type<'db> { TypeOwnerId::BuiltinDeriveImplId(def) => { generic_args_from_tys(interner, def.into(), args) } - TypeOwnerId::AnonConstId(def) => generic_args_from_tys(interner, def.into(), args), TypeOwnerId::NoParams(krate) => { (GenericArgs::empty(interner), TypeOwnerId::NoParams(krate)) } @@ -4546,7 +4541,6 @@ impl<'db> Type<'db> { let owner = match ty.owner { TypeOwnerId::GenericDefId(def) => def.into(), TypeOwnerId::BuiltinDeriveImplId(def) => def.into(), - TypeOwnerId::AnonConstId(def) => def.into(), TypeOwnerId::NoParams(_) => return ty.ty.skip_binder(), }; let args = GenericArgs::for_item(infcx.interner, owner, |_, param, _, _| { @@ -4624,7 +4618,7 @@ impl<'db> Type<'db> { tys: impl IntoIterator>>, ) -> Self { let interner = DbInterner::new_no_crate(db); - let mut owner = None::>; + let mut owner = None::; let ty = EarlyBinder::bind(Ty::new_tup_from_iter( interner, tys.into_iter().map(|ty| { @@ -4840,7 +4834,6 @@ impl<'db> Type<'db> { TypeOwnerId::BuiltinDeriveImplId(def) => { hir_def::HasModule::krate(&def.loc(db).adt, db) } - TypeOwnerId::AnonConstId(def) => hir_def::HasModule::krate(&def, db), TypeOwnerId::NoParams(krate) => krate, } } @@ -4856,10 +4849,6 @@ impl<'db> Type<'db> { param_env: hir_ty::builtin_derive::param_env(interner, def), krate, }, - TypeOwnerId::AnonConstId(def) => ParamEnvAndCrate { - param_env: db.trait_environment(def.loc(db).owner.generic_def(db)), - krate, - }, TypeOwnerId::NoParams(_) => { ParamEnvAndCrate { param_env: ParamEnv::empty(interner), krate } } @@ -5681,7 +5670,7 @@ impl<'db> Type<'db> { pub fn walk(&self, db: &'db dyn HirDatabase, callback: impl FnMut(Type<'db>)) { struct Visitor<'db, F> { db: &'db dyn HirDatabase, - owner: TypeOwnerId<'db>, + owner: TypeOwnerId, callback: F, visited: FxHashSet>, } @@ -6144,7 +6133,7 @@ pub enum PredicatePolarity { #[derive(Debug, Clone, PartialEq, Eq)] pub struct TraitPredicate<'db> { inner: hir_ty::next_solver::TraitPredicate<'db>, - owner: TypeOwnerId<'db>, + owner: TypeOwnerId, } impl<'db> TraitPredicate<'db> { @@ -6549,8 +6538,8 @@ fn generic_args_from_tys<'db>( interner: DbInterner<'db>, def_id: SolverDefId<'db>, args: impl IntoIterator>>, -) -> (GenericArgs<'db>, TypeOwnerId<'db>) { - let mut owner = None::>; +) -> (GenericArgs<'db>, TypeOwnerId) { + let mut owner = None::; let mut args = args.into_iter(); let args = GenericArgs::for_item(interner, def_id, |_, id, _, _| { if matches!(id, GenericParamId::TypeParamId(_)) 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 907193fe1d7ff..021b89677d283 100644 --- a/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs +++ b/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs @@ -78,7 +78,7 @@ pub(crate) struct SourceAnalyzer<'db> { pub(crate) file_id: HirFileId, pub(crate) resolver: Resolver<'db>, pub(crate) body_or_sig: Option>, - pub(crate) type_owner: TypeOwnerId<'db>, + pub(crate) type_owner: TypeOwnerId, pub(crate) infer_body: Option>, } diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs index f70795ed29bde..1e92a795e47a9 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs @@ -2019,4 +2019,25 @@ fn test(_: Result) { "#, ); } + + #[test] + fn regression_23313() { + check_diagnostics( + r#" +fn hello_world() {} + +struct Wrapper ()>; + +impl ()> Wrapper<{ + Wrapper::<{hello_world}>::call(); + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: no such associated item + }> { +//^ 💡 error: expected fn(), found () + fn hello_world() { + F(); + } +} + "#, + ); + } } From 0de0415a3747f6df951c3c5d23df86e897bde813 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Tue, 8 Sep 2026 04:53:16 +0300 Subject: [PATCH 07/22] Remove some unused code --- .../crates/hir-ty/src/diagnostics.rs | 5 +---- .../hir-ty/src/diagnostics/unsafe_check.rs | 19 ------------------- 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics.rs b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics.rs index 047a348fb09a7..17ec211bb579c 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics.rs @@ -9,8 +9,5 @@ pub use crate::diagnostics::{ expr::{ BodyValidationDiagnostic, record_literal_missing_fields, record_pattern_missing_fields, }, - unsafe_check::{ - InsideUnsafeBlock, UnsafetyReason, missing_unsafe, unsafe_operations, - unsafe_operations_for_body, - }, + unsafe_check::{InsideUnsafeBlock, UnsafetyReason, missing_unsafe, unsafe_operations}, }; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/unsafe_check.rs b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/unsafe_check.rs index ca843c8690f28..bc2e21d0e16e4 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/unsafe_check.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/unsafe_check.rs @@ -101,25 +101,6 @@ enum UnsafeDiagnostic { DeprecatedSafe2024 { node: ExprId, inside_unsafe_block: InsideUnsafeBlock }, } -pub fn unsafe_operations_for_body( - db: &dyn HirDatabase, - infer: &InferenceResult<'_>, - def: DefWithBodyId, - body: &Body, - callback: &mut dyn FnMut(ExprOrPatId), -) { - let mut visitor_callback = |diag| { - if let UnsafeDiagnostic::UnsafeOperation { node, .. } = diag { - callback(node); - } - }; - let mut visitor = UnsafeVisitor::new(db, infer, body, def.into(), &mut visitor_callback); - visitor.walk_expr(body.root_expr()); - for param in &body.params { - visitor.walk_pat(param.formal); - } -} - pub fn unsafe_operations( db: &dyn HirDatabase, infer: &InferenceResult<'_>, From a57cd314e0f6c61386d98a5989439800e766ed69 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Tue, 8 Sep 2026 07:30:12 +0300 Subject: [PATCH 08/22] Do not fill the body for `Drop::drop()` and `#[rustc_must_implement_one_of]` When invoking the "Add missing impl members" assist. --- .../src/handlers/add_missing_impl_members.rs | 108 +++++++++++++++++- .../ide-assists/src/handlers/generate_impl.rs | 1 + .../replace_derive_with_manual_impl.rs | 1 + .../crates/ide-assists/src/utils.rs | 6 +- .../crates/test-utils/src/minicore.rs | 6 +- 5 files changed, 117 insertions(+), 5 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs index 9f9bb1d131548..ebbde8ce3c37d 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs @@ -161,6 +161,7 @@ fn add_missing_impl_members_inner( trait_, &impl_def, &target_scope, + mode, ); let Some((first_new_item, other_items)) = new_item.split_first() else { @@ -2747,7 +2748,9 @@ pub trait Read { } impl Read for () { - $0fn read_buf() {} + fn read_buf() { + ${0:todo!()} + } } "#, ); @@ -2887,7 +2890,9 @@ pub trait Read { } impl Read for () { - $0fn read() {} + fn read() { + ${0:todo!()} + } } "#, ); @@ -2918,7 +2923,104 @@ pub trait Read { } impl Read for () { - $0fn read_buf() {} + fn read_buf() { + ${0:todo!()} + } +} + "#, + ); + } + + #[test] + fn required_method_with_body() { + check_assist( + add_missing_impl_members, + r#" +//- minicore: drop, pin +struct Foo; + +impl Drop for Foo { + $0 +} + "#, + r#" +struct Foo; + +impl Drop for Foo { + fn drop(&mut self) { + ${0:todo!()} + } +} + "#, + ); + + check_assist( + add_missing_impl_members, + r#" +#[rustc_must_implement_one_of(read_buf, read)] +pub trait Read { + fn read() { + Self::read_buf() + } + fn read_buf() { + Self::read(); + } +} + +impl Read for () { + $0 +} + "#, + r#" +#[rustc_must_implement_one_of(read_buf, read)] +pub trait Read { + fn read() { + Self::read_buf() + } + fn read_buf() { + Self::read(); + } +} + +impl Read for () { + fn read_buf() { + ${0:todo!()} + } +} + "#, + ); + check_assist( + add_missing_default_members, + r#" +#[rustc_must_implement_one_of(read_buf, read)] +pub trait Read { + fn read() { + Self::read_buf() + } + fn read_buf() { + Self::read(); + } +} + +impl Read for () { + $0 +} + "#, + r#" +#[rustc_must_implement_one_of(read_buf, read)] +pub trait Read { + fn read() { + Self::read_buf() + } + fn read_buf() { + Self::read(); + } +} + +impl Read for () { + $0fn read() { + Self::read_buf() + } } "#, ); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs index d88a94e2f7307..5813a92427cb1 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs @@ -205,6 +205,7 @@ pub(crate) fn generate_impl_trait(acc: &mut Assists, ctx: &AssistContext<'_, '_> hir_trait, &impl_, &target_scope, + DefaultMethods::No, ); let assoc_item_list = make.assoc_item_list(assoc_items); make_impl_(Some(assoc_item_list)) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs index 8648a013438e7..1ca291c5775a7 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs @@ -252,6 +252,7 @@ fn impl_def_from_trait( trait_, &impl_def, &target_scope, + DefaultMethods::No, ); let assoc_item_list = if let Some((first, other)) = assoc_items.split_first() { let first_item = if let ast::AssocItem::Fn(func) = first diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs index e5e735faf6f93..68e7544e7d76b 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs @@ -204,6 +204,7 @@ pub fn add_trait_assoc_items_to_impl( trait_: hir::Trait, impl_: &ast::Impl, target_scope: &hir::SemanticsScope<'_>, + default_mode: DefaultMethods, ) -> Vec { let new_indent_level = IndentLevel::from_node(impl_.syntax()) + 1; original_items @@ -240,7 +241,10 @@ pub fn add_trait_assoc_items_to_impl( ast::AssocItem::cast(editor.finish().new_root().clone()).unwrap() }) .filter_map(|item| match item { - ast::AssocItem::Fn(fn_) if fn_.body().is_none() => { + // We can check `fn_.body().is_none()`, but this is actually not what we want to check: some functions (`Drop::drop()` + // or `#[rustc_must_implement_one_of]`) have a default body that should be ignored. So the criteria is whether + // we requested required or defaulted methods, and not whether the method actually has a body. + ast::AssocItem::Fn(fn_) if default_mode == DefaultMethods::No => { let (fn_editor, fn_) = SyntaxEditor::with_ast_node(&fn_); let fill_expr: ast::Expr = match config.expr_fill_default { ExprFillDefaultMode::Todo | ExprFillDefaultMode::Default => make.expr_todo(), diff --git a/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs b/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs index 0d9bb4f92bdc0..129e63d3988b5 100644 --- a/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs +++ b/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs @@ -670,7 +670,11 @@ pub mod ops { // region:drop #[lang = "drop"] pub trait Drop { - fn drop(&mut self); + fn drop(&mut self) { + // region:pin + Drop::pin_drop(crate::pin::Pin::new(self)) + // endregion:pin + } // region:pin fn pin_drop(self: crate::pin::Pin<&mut Self>) {} From 9fdf3cd2d59ee5090b6744f747ee0a0f8f8372b3 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Tue, 8 Sep 2026 22:19:21 +0300 Subject: [PATCH 09/22] Do not run the "Generate lints and feature flags" CI workflow on forks Because it's hugely annoying. --- src/tools/rust-analyzer/.github/workflows/gen-lints.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tools/rust-analyzer/.github/workflows/gen-lints.yml b/src/tools/rust-analyzer/.github/workflows/gen-lints.yml index c978e3571c40d..05dfdc2fc4534 100644 --- a/src/tools/rust-analyzer/.github/workflows/gen-lints.yml +++ b/src/tools/rust-analyzer/.github/workflows/gen-lints.yml @@ -11,6 +11,7 @@ defaults: jobs: lints-gen: + if: ${{ github.repository == 'rust-lang/rust-analyzer' || github.event_name == 'workflow_dispatch' }} name: Generate lints runs-on: ubuntu-latest permissions: From 1793d1e8c4732751593c007f533470744e2a1fa9 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Tue, 8 Sep 2026 23:56:53 +0300 Subject: [PATCH 10/22] Stop at eager macro recursion overflow We tracked the depth perfectly but didn't do anything with it. Also fix the depth tracking for expression stores (it didn't intern the new ID with an incremented depth). --- .../crates/hir-def/src/attrs/docs.rs | 1 + .../crates/hir-def/src/expr_store/expander.rs | 3 +- .../rust-analyzer/crates/hir-def/src/lib.rs | 2 ++ .../macro_expansion_tests/builtin_fn_macro.rs | 31 +++++++++++++++++++ .../crates/hir-def/src/nameres/assoc.rs | 1 + .../crates/hir-def/src/nameres/collector.rs | 3 ++ .../crates/hir-expand/src/eager.rs | 17 ++++++++-- 7 files changed, 55 insertions(+), 3 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/attrs/docs.rs b/src/tools/rust-analyzer/crates/hir-def/src/attrs/docs.rs index ac8ca70ccc4b9..583bde92679dd 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/attrs/docs.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/attrs/docs.rs @@ -608,6 +608,7 @@ fn expand_doc_macro_call<'db>( ExpandTo::Expr, expander.krate, expander.macro_depth + 1, + expander.recursion_limit, |path| { expander.resolver.resolve_path_as_macro_def(expander.db, path, Some(MacroSubNs::Bang)) }, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/expander.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/expander.rs index a974815cc6423..12a2263697908 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/expander.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/expander.rs @@ -111,7 +111,8 @@ impl<'db> Expander<'db> { call_site.ctx, expands_to, krate, - this.macro_depth, + this.macro_depth + 1, + this.recursion_limit, |path| resolver(path).map(|it| it.definition(db)), eager_callback, ) { diff --git a/src/tools/rust-analyzer/crates/hir-def/src/lib.rs b/src/tools/rust-analyzer/crates/hir-def/src/lib.rs index 0712a025b49cb..77d00072fd2df 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/lib.rs @@ -1442,6 +1442,7 @@ pub fn macro_call_as_call_id( expand_to: ExpandTo, krate: Crate, macro_depth: u32, + recursion_limit: u32, resolver: impl Fn(&ModPath) -> Option + Copy, eager_callback: &mut dyn FnMut( InFile<(syntax::AstPtr, span::FileAstId)>, @@ -1459,6 +1460,7 @@ pub fn macro_call_as_call_id( def, call_site, macro_depth, + recursion_limit, &|path| resolver(path).filter(MacroDefId::is_fn_like), eager_callback, ), diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs index 7120980dd30cf..1ba636ee901c5 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs @@ -628,3 +628,34 @@ const _: bool = foo::<(), fn() -> Foo>(1, ); "#]], ); } + +#[test] +fn eager_recursion_limit() { + check( + r#" +//- minicore: concat + +macro_rules! concat_separator { + () => { + concat!("", concat_separator!()) + }; +} + +fn main() { + concat_separator!() +} + "#, + expect![[r#" + +macro_rules! concat_separator { + () => { + concat!("", concat_separator!()) + }; +} + +fn main() { + concat!("", concat_separator!()) +} + "#]], + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/assoc.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/assoc.rs index 8dea9a4eda1da..0e27d74516007 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/assoc.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/assoc.rs @@ -323,6 +323,7 @@ impl<'db> AssocItemCollector<'db> { ExpandTo::Items, self.module_id.krate(self.db), self.macro_depth + 1, + self.def_map.recursion_limit(), resolver, &mut |ptr, call_id| { self.macro_calls.push((ptr.map(|(_, it)| it.upcast()), call_id)) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs index ce359ebda6af2..e40e4d99bdbd0 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs @@ -1360,6 +1360,7 @@ impl<'db> DefCollector<'db> { *expand_to, self.def_map.krate, directive.depth, + self.def_map.recursion_limit(), resolver_def_id, &mut |ptr, call_id| { eager_callback_buffer.push((directive.module_id, ptr, call_id)); @@ -1793,6 +1794,7 @@ impl<'db> DefCollector<'db> { *expand_to, self.def_map.krate, directive.depth, + self.def_map.recursion_limit(), |path| { let resolved_res = self.def_map.resolve_path_fp_with_macro( self.crate_local_def_map.unwrap_or(&self.local_def_map), @@ -2698,6 +2700,7 @@ impl ModCollector<'_, '_> { expand_to, self.def_collector.def_map.krate, self.macro_depth + 1, + self.def_collector.def_map.recursion_limit(), |path| { path.as_ident().and_then(|name| { let def_map = &self.def_collector.def_map; diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/eager.rs b/src/tools/rust-analyzer/crates/hir-expand/src/eager.rs index 7002b70a7687b..e0058b78d9e53 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/eager.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/eager.rs @@ -26,8 +26,8 @@ use syntax::{ use syntax_bridge::DocCommentDesugarMode; use crate::{ - AstId, EagerCallInfo, ExpandError, ExpandResult, ExpandTo, ExpansionSpanMap, InFile, - MacroCallId, MacroCallKind, MacroCallLoc, MacroDefId, MacroDefKind, + AstId, EagerCallInfo, ExpandError, ExpandErrorKind, ExpandResult, ExpandTo, ExpansionSpanMap, + InFile, MacroCallId, MacroCallKind, MacroCallLoc, MacroDefId, MacroDefKind, ast::{self, AstNode}, mod_path::ModPath, }; @@ -45,6 +45,7 @@ pub fn expand_eager_macro_input( def: MacroDefId, call_site: SyntaxContext, macro_depth: u32, + recursion_limit: u32, resolver: &dyn Fn(&ModPath) -> Option, eager_callback: EagerCallBackFn<'_>, ) -> ExpandResult> { @@ -79,6 +80,7 @@ pub fn expand_eager_macro_input( krate, call_site, macro_depth, + recursion_limit, resolver, eager_callback, ) @@ -155,6 +157,7 @@ fn eager_macro_recur( krate: Crate, call_site: SyntaxContext, macro_depth: u32, + recursion_limit: u32, macro_resolver: &dyn Fn(&ModPath) -> Option, eager_callback: EagerCallBackFn<'_>, ) -> ExpandResult> { @@ -213,6 +216,14 @@ fn eager_macro_recur( } }; let ast_id = curr.file_id.ast_id_map(db).ast_id(&call); + + if macro_depth > recursion_limit { + return ExpandResult::only_err(ExpandError::new( + span_map.span_at(call.syntax().text_range().start()), + ExpandErrorKind::RecursionOverflow, + )); + } + let ExpandResult { value, err } = match def.kind { MacroDefKind::BuiltInEager(..) => { let ExpandResult { value, err } = expand_eager_macro_input( @@ -223,6 +234,7 @@ fn eager_macro_recur( def, call_site, macro_depth + 1, + recursion_limit, macro_resolver, eager_callback, ); @@ -277,6 +289,7 @@ fn eager_macro_recur( krate, call_site, macro_depth + 1, + recursion_limit, macro_resolver, eager_callback, ); From 77db98fcc6ee06d0ae5eac08e70b73f3363b0ead Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Tue, 11 Aug 2026 16:40:30 +0200 Subject: [PATCH 11/22] use `expect` for lints we actually expect ..and explain the reasoning using `reason`. Sometimes `expect` wouldn't work (for _reasons_), so keep `allow` in those cases. `lsp-extensions.md` didn't need updating, as the changes in `lsp/ext.rs` only touch the `allow` attribute. --- .../rust-analyzer/crates/hir-def/src/expr_store/scope.rs | 5 ++++- src/tools/rust-analyzer/crates/hir-expand/src/eager.rs | 2 +- src/tools/rust-analyzer/crates/hir-expand/src/lib.rs | 2 +- .../crates/hir-ty/src/consteval/tests/intrinsics.rs | 3 ++- src/tools/rust-analyzer/crates/hir-ty/src/traits.rs | 2 +- src/tools/rust-analyzer/crates/ide-db/src/ra_fixture.rs | 6 +++--- .../rust-analyzer/crates/rust-analyzer/src/flycheck.rs | 2 +- .../rust-analyzer/crates/rust-analyzer/src/lsp/ext.rs | 7 ++++++- src/tools/rust-analyzer/crates/stdx/src/rand.rs | 2 +- src/tools/rust-analyzer/crates/toolchain/src/lib.rs | 2 +- .../docs/book/src/contributing/lsp-extensions.md | 2 +- src/tools/rust-analyzer/lib/line-index/src/tests.rs | 5 ++++- 12 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/scope.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/scope.rs index 24baa0f8c8d89..ee43ee5cc47b4 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr_store/scope.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expr_store/scope.rs @@ -115,7 +115,10 @@ impl ExprScopes { } /// If `scope` refers to a macro def scope, returns the corresponding `MacroId`. - #[allow(clippy::borrowed_box)] // If we return `&MacroDefId` we need to move it, this way we just clone the `Box`. + #[expect( + clippy::borrowed_box, + reason = "If we return `&MacroDefId` we need to move it, this way we just clone the `Box`." + )] pub fn macro_def(&self, scope: ScopeId) -> Option<&Box> { match &self.scopes[scope].kind { ScopeKind::MacroDef(macro_def) => Some(macro_def), diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/eager.rs b/src/tools/rust-analyzer/crates/hir-expand/src/eager.rs index 7002b70a7687b..918ccac815601 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/eager.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/eager.rs @@ -62,7 +62,7 @@ pub fn expand_eager_macro_input( macro_depth, }; let arg_id = MacroCallId::new(db, loc); - #[allow(deprecated)] // builtin eager macros are never derives + #[expect(deprecated, reason = "builtin eager macros are never derives")] let (_, _, span) = arg_id.macro_arg(db); let ExpandResult { value: (arg_exp, arg_exp_map), err: parse_err } = arg_id.parse_macro_expansion(db); diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs index e8d61a6eadf63..e646c21ec8c5e 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs @@ -519,7 +519,7 @@ impl MacroCallId { /// This is not connected to the database so it does not cache the result. However, the inner [macro_arg] query is /// /// [macro_arg]: Self::macro_arg - #[allow(deprecated)] // we are macro_arg_considering_derives + #[expect(deprecated, reason = "we are `macro_arg_considering_derives`")] pub fn macro_arg_considering_derives<'db>( self, db: &'db dyn SourceDatabase, diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests/intrinsics.rs b/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests/intrinsics.rs index 1dad8dad73192..ab6294cb375df 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests/intrinsics.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests/intrinsics.rs @@ -469,7 +469,8 @@ fn floating_point() { IsSigned::Yes, )), ); - #[allow(clippy::unnecessary_min_or_max)] + // FIXME: this should be an `expect`, but that currently results in `lint_expectation_unfulfilled` + #[allow(clippy::unnecessary_min_or_max, reason = "for symmetry with the expression in `GOAL`")] check_number( r#" #[rustc_intrinsic] 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 f3aa024399e06..61f0e17cc9984 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/traits.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/traits.rs @@ -391,6 +391,6 @@ pub fn check_orphan_rules<'db>(db: &'db dyn HirDatabase, impl_: ImplId) -> bool } _ => false, }); - #[allow(clippy::let_and_return)] + #[allow(clippy::let_and_return, reason = "the name clarifies the meaning of the boolean")] is_not_orphan } diff --git a/src/tools/rust-analyzer/crates/ide-db/src/ra_fixture.rs b/src/tools/rust-analyzer/crates/ide-db/src/ra_fixture.rs index 09a270c143888..ee61cab4828c5 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/ra_fixture.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/ra_fixture.rs @@ -307,7 +307,7 @@ impl IsEmpty for SmallVec<[T; N]> { } } -#[allow(clippy::disallowed_types)] +#[expect(clippy::disallowed_types, reason = "generic allows for `FxHashMap`")] impl IsEmpty for std::collections::HashMap { fn is_empty(&self) -> bool { self.is_empty() @@ -376,7 +376,7 @@ impl UpmapFromRaFixture for SmallVec<[T; } } -#[allow(clippy::disallowed_types)] +#[expect(clippy::disallowed_types, reason = "generic allows for `FxHashMap`")] impl UpmapFromRaFixture for std::collections::HashMap { @@ -391,7 +391,7 @@ impl UpmapFromRaFixture for std::collections::HashMap { 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 85edb239e3f57..7f1f4ef137054 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/flycheck.rs @@ -490,7 +490,7 @@ impl<'a> Substitutions<'a> { /// /// Same for {saved_file}. /// - #[allow(clippy::disallowed_types)] /* generic parameter allows for FxHashMap */ + #[expect(clippy::disallowed_types, reason = "generic parameter allows for `FxHashMap`")] fn substitute( self, template: &project_json::Runnable, diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/ext.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/ext.rs index 8e0bb285c2318..19e067334e008 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/ext.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/ext.rs @@ -4,7 +4,12 @@ // might strip `false` values from the JSON payload due to their reserialization logic turning false // into null which will then cause them to be omitted in the resolve request. See https://github.com/rust-lang/rust-analyzer/issues/18767 -#![allow(clippy::disallowed_types)] +// FIXME: ideally we'd put this on `SnippetWorkspaceEdit.change_annotations`, but that doesn't work, +// most likely because the lint fires in the impls generated by the derives as well. +#![expect( + clippy::disallowed_types, + reason = "`SnippetWorkspaceEdit.change_annotations` needs to match `lsp_types::WorkspaceEdit.change_annotations` for the `From` impl" +)] use std::ops; diff --git a/src/tools/rust-analyzer/crates/stdx/src/rand.rs b/src/tools/rust-analyzer/crates/stdx/src/rand.rs index e028990900af6..07dfa6ef8877c 100644 --- a/src/tools/rust-analyzer/crates/stdx/src/rand.rs +++ b/src/tools/rust-analyzer/crates/stdx/src/rand.rs @@ -14,6 +14,6 @@ pub fn shuffle(slice: &mut [T], mut rand_index: impl FnMut(usize) -> usize) { pub fn seed() -> u64 { use std::hash::{BuildHasher, Hasher}; - #[allow(clippy::disallowed_types)] + #[expect(clippy::disallowed_types, reason = "we need a source of randomness for the seed")] std::collections::hash_map::RandomState::new().build_hasher().finish() } diff --git a/src/tools/rust-analyzer/crates/toolchain/src/lib.rs b/src/tools/rust-analyzer/crates/toolchain/src/lib.rs index 6bed98f4cd2d7..26883add08699 100644 --- a/src/tools/rust-analyzer/crates/toolchain/src/lib.rs +++ b/src/tools/rust-analyzer/crates/toolchain/src/lib.rs @@ -74,7 +74,7 @@ impl Tool { // Prevent rustup from automatically installing toolchains, see https://github.com/rust-lang/rust-analyzer/issues/20719. pub const NO_RUSTUP_AUTO_INSTALL_ENV: (&str, &str) = ("RUSTUP_AUTO_INSTALL", "0"); -#[allow(clippy::disallowed_types)] /* generic parameter allows for FxHashMap */ +#[expect(clippy::disallowed_types, reason = "generic parameter allows for `FxHashMap`")] pub fn command( cmd: impl AsRef, working_directory: impl AsRef, diff --git a/src/tools/rust-analyzer/docs/book/src/contributing/lsp-extensions.md b/src/tools/rust-analyzer/docs/book/src/contributing/lsp-extensions.md index da4a5aaa686c5..42bdb5dc1055a 100644 --- a/src/tools/rust-analyzer/docs/book/src/contributing/lsp-extensions.md +++ b/src/tools/rust-analyzer/docs/book/src/contributing/lsp-extensions.md @@ -1,5 +1,5 @@