From 5e53d929e4638976cac6b9a3069860af8fe814fc Mon Sep 17 00:00:00 2001 From: byd1 <2156864690@qq.com> Date: Sun, 28 Jun 2026 14:19:13 +0800 Subject: [PATCH 01/24] fix --- compiler/rustc_parse/src/parser/stmt.rs | 20 +++++++++++++++++-- .../ui/let-else/detect-invisible-delimiter.rs | 16 +++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 tests/ui/let-else/detect-invisible-delimiter.rs diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index 5bd2ca3139228..2b63a31a139da 100644 --- a/compiler/rustc_parse/src/parser/stmt.rs +++ b/compiler/rustc_parse/src/parser/stmt.rs @@ -5,6 +5,7 @@ use std::ops::Bound; use ast::Label; use rustc_ast as ast; use rustc_ast::token::{self, Delimiter, InvisibleOrigin, MetaVarKind, TokenKind}; +use rustc_ast::tokenstream::TokenTree; use rustc_ast::util::classify::{self, TrailingBrace}; use rustc_ast::visit::{Visitor, walk_expr}; use rustc_ast::{ @@ -353,6 +354,14 @@ impl<'a> Parser<'a> { } else { (None, None, None) }; + + let init_wrapped = self + .tree_look_ahead(2, |tree| match tree { + TokenTree::Token(tok, _) => tok.is_keyword(kw::Else), + TokenTree::Delimited(..) => false, + }) + .unwrap_or(false); + let init = match (self.parse_initializer(err.is_some()), err) { (Ok(init), None) => { // init parsed, ty parsed @@ -390,6 +399,7 @@ impl<'a> Parser<'a> { return Err(err); } }; + let trailing_token = self.prev_token; let kind = match init { None => LocalKind::Decl, Some(init) => { @@ -401,8 +411,14 @@ impl<'a> Parser<'a> { return Err(self.error_block_no_opening_brace_msg(Cow::from(msg))); } let els = self.parse_block()?; - self.check_let_else_init_bool_expr(&init); - self.check_let_else_init_trailing_brace(&init); + // These checks should also respect invisible delimiter + if !init_wrapped { + self.check_let_else_init_bool_expr(&init); + } + if matches!(trailing_token.kind, TokenKind::CloseBrace) { + self.check_let_else_init_trailing_brace(&init); + } + LocalKind::InitElse(init, els) } else { LocalKind::Init(init) diff --git a/tests/ui/let-else/detect-invisible-delimiter.rs b/tests/ui/let-else/detect-invisible-delimiter.rs new file mode 100644 index 0000000000000..76c231bd21e6d --- /dev/null +++ b/tests/ui/let-else/detect-invisible-delimiter.rs @@ -0,0 +1,16 @@ +// The user shouldn't need to wrap the expression in parentheses(#147899) +//@check-pass +#![allow(irrefutable_let_patterns)] +struct Thing {} +macro_rules! foo { + ($e:expr) => { + let _ = $e else { + return; + }; + }; +} + +fn main() { + foo!(true && true); + foo!(Thing {}); +} From a0b341668d9fddfacbcf1dc66520ce5672d5cff9 Mon Sep 17 00:00:00 2001 From: aisr Date: Wed, 1 Apr 2026 18:00:47 +0800 Subject: [PATCH 02/24] add safety section for mem::zeroed --- library/core/src/mem/mod.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/library/core/src/mem/mod.rs b/library/core/src/mem/mod.rs index 63dcf768de073..a603f7f9aaac0 100644 --- a/library/core/src/mem/mod.rs +++ b/library/core/src/mem/mod.rs @@ -706,16 +706,18 @@ pub const fn needs_drop() -> bool { /// This means that, for example, the padding byte in `(u8, u16)` is not /// necessarily zeroed. /// -/// There is no guarantee that an all-zero byte-pattern represents a valid value -/// of some type `T`. For example, the all-zero byte-pattern is not a valid value -/// for reference types (`&T`, `&mut T`) and function pointers. Using `zeroed` -/// on such types causes immediate [undefined behavior][ub] because [the Rust -/// compiler assumes][inv] that there always is a valid value in a variable it -/// considers initialized. -/// /// This has the same effect as [`MaybeUninit::zeroed().assume_init()`][zeroed]. /// It is useful for FFI sometimes, but should generally be avoided. /// +/// +/// # Safety +/// +/// The all-zero byte-pattern must represent a valid value of type `T`. +/// For example, it is not valid for reference types (`&T`, `&mut T`) or function +/// pointers. Using `zeroed` on such types causes immediate [undefined behavior][ub] +/// because [the Rust compiler assumes][inv] that there always is a valid value in a +/// variable it considers initialized. +/// /// [zeroed]: MaybeUninit::zeroed /// [ub]: ../../reference/behavior-considered-undefined.html /// [inv]: MaybeUninit#initialization-invariant From b78c8acf1e22f904cef325d6fab04a2eab00dc56 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Fri, 21 Aug 2026 06:22:56 +0300 Subject: [PATCH 03/24] Provide a `supertrait_def_ids()` function in rustc_type_ir's interner rust-analyzer has a query for this, so we want to use it there. I don't know if using a query for this will be a perf win for rustc, but rust-analyzer already has this query for other reasons, so it feels a waste to not use it. --- compiler/rustc_middle/src/ty/context/impl_interner.rs | 4 ++++ compiler/rustc_next_trait_solver/src/solve/trait_goals.rs | 3 ++- compiler/rustc_type_ir/src/elaborate.rs | 3 +++ compiler/rustc_type_ir/src/interner.rs | 3 +++ 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_middle/src/ty/context/impl_interner.rs b/compiler/rustc_middle/src/ty/context/impl_interner.rs index 2444f8513b8e4..7f10cb1994ee1 100644 --- a/compiler/rustc_middle/src/ty/context/impl_interner.rs +++ b/compiler/rustc_middle/src/ty/context/impl_interner.rs @@ -430,6 +430,10 @@ impl<'tcx> Interner for TyCtxt<'tcx> { self.impl_super_outlives(impl_def_id) } + fn supertrait_def_ids(self, trait_def_id: DefId) -> impl Iterator { + rustc_type_ir::elaborate::supertrait_def_ids(self, trait_def_id) + } + fn impl_is_const(self, def_id: DefId) -> bool { debug_assert_matches!(self.def_kind(def_id), DefKind::Impl { of_trait: true }); self.is_conditionally_const(def_id) diff --git a/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs b/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs index 335263b1d169d..f9793fb6e417c 100644 --- a/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs +++ b/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs @@ -1109,7 +1109,8 @@ where .auto_traits() .into_iter() .chain(a_data.principal_def_id().into_iter().flat_map(|principal_def_id| { - elaborate::supertrait_def_ids(self.cx(), principal_def_id) + self.cx() + .supertrait_def_ids(principal_def_id) .filter(|def_id| self.cx().trait_is_auto(*def_id)) })) .collect(); diff --git a/compiler/rustc_type_ir/src/elaborate.rs b/compiler/rustc_type_ir/src/elaborate.rs index 912a5ac90f632..2110521eae764 100644 --- a/compiler/rustc_type_ir/src/elaborate.rs +++ b/compiler/rustc_type_ir/src/elaborate.rs @@ -318,6 +318,9 @@ impl> Iterator for Elaborator { /// does not compute the full elaborated super-predicates but just the set of def-ids. It is used /// to identify which traits may define a given associated type to help avoid cycle errors, /// and to make size estimates for vtable layout computation. +/// +/// rust-analyzer has a query for this, so don't use this function there. +#[cfg(feature = "nightly")] pub fn supertrait_def_ids( cx: I, trait_def_id: I::TraitId, diff --git a/compiler/rustc_type_ir/src/interner.rs b/compiler/rustc_type_ir/src/interner.rs index 49899147d5747..5a14b60e1d296 100644 --- a/compiler/rustc_type_ir/src/interner.rs +++ b/compiler/rustc_type_ir/src/interner.rs @@ -359,6 +359,9 @@ pub trait Interner: impl_def_id: Self::ImplId, ) -> ty::EarlyBinder>; + fn supertrait_def_ids(self, trait_def_id: Self::TraitId) + -> impl Iterator; + fn impl_is_const(self, def_id: Self::ImplId) -> bool; fn fn_is_const(self, def_id: Self::FunctionId) -> bool; fn closure_is_const(self, def_id: Self::ClosureId) -> bool; From 940498dad7cb35a753a7cce104ab510b414e84de Mon Sep 17 00:00:00 2001 From: Rachit2323 Date: Mon, 24 Aug 2026 12:25:29 +0530 Subject: [PATCH 04/24] fix const_item_mutation lint to use needs_drop instead of has_dtor --- .../src/check_const_item_mutation.rs | 40 ++++++++++------- tests/ui/lint/lint-const-item-mutation.rs | 10 +++-- tests/ui/lint/lint-const-item-mutation.stderr | 43 +++++++------------ 3 files changed, 47 insertions(+), 46 deletions(-) diff --git a/compiler/rustc_mir_transform/src/check_const_item_mutation.rs b/compiler/rustc_mir_transform/src/check_const_item_mutation.rs index 5b25bdc01117b..e8ff3c3a08b79 100644 --- a/compiler/rustc_mir_transform/src/check_const_item_mutation.rs +++ b/compiler/rustc_mir_transform/src/check_const_item_mutation.rs @@ -2,7 +2,7 @@ use rustc_hir::HirId; use rustc_lint_defs::builtin::CONST_ITEM_MUTATION; use rustc_middle::mir::visit::Visitor; use rustc_middle::mir::*; -use rustc_middle::ty::TyCtxt; +use rustc_middle::ty::{self, TyCtxt, TypeVisitableExt}; use rustc_span::Span; use rustc_span::def_id::DefId; @@ -35,8 +35,9 @@ impl<'tcx> ConstMutationChecker<'_, 'tcx> { fn is_const_item_without_destructor(&self, local: Local) -> Option { let def_id = self.is_const_item(local)?; - // We avoid linting mutation of a const item if the const's type has a - // Drop impl. The Drop logic observes the mutation which was performed. + // We avoid linting mutation of a const item if the const's type needs + // drop. Any drop logic (including that of fields) may observe the + // mutation which was performed. // // pub struct Log { msg: &'static str } // pub const LOG: Log = Log { msg: "" }; @@ -46,21 +47,30 @@ impl<'tcx> ConstMutationChecker<'_, 'tcx> { // // LOG.msg = "wow"; // prints "wow" // + // Likewise, if a field of the const type has its own Drop impl, that + // drop logic may also observe the mutation: + // + // struct Inner { val: u32 } + // impl Drop for Inner { fn drop(&mut self) { println!("{}", self.val); } } + // struct Outer { inner: Inner } + // const O: Outer = Outer { inner: Inner { val: 0 } }; + // + // O.inner.val = 42; // Inner::drop prints "42" + // // FIXME(https://github.com/rust-lang/rust/issues/77425): // Drop this exception once there is a stable attribute to suppress the - // const item mutation lint for a single specific const only. Something - // equivalent to: - // - // #[const_mutation_allowed] - // pub const LOG: Log = Log { msg: "" }; - // FIXME: this should not be checking for `Drop` impls, - // but whether it or any field has a Drop impl (`needs_drop`) - // as fields' Drop impls may make this observable, too. - match self.tcx.type_of(def_id).skip_binder().ty_adt_def().map(|adt| adt.has_dtor(self.tcx)) - { - Some(true) => None, - Some(false) | None => Some(def_id), + // const item mutation lint for a single specific const only. + let ty = self.tcx.type_of(def_id).instantiate_identity().skip_norm_wip(); + // `needs_drop` is overly conservative for types that contain type + // parameters (e.g. `Self` in a trait associated const): it always + // returns `true` because the parameter *might* implement Drop, even + // when the concrete type at the call site does not. In that case we + // cannot suppress the lint, so fall through and warn. + if ty.has_param() { + return Some(def_id); } + let typing_env = ty::TypingEnv::non_body_analysis(self.tcx, def_id); + if ty.needs_drop(self.tcx, typing_env) { None } else { Some(def_id) } } /// If we should lint on this usage, return the [`HirId`], source [`Span`] diff --git a/tests/ui/lint/lint-const-item-mutation.rs b/tests/ui/lint/lint-const-item-mutation.rs index d51d3c394937c..877455e7bb869 100644 --- a/tests/ui/lint/lint-const-item-mutation.rs +++ b/tests/ui/lint/lint-const-item-mutation.rs @@ -18,16 +18,19 @@ impl Drop for Mutable { } } -struct Mutable2 { // this one has drop glue but not a Drop impl +struct Mutable2 { // this one has drop glue but not a direct Drop impl msg: &'static str, other: String, } +struct WithFieldDrop { inner: Mutable } // no Drop on this type, but Mutable has one + const ARRAY: [u8; 1] = [25]; const MY_STRUCT: MyStruct = MyStruct { field: true, inner_array: ['a'], raw_ptr: 2 as *mut u8 }; const RAW_PTR: *mut u8 = 1 as *mut u8; const MUTABLE: Mutable = Mutable { msg: "" }; const MUTABLE2: Mutable2 = Mutable2 { msg: "", other: String::new() }; +const WFD: WithFieldDrop = WithFieldDrop { inner: Mutable { msg: "" } }; const VEC: Vec = Vec::new(); const PTR: *mut () = 1 as *mut _; const PTR_TO_ARRAY: *mut [u32; 4] = 0x12345678 as _; @@ -50,8 +53,9 @@ fn main() { *MY_STRUCT.raw_ptr = 0; } - MUTABLE.msg = "wow"; // no warning, because Drop observes the mutation - MUTABLE2.msg = "wow"; //~ WARN attempting to modify + MUTABLE.msg = "wow"; // no warning — Drop impl observes the mutation + MUTABLE2.msg = "wow"; // no warning — field String has drop glue (needs_drop = true) + WFD.inner.msg = "observed"; // no warning — Mutable's Drop observes the field mutation VEC.push(0); //~ WARN taking a mutable reference to a `const` item // Test that we don't warn when converting a raw pointer diff --git a/tests/ui/lint/lint-const-item-mutation.stderr b/tests/ui/lint/lint-const-item-mutation.stderr index 0e405c306fe46..84f5e78953e60 100644 --- a/tests/ui/lint/lint-const-item-mutation.stderr +++ b/tests/ui/lint/lint-const-item-mutation.stderr @@ -1,45 +1,45 @@ warning: attempting to modify a `const` item - --> $DIR/lint-const-item-mutation.rs:37:5 + --> $DIR/lint-const-item-mutation.rs:40:5 | LL | ARRAY[0] = 5; | ^^^^^^^^^^^^ | = note: each usage of a `const` item creates a new temporary; the original `const` item will not be modified note: `const` item defined here - --> $DIR/lint-const-item-mutation.rs:26:1 + --> $DIR/lint-const-item-mutation.rs:28:1 | LL | const ARRAY: [u8; 1] = [25]; | ^^^^^^^^^^^^^^^^^^^^ = note: `#[warn(const_item_mutation)]` on by default warning: attempting to modify a `const` item - --> $DIR/lint-const-item-mutation.rs:38:5 + --> $DIR/lint-const-item-mutation.rs:41:5 | LL | MY_STRUCT.field = false; | ^^^^^^^^^^^^^^^^^^^^^^^ | = note: each usage of a `const` item creates a new temporary; the original `const` item will not be modified note: `const` item defined here - --> $DIR/lint-const-item-mutation.rs:27:1 + --> $DIR/lint-const-item-mutation.rs:29:1 | LL | const MY_STRUCT: MyStruct = MyStruct { field: true, inner_array: ['a'], raw_ptr: 2 as *mut u8 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^ warning: attempting to modify a `const` item - --> $DIR/lint-const-item-mutation.rs:39:5 + --> $DIR/lint-const-item-mutation.rs:42:5 | LL | MY_STRUCT.inner_array[0] = 'b'; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: each usage of a `const` item creates a new temporary; the original `const` item will not be modified note: `const` item defined here - --> $DIR/lint-const-item-mutation.rs:27:1 + --> $DIR/lint-const-item-mutation.rs:29:1 | LL | const MY_STRUCT: MyStruct = MyStruct { field: true, inner_array: ['a'], raw_ptr: 2 as *mut u8 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^ warning: taking a mutable reference to a `const` item - --> $DIR/lint-const-item-mutation.rs:40:5 + --> $DIR/lint-const-item-mutation.rs:43:5 | LL | MY_STRUCT.use_mut(); | ^^^^^^^^^^^^^^^^^^^ @@ -52,13 +52,13 @@ note: mutable reference created due to call to this method LL | fn use_mut(&mut self) {} | ^^^^^^^^^^^^^^^^^^^^^ note: `const` item defined here - --> $DIR/lint-const-item-mutation.rs:27:1 + --> $DIR/lint-const-item-mutation.rs:29:1 | LL | const MY_STRUCT: MyStruct = MyStruct { field: true, inner_array: ['a'], raw_ptr: 2 as *mut u8 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^ warning: taking a mutable reference to a `const` item - --> $DIR/lint-const-item-mutation.rs:41:5 + --> $DIR/lint-const-item-mutation.rs:44:5 | LL | &mut MY_STRUCT; | ^^^^^^^^^^^^^^ @@ -66,13 +66,13 @@ LL | &mut MY_STRUCT; = note: each usage of a `const` item creates a new temporary = note: the mutable reference will refer to this temporary, not the original `const` item note: `const` item defined here - --> $DIR/lint-const-item-mutation.rs:27:1 + --> $DIR/lint-const-item-mutation.rs:29:1 | LL | const MY_STRUCT: MyStruct = MyStruct { field: true, inner_array: ['a'], raw_ptr: 2 as *mut u8 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^ warning: taking a mutable reference to a `const` item - --> $DIR/lint-const-item-mutation.rs:42:5 + --> $DIR/lint-const-item-mutation.rs:45:5 | LL | (&mut MY_STRUCT).use_mut(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -85,26 +85,13 @@ note: mutable reference created due to call to this method LL | fn use_mut(&mut self) {} | ^^^^^^^^^^^^^^^^^^^^^ note: `const` item defined here - --> $DIR/lint-const-item-mutation.rs:27:1 + --> $DIR/lint-const-item-mutation.rs:29:1 | LL | const MY_STRUCT: MyStruct = MyStruct { field: true, inner_array: ['a'], raw_ptr: 2 as *mut u8 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^ -warning: attempting to modify a `const` item - --> $DIR/lint-const-item-mutation.rs:54:5 - | -LL | MUTABLE2.msg = "wow"; - | ^^^^^^^^^^^^^^^^^^^^ - | - = note: each usage of a `const` item creates a new temporary; the original `const` item will not be modified -note: `const` item defined here - --> $DIR/lint-const-item-mutation.rs:30:1 - | -LL | const MUTABLE2: Mutable2 = Mutable2 { msg: "", other: String::new() }; - | ^^^^^^^^^^^^^^^^^^^^^^^^ - warning: taking a mutable reference to a `const` item - --> $DIR/lint-const-item-mutation.rs:55:5 + --> $DIR/lint-const-item-mutation.rs:59:5 | LL | VEC.push(0); | ^^^^^^^^^^^ @@ -114,10 +101,10 @@ LL | VEC.push(0); note: mutable reference created due to call to this method --> $SRC_DIR/alloc/src/vec/mod.rs:LL:COL note: `const` item defined here - --> $DIR/lint-const-item-mutation.rs:31:1 + --> $DIR/lint-const-item-mutation.rs:34:1 | LL | const VEC: Vec = Vec::new(); | ^^^^^^^^^^^^^^^^^^^ -warning: 8 warnings emitted +warning: 7 warnings emitted From 98a157aec8b08729249aa9ca78f9703aa3a4f279 Mon Sep 17 00:00:00 2001 From: zakrad <49591476+zakrad@users.noreply.github.com> Date: Mon, 31 Aug 2026 13:21:07 +0330 Subject: [PATCH 05/24] Add test for the fn item uniqueness note with late bound lifetimes --- .../fn/fn-item-type-note-late-bound-145558.rs | 19 ++++++++++++++++++ ...fn-item-type-note-late-bound-145558.stderr | 20 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 tests/ui/fn/fn-item-type-note-late-bound-145558.rs create mode 100644 tests/ui/fn/fn-item-type-note-late-bound-145558.stderr diff --git a/tests/ui/fn/fn-item-type-note-late-bound-145558.rs b/tests/ui/fn/fn-item-type-note-late-bound-145558.rs new file mode 100644 index 0000000000000..2463e2ba70675 --- /dev/null +++ b/tests/ui/fn/fn-item-type-note-late-bound-145558.rs @@ -0,0 +1,19 @@ +//! Regression test for https://github.com/rust-lang/rust/issues/145558 +//! +//! The note explaining that distinct fn items have distinct types was suppressed when the +//! signatures contained a late-bound lifetime, because the two binders name their bound +//! region differently. + +//@ dont-require-annotations: NOTE + +struct A; + +fn f1<'a>(_: &'a A) {} +fn f2<'a>(_: &'a A) {} + +fn main() { + let mut map = vec![]; + map.push(f1); + map.push(f2); + //~^ ERROR mismatched types +} diff --git a/tests/ui/fn/fn-item-type-note-late-bound-145558.stderr b/tests/ui/fn/fn-item-type-note-late-bound-145558.stderr new file mode 100644 index 0000000000000..31d773c7e4a79 --- /dev/null +++ b/tests/ui/fn/fn-item-type-note-late-bound-145558.stderr @@ -0,0 +1,20 @@ +error[E0308]: mismatched types + --> $DIR/fn-item-type-note-late-bound-145558.rs:17:14 + | +LL | map.push(f1); + | --- -- this argument has type `for<'a> fn(&'a A) {f1}`... + | | + | ... which causes `map` to have type `Vec fn(&'a A) {f1}>` +LL | map.push(f2); + | ---- ^^ expected fn item, found a different fn item + | | + | arguments to this method are incorrect + | + = note: expected fn item `for<'a> fn(&'a A) {f1}` + found fn item `for<'a> fn(&'a A) {f2}` +note: method defined here + --> $SRC_DIR/alloc/src/vec/mod.rs:LL:COL + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0308`. From 21228c2dfa1a8afda35130d0fd59979ed6c34937 Mon Sep 17 00:00:00 2001 From: zakrad <49591476+zakrad@users.noreply.github.com> Date: Mon, 31 Aug 2026 13:23:02 +0330 Subject: [PATCH 06/24] Do not suppress the fn item uniqueness note for late bound lifetimes --- .../src/error_reporting/infer/suggest.rs | 4 +++- tests/ui/fn/fn-item-type-note-late-bound-145558.rs | 1 + tests/ui/fn/fn-item-type-note-late-bound-145558.stderr | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_trait_selection/src/error_reporting/infer/suggest.rs b/compiler/rustc_trait_selection/src/error_reporting/infer/suggest.rs index db852701051cf..577571196a239 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/suggest.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/suggest.rs @@ -521,7 +521,9 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { let found_sig = self.normalize_fn_sig(self.tcx.fn_sig(*did2).instantiate(self.tcx, args2)); - if self.same_type_modulo_infer(expected_sig, found_sig) { + let expected_sig_anon = self.tcx.anonymize_bound_vars(expected_sig); + let found_sig_anon = self.tcx.anonymize_bound_vars(found_sig); + if self.same_type_modulo_infer(expected_sig_anon, found_sig_anon) { diag.subdiagnostic(FnUniqTypes); } diff --git a/tests/ui/fn/fn-item-type-note-late-bound-145558.rs b/tests/ui/fn/fn-item-type-note-late-bound-145558.rs index 2463e2ba70675..3e8fc0e8b14a7 100644 --- a/tests/ui/fn/fn-item-type-note-late-bound-145558.rs +++ b/tests/ui/fn/fn-item-type-note-late-bound-145558.rs @@ -16,4 +16,5 @@ fn main() { map.push(f1); map.push(f2); //~^ ERROR mismatched types + //~| NOTE different fn items have unique types } diff --git a/tests/ui/fn/fn-item-type-note-late-bound-145558.stderr b/tests/ui/fn/fn-item-type-note-late-bound-145558.stderr index 31d773c7e4a79..bf0a1c3316e57 100644 --- a/tests/ui/fn/fn-item-type-note-late-bound-145558.stderr +++ b/tests/ui/fn/fn-item-type-note-late-bound-145558.stderr @@ -12,6 +12,7 @@ LL | map.push(f2); | = note: expected fn item `for<'a> fn(&'a A) {f1}` found fn item `for<'a> fn(&'a A) {f2}` + = note: different fn items have unique types, even if their signatures are the same note: method defined here --> $SRC_DIR/alloc/src/vec/mod.rs:LL:COL From ee766dc8d03d3f7b9de904333cea9867f58bf9d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Fri, 18 Sep 2026 23:52:25 +0200 Subject: [PATCH 07/24] post GH comment on types nominations --- triagebot.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/triagebot.toml b/triagebot.toml index fc9c43d2dbcae..36d9ee29b6597 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -702,6 +702,7 @@ topic = "#{number}: {title}" message_on_add = """\ @*T-types* issue #{number} "{title}" has been nominated for team discussion. """ +github_comment = ":robot: A [dedicated `#t-types/nominated` topic]({zulip_topic_url}) has been opened for humans to discuss this issue :robot:" message_on_remove = "Issue #{number}'s nomination has been removed. Thanks all for participating!" message_on_close = "Issue #{number} has been closed. Thanks for participating!" message_on_reopen = "Issue #{number} has been reopened. Pinging @*T-types*." From 75b444d1ad334617718f331b210fb88a6ff77c4b Mon Sep 17 00:00:00 2001 From: Shun Sakai Date: Sun, 6 Sep 2026 11:39:02 +0900 Subject: [PATCH 08/24] Constify `impl FromStr for NonZero` --- library/core/src/num/nonzero.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/core/src/num/nonzero.rs b/library/core/src/num/nonzero.rs index 0563c225f7e0b..d0ae6c31267f3 100644 --- a/library/core/src/num/nonzero.rs +++ b/library/core/src/num/nonzero.rs @@ -1412,7 +1412,8 @@ macro_rules! nonzero_integer { } #[stable(feature = "nonzero_parse", since = "1.35.0")] - impl FromStr for NonZero<$Int> { + #[rustc_const_unstable(feature = "const_convert", issue = "143773")] + const impl FromStr for NonZero<$Int> { type Err = ParseIntError; /// Parses a non-zero integer from a string slice with decimal digits. From c30f2a421f4a835a04476d4c71e16e8f7fe90c34 Mon Sep 17 00:00:00 2001 From: lapla Date: Sat, 19 Sep 2026 15:45:50 +0900 Subject: [PATCH 09/24] Use `end_point` for trailing brace in `let...else` diagnostics --- compiler/rustc_parse/src/parser/stmt.rs | 4 +-- tests/ui/parser/let-else-fullwidth-brace.rs | 7 ++++++ .../ui/parser/let-else-fullwidth-brace.stderr | 25 +++++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 tests/ui/parser/let-else-fullwidth-brace.rs create mode 100644 tests/ui/parser/let-else-fullwidth-brace.stderr diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs index 7c3752cfff187..df34a2864b48a 100644 --- a/compiler/rustc_parse/src/parser/stmt.rs +++ b/compiler/rustc_parse/src/parser/stmt.rs @@ -12,7 +12,7 @@ use rustc_ast::{ LocalKind, MacCall, MacCallStmt, MacStmtStyle, Recovered, Stmt, StmtKind, }; use rustc_errors::{Applicability, Diag, PResult}; -use rustc_span::{BytePos, ErrorGuaranteed, Ident, Span, kw, sym}; +use rustc_span::{ErrorGuaranteed, Ident, Span, kw, sym}; use thin_vec::{ThinVec, thin_vec}; use super::attr::InnerAttrForbiddenReason; @@ -467,7 +467,7 @@ impl<'a> Parser<'a> { ), }; self.dcx().emit_err(diagnostics::InvalidCurlyInLetElse { - span: span.with_lo(span.hi() - BytePos(1)), + span: self.psess.source_map().end_point(span), sugg, }); } diff --git a/tests/ui/parser/let-else-fullwidth-brace.rs b/tests/ui/parser/let-else-fullwidth-brace.rs new file mode 100644 index 0000000000000..98bd65605faa8 --- /dev/null +++ b/tests/ui/parser/let-else-fullwidth-brace.rs @@ -0,0 +1,7 @@ +#![allow(irrefutable_let_patterns)] + +fn main() { + let x = {1} else { return; }; + //~^ ERROR unknown start of token: \u{ff5d} + //~| ERROR right curly brace `}` before `else` in a `let...else` statement not allowed +} diff --git a/tests/ui/parser/let-else-fullwidth-brace.stderr b/tests/ui/parser/let-else-fullwidth-brace.stderr new file mode 100644 index 0000000000000..a5bb4d029dbc5 --- /dev/null +++ b/tests/ui/parser/let-else-fullwidth-brace.stderr @@ -0,0 +1,25 @@ +error: unknown start of token: \u{ff5d} + --> $DIR/let-else-fullwidth-brace.rs:4:15 + | +LL | let x = {1} else { return; }; + | ^^ + | +help: Unicode character '}' (Fullwidth Right Curly Bracket) looks like '}' (Right Curly Brace), but it is not + | +LL - let x = {1} else { return; }; +LL + let x = {1} else { return; }; + | + +error: right curly brace `}` before `else` in a `let...else` statement not allowed + --> $DIR/let-else-fullwidth-brace.rs:4:15 + | +LL | let x = {1} else { return; }; + | ^^ + | +help: wrap the expression in parentheses + | +LL | let x = ({1}) else { return; }; + | + + + +error: aborting due to 2 previous errors + From 1b3f10d6f9be9dbbe571615aea7749ab83a81433 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 19 Sep 2026 11:13:45 +0200 Subject: [PATCH 10/24] add Dir::try_clone --- library/std/src/fs.rs | 22 ++++++++++++++++++++++ library/std/src/fs/tests.rs | 13 +++++++++++++ library/std/src/sys/fs/common.rs | 4 ++++ library/std/src/sys/fs/unix/dir.rs | 4 ++++ library/std/src/sys/fs/windows/dir.rs | 4 ++++ 5 files changed, 47 insertions(+) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 8df809264a6dd..65b8ed634bc05 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -1869,6 +1869,28 @@ impl Dir { pub fn remove_dir>(&self, path: P) -> io::Result<()> { self.inner.remove_dir(path.as_ref()) } + + /// Creates a new `Dir` instance that shares the same underlying directory handle + /// as the existing `Dir` instance. + /// + /// # Examples + /// + /// Creates two handles for a directory named `foo`: + /// + /// ```no_run + /// #![feature(dirfd)] + /// use std::fs::Dir; + /// + /// fn main() -> std::io::Result<()> { + /// let dir = Dir::open("foo")?; + /// let dir_copy = dir.try_clone()?; + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "dirfd", issue = "120426")] + pub fn try_clone(&self) -> io::Result { + Ok(Dir { inner: self.inner.duplicate()? }) + } } impl AsInner for Dir { diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 148f1c32b08b9..e949237d3dca2 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -2726,6 +2726,19 @@ fn test_dir_read_file() { assert_eq!("bar", &buf); } +#[test] +fn test_dir_clone() { + let tmpdir = tmpdir(); + let mut f = check!(File::create(tmpdir.join("foo.txt"))); + check!(f.write_all(b"bar")); + drop(f); + + let dir = check!(Dir::open(tmpdir.path())); + let dir2 = check!(dir.try_clone()); + let f = check!(dir2.open_file("foo.txt")); + drop(f); +} + #[test] fn test_dir_metadata() { let tmpdir = tmpdir(); diff --git a/library/std/src/sys/fs/common.rs b/library/std/src/sys/fs/common.rs index 17b98a4506544..96bafb26bb969 100644 --- a/library/std/src/sys/fs/common.rs +++ b/library/std/src/sys/fs/common.rs @@ -77,6 +77,10 @@ impl Dir { Self::open(path, &opts) } + pub fn duplicate(&self) -> io::Result { + Ok(Self { path: self.path.clone() }) + } + pub fn open_file(&self, path: &Path, opts: &OpenOptions) -> io::Result { File::open(&self.path.join(path), opts) } diff --git a/library/std/src/sys/fs/unix/dir.rs b/library/std/src/sys/fs/unix/dir.rs index 3fe952d942927..cf0dece265054 100644 --- a/library/std/src/sys/fs/unix/dir.rs +++ b/library/std/src/sys/fs/unix/dir.rs @@ -47,6 +47,10 @@ impl Dir { run_path_with_cstr(path, &|path| Self::open_traversal_c(path)) } + pub fn duplicate(&self) -> io::Result { + Ok(Self(self.0.try_clone()?)) + } + pub fn open_file(&self, path: &Path, opts: &OpenOptions) -> io::Result { run_path_with_cstr(path.as_ref(), &|path| self.open_file_c(path, opts, 0)) .map(FileDesc::from_inner) diff --git a/library/std/src/sys/fs/windows/dir.rs b/library/std/src/sys/fs/windows/dir.rs index 70bade84f58fd..d4674ad24f87e 100644 --- a/library/std/src/sys/fs/windows/dir.rs +++ b/library/std/src/sys/fs/windows/dir.rs @@ -72,6 +72,10 @@ impl Dir { with_native_path(path, &|path| Self::open_with_native(path, &opts)) } + pub fn duplicate(&self) -> io::Result { + Ok(Self { handle: self.handle.try_clone()? }) + } + pub fn open_file(&self, path: &Path, opts: &OpenOptions) -> io::Result { // NtCreateFile will fail if given an absolute path and a non-null RootDirectory if path.is_absolute() { From 6524ec5bde1bc006773ecceccf80167e9ebd9092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sat, 12 Sep 2026 23:01:57 +0200 Subject: [PATCH 11/24] Remove incorrect parse error recovery code that mistakes `as` casts for the long removed type ascription --- compiler/rustc_parse/src/parser/expr.rs | 32 ------------------------- 1 file changed, 32 deletions(-) diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 58e98a64b5e41..021ed221f057c 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -576,38 +576,6 @@ impl<'a> Parser<'a> { // `usize < y` as a type with generic arguments. let parser_snapshot_after_type = mem::replace(self, parser_snapshot_before_type); - // Check for typo of `'a: loop { break 'a }` with a missing `'`. - match (&lhs.kind, &self.token.kind) { - ( - // `foo: ` - ExprKind::Path(None, ast::Path { segments, .. }), - token::Ident(kw::For | kw::Loop | kw::While, IdentIsRaw::No), - ) if let [segment] = segments.as_slice() => { - let snapshot = self.create_snapshot_for_diagnostic(); - let label = Label { - ident: Ident::from_str_and_span( - &format!("'{}", segment.ident), - segment.ident.span, - ), - }; - match self.parse_expr_labeled(label, false) { - Ok(expr) => { - type_err.cancel(); - self.dcx().emit_err(crate::diagnostics::MalformedLoopLabel { - span: label.ident.span, - suggestion: label.ident.span.shrink_to_lo(), - }); - return Ok(expr); - } - Err(err) => { - err.cancel(); - self.restore_snapshot(snapshot); - } - } - } - _ => {} - } - match self.parse_path(PathStyle::Expr) { Ok(path) => { let span_after_type = parser_snapshot_after_type.token.span; From c095a72277a472c874db27add811ca9b7e8ad161 Mon Sep 17 00:00:00 2001 From: increasing Date: Fri, 18 Sep 2026 12:24:24 +0200 Subject: [PATCH 12/24] add test --- .../issues/true-false-type-issue-162947.rs | 16 +++++++ .../true-false-type-issue-162947.stderr | 46 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 tests/ui/parser/issues/true-false-type-issue-162947.rs create mode 100644 tests/ui/parser/issues/true-false-type-issue-162947.stderr diff --git a/tests/ui/parser/issues/true-false-type-issue-162947.rs b/tests/ui/parser/issues/true-false-type-issue-162947.rs new file mode 100644 index 0000000000000..ba6c9a13a36fa --- /dev/null +++ b/tests/ui/parser/issues/true-false-type-issue-162947.rs @@ -0,0 +1,16 @@ +struct A; + +impl A { + fn _a() -> true { //~ ERROR: expected type, found keyword `true` + false + } + fn b(&self) {} +} + +fn main() { + let a = A; + a.b(); //~ ERROR E0599 + + let _b: true = true; //~ ERROR: expected type, found keyword `true` + //~^ ERROR E0070 +} diff --git a/tests/ui/parser/issues/true-false-type-issue-162947.stderr b/tests/ui/parser/issues/true-false-type-issue-162947.stderr new file mode 100644 index 0000000000000..f471eb2739d81 --- /dev/null +++ b/tests/ui/parser/issues/true-false-type-issue-162947.stderr @@ -0,0 +1,46 @@ +error: expected type, found keyword `true` + --> $DIR/true-false-type-issue-162947.rs:4:15 + | +LL | impl A { + | - while parsing this item list starting here +LL | fn a() -> true { + | ^^^^ expected type +... +LL | } + | - the item list ends here + +error: expected type, found keyword `true` + --> $DIR/true-false-type-issue-162947.rs:14:12 + | +LL | let b: true = true; + | - ^^^^ expected type + | | + | while parsing the type for `b` + | +help: use `=` if you meant to assign + | +LL - let b: true = true; +LL + let b = true = true; + | + +error[E0599]: no method named `b` found for struct `A` in the current scope + --> $DIR/true-false-type-issue-162947.rs:12:7 + | +LL | struct A; + | -------- method `b` not found for this struct +... +LL | a.b(); + | ^ method not found in `A` + +error[E0070]: invalid left-hand side of assignment + --> $DIR/true-false-type-issue-162947.rs:14:17 + | +LL | let b: true = true; + | ---- ^ + | | + | cannot assign to this expression + +error: aborting due to 4 previous errors + +Some errors have detailed explanations: E0070, E0599. +For more information about an error, try `rustc --explain E0070`. From fe96227afdf72caf064b14b5e3a1f2e9f6cf8c85 Mon Sep 17 00:00:00 2001 From: increasing Date: Fri, 18 Sep 2026 18:09:30 +0200 Subject: [PATCH 13/24] recover true and false as bool type --- compiler/rustc_parse/src/parser/ty.rs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_parse/src/parser/ty.rs b/compiler/rustc_parse/src/parser/ty.rs index 88c07c6e2778c..42fb9121076f4 100644 --- a/compiler/rustc_parse/src/parser/ty.rs +++ b/compiler/rustc_parse/src/parser/ty.rs @@ -3,8 +3,8 @@ use rustc_ast::util::case::Case; use rustc_ast::{ self as ast, BoundAsyncness, BoundConstness, BoundPolarity, DUMMY_NODE_ID, FnPtrTy, FnRetTy, GenericBound, GenericBounds, GenericParam, Generics, Lifetime, MacCall, MutTy, Mutability, - Pinnedness, PolyTraitRef, PreciseCapturingArg, TraitBoundModifiers, TraitObjectSyntax, Ty, - TyKind, UnsafeBinderTy, + Path, Pinnedness, PolyTraitRef, PreciseCapturingArg, TraitBoundModifiers, TraitObjectSyntax, + Ty, TyKind, UnsafeBinderTy, }; use rustc_errors::{Applicability, Diag, E0516, PResult}; use rustc_span::{ErrorGuaranteed, Ident, Span, kw, sym}; @@ -406,7 +406,23 @@ impl<'a> Parser<'a> { let msg = format!("expected type, found {}", super::token_descr(&self.token)); let mut err = self.dcx().struct_span_err(lo, msg); err.span_label(lo, "expected type"); - return Err(err); + if self.may_recover() + && (self.eat_keyword_noexpect(kw::True) || self.eat_keyword_noexpect(kw::False)) + { + err.span_suggestion( + self.prev_token.span, + "the type is called", + "bool", + Applicability::MachineApplicable, + ); + err.emit(); + TyKind::Path( + None, + Path::from_ident(Ident { span: self.prev_token.span, name: sym::bool }), + ) + } else { + return Err(err); + } }; let span = lo.to(self.prev_token.span); From 8026b7beef9fb1b1e8f83efc8cdd7b544bff66d9 Mon Sep 17 00:00:00 2001 From: increasing Date: Fri, 18 Sep 2026 19:35:29 +0200 Subject: [PATCH 14/24] bless tests --- .../issues/true-false-type-issue-162947.fixed | 17 ++++++ .../issues/true-false-type-issue-162947.rs | 5 +- .../true-false-type-issue-162947.stderr | 52 +++++-------------- 3 files changed, 33 insertions(+), 41 deletions(-) create mode 100644 tests/ui/parser/issues/true-false-type-issue-162947.fixed diff --git a/tests/ui/parser/issues/true-false-type-issue-162947.fixed b/tests/ui/parser/issues/true-false-type-issue-162947.fixed new file mode 100644 index 0000000000000..8d3037ea0f458 --- /dev/null +++ b/tests/ui/parser/issues/true-false-type-issue-162947.fixed @@ -0,0 +1,17 @@ +//@ run-rustfix + +struct A; + +impl A { + fn _a() -> bool { //~ ERROR: expected type, found keyword `true` + false + } + fn b(&self) {} +} + +fn main() { + let a = A; + a.b(); + + let _b: bool = true; //~ ERROR: expected type, found keyword `true` +} diff --git a/tests/ui/parser/issues/true-false-type-issue-162947.rs b/tests/ui/parser/issues/true-false-type-issue-162947.rs index ba6c9a13a36fa..1c3a35b3f16f1 100644 --- a/tests/ui/parser/issues/true-false-type-issue-162947.rs +++ b/tests/ui/parser/issues/true-false-type-issue-162947.rs @@ -1,3 +1,5 @@ +//@ run-rustfix + struct A; impl A { @@ -9,8 +11,7 @@ impl A { fn main() { let a = A; - a.b(); //~ ERROR E0599 + a.b(); let _b: true = true; //~ ERROR: expected type, found keyword `true` - //~^ ERROR E0070 } diff --git a/tests/ui/parser/issues/true-false-type-issue-162947.stderr b/tests/ui/parser/issues/true-false-type-issue-162947.stderr index f471eb2739d81..558587d8ebe88 100644 --- a/tests/ui/parser/issues/true-false-type-issue-162947.stderr +++ b/tests/ui/parser/issues/true-false-type-issue-162947.stderr @@ -1,46 +1,20 @@ error: expected type, found keyword `true` - --> $DIR/true-false-type-issue-162947.rs:4:15 + --> $DIR/true-false-type-issue-162947.rs:6:16 | -LL | impl A { - | - while parsing this item list starting here -LL | fn a() -> true { - | ^^^^ expected type -... -LL | } - | - the item list ends here +LL | fn _a() -> true { + | ^^^^ + | | + | expected type + | help: the type is called: `bool` error: expected type, found keyword `true` - --> $DIR/true-false-type-issue-162947.rs:14:12 + --> $DIR/true-false-type-issue-162947.rs:16:13 | -LL | let b: true = true; - | - ^^^^ expected type - | | - | while parsing the type for `b` - | -help: use `=` if you meant to assign - | -LL - let b: true = true; -LL + let b = true = true; - | - -error[E0599]: no method named `b` found for struct `A` in the current scope - --> $DIR/true-false-type-issue-162947.rs:12:7 - | -LL | struct A; - | -------- method `b` not found for this struct -... -LL | a.b(); - | ^ method not found in `A` - -error[E0070]: invalid left-hand side of assignment - --> $DIR/true-false-type-issue-162947.rs:14:17 - | -LL | let b: true = true; - | ---- ^ - | | - | cannot assign to this expression +LL | let _b: true = true; + | ^^^^ + | | + | expected type + | help: the type is called: `bool` -error: aborting due to 4 previous errors +error: aborting due to 2 previous errors -Some errors have detailed explanations: E0070, E0599. -For more information about an error, try `rustc --explain E0070`. From e224f8f81ef41b3e7aa64d9a309365768408af36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sat, 12 Sep 2026 23:01:35 +0200 Subject: [PATCH 15/24] Further simplify `parse_assoc_op_cast` --- compiler/rustc_parse/src/parser/expr.rs | 79 ++++++++++--------------- 1 file changed, 32 insertions(+), 47 deletions(-) diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 021ed221f057c..c8dfebcedfe87 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -210,9 +210,7 @@ impl<'a> Parser<'a> { let (rhs, span) = finish_parsing_bin_op(self)?; self.mk_expr(span, ExprKind::Assign(lhs, rhs, op.span)) } - AssocOp::Cast => { - self.parse_assoc_op_cast(lhs, lhs_span, op.span, ExprKind::Cast)? - } + AssocOp::Cast => self.parse_assoc_op_cast(lhs, lhs_span, op.span)?, AssocOp::Range(limits) => self.parse_expr_range(min_prec, lhs, limits, op.span)?, }; @@ -555,17 +553,17 @@ impl<'a> Parser<'a> { lhs: Box, lhs_span: Span, op_span: Span, - expr_kind: fn(Box, Box) -> ExprKind, ) -> PResult<'a, Box> { - let mk_expr = |this: &mut Self, lhs: Box, rhs: Box| { - this.mk_expr(this.mk_expr_sp(&lhs, lhs_span, op_span, rhs.span), expr_kind(lhs, rhs)) + let mk_expr = |this: &mut Self, rhs: Box| { + let span = this.mk_expr_sp(&lhs, lhs_span, op_span, rhs.span); + this.mk_expr(span, ExprKind::Cast(lhs, rhs)) }; // Save the state of the parser before parsing type normally, in case there is a // LessThan comparison after this cast. let parser_snapshot_before_type = self.clone(); let cast_expr = match self.parse_as_cast_ty() { - Ok(rhs) => mk_expr(self, lhs, rhs), + Ok(rhs) => mk_expr(self, rhs), Err(type_err) => { if !self.may_recover() { return Err(type_err); @@ -579,11 +577,8 @@ impl<'a> Parser<'a> { match self.parse_path(PathStyle::Expr) { Ok(path) => { let span_after_type = parser_snapshot_after_type.token.span; - let expr = mk_expr( - self, - lhs, - self.mk_ty(path.span, TyKind::Path(None, path.clone())), - ); + let expr = + mk_expr(self, self.mk_ty(path.span, TyKind::Path(None, path.clone()))); let args_span = self.look_ahead(1, |t| t.span).to(span_after_type); match self.token.kind { @@ -642,48 +637,38 @@ impl<'a> Parser<'a> { // written `((&x) as T)[0]`. let span = cast_expr.span; - let with_postfix = self.parse_expr_dot_or_call_with(AttrVec::new(), cast_expr, span)?; // Check if an illegal postfix operator has been added after the cast. // If the resulting expression is not a cast, it is an illegal postfix operator. if !matches!(with_postfix.kind, ExprKind::Cast(_, _)) { - let msg = format!( - "cast cannot be followed by {}", - match with_postfix.kind { - ExprKind::Index(..) => "indexing", - ExprKind::Try(_) => "`?`", - ExprKind::Field(_, _) => "a field access", - ExprKind::MethodCall(_) => "a method call", - ExprKind::Call(_, _) => "a function call", - ExprKind::Await(_, _) => "`.await`", - ExprKind::Use(_, _) => "`.use`", - ExprKind::Yield(YieldKind::Postfix(_)) => "`.yield`", - ExprKind::Match(_, _, MatchKind::Postfix) => "a postfix match", - ExprKind::Err(_) => return Ok(with_postfix), - _ => unreachable!( - "did not expect {:?} as an illegal postfix operator following cast", - with_postfix.kind - ), - } - ); - let mut err = self.dcx().struct_span_err(span, msg); - - let suggest_parens = |err: &mut Diag<'_>| { - let suggestions = vec![ - (span.shrink_to_lo(), "(".to_string()), - (span.shrink_to_hi(), ")".to_string()), - ]; - err.multipart_suggestion( + let kind = match with_postfix.kind { + ExprKind::Index(..) => "indexing", + ExprKind::Try(_) => "`?`", + ExprKind::Field(_, _) => "a field access", + ExprKind::MethodCall(_) => "a method call", + ExprKind::Call(_, _) => "a function call", + ExprKind::Await(_, _) => "`.await`", + ExprKind::Use(_, _) => "`.use`", + ExprKind::Yield(YieldKind::Postfix(_)) => "`.yield`", + ExprKind::Match(_, _, MatchKind::Postfix) => "a postfix match", + ExprKind::Err(_) => return Ok(with_postfix), + _ => unreachable!( + "did not expect {:?} as an illegal postfix operator following cast", + with_postfix.kind + ), + }; + self.dcx() + .struct_span_err(span, format!("cast cannot be followed by {kind}")) + .with_multipart_suggestion( "try surrounding the expression in parentheses", - suggestions, + vec![ + (span.shrink_to_lo(), "(".to_string()), + (span.shrink_to_hi(), ")".to_string()), + ], Applicability::MachineApplicable, - ); - }; - - suggest_parens(&mut err); - - err.emit(); + ) + .emit(); }; Ok(with_postfix) } From 8a0a23db23e5d2c9055b980a0448e8a16562e9ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sun, 13 Sep 2026 01:58:27 +0200 Subject: [PATCH 16/24] Trigger "C array" parse error recovery in far fewer cases Previously we would trigger on 1. `unsafe { 1, 2, 3 }` and suggest `[ { 1, 2, 3 ]` (sic!) 2. `'label: { 1, 2, 3 }` and suggest `[: { 1, 2, 3 ]` (sic!) 3. `X::<{ 1, 2, 3 }>` and suggest `X::<[ 1, 2, 3]>` (wrong) 4. `|| -> i32 { 1, 2, 3 }` and suggest `|| -> i32 [ 1, 2, 3 ]` (wrong) 5. `await { 1, 2, 3 }` and suggest `await [ 1, 2, 3 ]` (wrong) Moreover, stop looking for identifiers after the `{` as that case can no longer be reached anyway as `maybe_recover_bad_struct_literal_path` will always snatch it first. --- compiler/rustc_parse/src/parser/expr.rs | 42 ++-------------- .../src/parser/expr/diagnostics.rs | 34 ++++++++++++- .../issue-87830-try-brackets-for-arrays.rs | 32 +++++++++++-- ...issue-87830-try-brackets-for-arrays.stderr | 48 ++++++++++++++----- 4 files changed, 100 insertions(+), 56 deletions(-) diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 58e98a64b5e41..2adfefe2bcb04 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -1401,6 +1401,9 @@ impl<'a> Parser<'a> { if let Some(expr) = this.maybe_recover_bad_struct_literal_path(false)? { return Ok(expr); } + if let Some(arr) = this.recover_from_c_array(lo) { + return Ok(arr); + } this.parse_expr_block(None, lo, BlockCheckMode::Default) } else if this.check(exp!(Or)) || this.check(exp!(OrOr)) { this.parse_expr_closure().map_err(|mut err| { @@ -2227,39 +2230,6 @@ impl<'a> Parser<'a> { } } - fn is_array_like_block(&mut self) -> bool { - self.token.kind == TokenKind::OpenBrace - && self - .look_ahead(1, |t| matches!(t.kind, TokenKind::Ident(..) | TokenKind::Literal(_))) - && self.look_ahead(2, |t| t == &token::Comma) - && self.look_ahead(3, |t| t.can_begin_expr()) - } - - /// Emits a suggestion if it looks like the user meant an array but - /// accidentally used braces, causing the code to be interpreted as a block - /// expression. - fn maybe_suggest_brackets_instead_of_braces(&mut self, lo: Span) -> Option> { - let mut snapshot = self.create_snapshot_for_diagnostic(); - match snapshot.parse_expr_array_or_repeat(exp!(CloseBrace)) { - Ok(arr) => { - let guar = self.dcx().emit_err(crate::diagnostics::ArrayBracketsInsteadOfBraces { - span: arr.span, - sub: crate::diagnostics::ArrayBracketsInsteadOfBracesSugg { - left: lo, - right: snapshot.prev_token.span, - }, - }); - - self.restore_snapshot(snapshot); - Some(self.mk_expr_err(arr.span, guar)) - } - Err(e) => { - e.cancel(); - None - } - } - } - fn suggest_missing_semicolon_before_array( &self, prev_span: Span, @@ -2309,12 +2279,6 @@ impl<'a> Parser<'a> { lo: Span, blk_mode: BlockCheckMode, ) -> PResult<'a, Box> { - if self.may_recover() && self.is_array_like_block() { - if let Some(arr) = self.maybe_suggest_brackets_instead_of_braces(lo) { - return Ok(arr); - } - } - if self.token.is_metavar_block() { self.dcx().emit_err(crate::diagnostics::InvalidBlockMacroSegment { span: self.token.span, diff --git a/compiler/rustc_parse/src/parser/expr/diagnostics.rs b/compiler/rustc_parse/src/parser/expr/diagnostics.rs index 707ae5d34bc75..6e56ea6c616fd 100644 --- a/compiler/rustc_parse/src/parser/expr/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/expr/diagnostics.rs @@ -3,8 +3,8 @@ use rustc_ast::{BinOpKind, Expr, ExprKind, token}; use rustc_errors::{Applicability, Diag, PResult}; use rustc_span::{Span, Spanned, respan, sym}; -use crate::diagnostics; use crate::parser::Parser; +use crate::{diagnostics, exp}; impl<'a> Parser<'a> { /// Recover from alphabetic logic operators `and` and `or` as found in e.g., Python and PHP. @@ -216,6 +216,38 @@ impl<'a> Parser<'a> { } err } + + /// Recover from array expressions as found in C like `{0, 1, 2, 3}`. + pub(super) fn recover_from_c_array(&mut self, lo: Span) -> Option> { + if !self.may_recover() + || self.token.kind != token::OpenBrace + || self.look_ahead(1, |t| !matches!(t.kind, token::Literal(_))) + || self.look_ahead(2, |t| t != &token::Comma) + || self.look_ahead(3, |t| !t.can_begin_expr()) + { + return None; + } + + let mut snapshot = self.create_snapshot_for_diagnostic(); + match snapshot.parse_expr_array_or_repeat(exp!(CloseBrace)) { + Ok(arr) => { + let guar = self.dcx().emit_err(diagnostics::ArrayBracketsInsteadOfBraces { + span: arr.span, + sub: diagnostics::ArrayBracketsInsteadOfBracesSugg { + left: lo, + right: snapshot.prev_token.span, + }, + }); + + self.restore_snapshot(snapshot); + Some(self.mk_expr_err(arr.span, guar)) + } + Err(e) => { + e.cancel(); + None + } + } + } } #[derive(Copy, Clone)] diff --git a/tests/ui/did_you_mean/issue-87830-try-brackets-for-arrays.rs b/tests/ui/did_you_mean/issue-87830-try-brackets-for-arrays.rs index 070ffaa1eff00..99d63929db484 100644 --- a/tests/ui/did_you_mean/issue-87830-try-brackets-for-arrays.rs +++ b/tests/ui/did_you_mean/issue-87830-try-brackets-for-arrays.rs @@ -1,18 +1,42 @@ +// Test that we can recover from very basic C arrays in the parser & provide a good diagnostic. + fn main() {} -const FOO: [u8; 3] = { +const INTS: [u8; 3] = { //~^ ERROR this is a block expression, not an array 1, 2, 3 }; -const BAR: [&str; 3] = {"one", "two", "three"}; +const STRS: [&str; 3] = {"one", "two", "three"}; //~^ ERROR this is a block expression, not an array -fn foo() { +fn expr_stmt() { {1, 2, 3}; //~^ ERROR this is a block expression, not an array } -fn bar() { +// Don't trigger here. +fn unsafe_block() { + unsafe { 1, 2, 3 } //~ ERROR expected one of +} + +// Don't trigger here. +fn labeled_block() { + 'label: { 1, 2, 3 } //~ ERROR expected one of +} + +// Don't trigger here, this is not a block expression, only a block. +fn fn_body_block() { 1, 2, 3 //~ ERROR expected one of } + +// Don't trigger here, this is not a block expression, only a block. +fn closure_body_block() { + || -> i32 { 1, 2, 3 }; //~ ERROR expected one of +} + +// Don't trigger here. +fn const_arg() { + struct Casket; + Casket::<{ 1, 2, 3 }>; //~ ERROR expected one of +} diff --git a/tests/ui/did_you_mean/issue-87830-try-brackets-for-arrays.stderr b/tests/ui/did_you_mean/issue-87830-try-brackets-for-arrays.stderr index 58232e2307d8e..531d54a9e3f1e 100644 --- a/tests/ui/did_you_mean/issue-87830-try-brackets-for-arrays.stderr +++ b/tests/ui/did_you_mean/issue-87830-try-brackets-for-arrays.stderr @@ -1,8 +1,8 @@ error: this is a block expression, not an array - --> $DIR/issue-87830-try-brackets-for-arrays.rs:3:22 + --> $DIR/issue-87830-try-brackets-for-arrays.rs:5:23 | -LL | const FOO: [u8; 3] = { - | ______________________^ +LL | const INTS: [u8; 3] = { + | _______________________^ LL | | LL | | 1, 2, 3 LL | | }; @@ -10,26 +10,26 @@ LL | | }; | help: to make an array, use square brackets instead of curly braces | -LL ~ const FOO: [u8; 3] = [ +LL ~ const INTS: [u8; 3] = [ LL | LL | 1, 2, 3 LL ~ ]; | error: this is a block expression, not an array - --> $DIR/issue-87830-try-brackets-for-arrays.rs:8:24 + --> $DIR/issue-87830-try-brackets-for-arrays.rs:10:25 | -LL | const BAR: [&str; 3] = {"one", "two", "three"}; - | ^^^^^^^^^^^^^^^^^^^^^^^ +LL | const STRS: [&str; 3] = {"one", "two", "three"}; + | ^^^^^^^^^^^^^^^^^^^^^^^ | help: to make an array, use square brackets instead of curly braces | -LL - const BAR: [&str; 3] = {"one", "two", "three"}; -LL + const BAR: [&str; 3] = ["one", "two", "three"]; +LL - const STRS: [&str; 3] = {"one", "two", "three"}; +LL + const STRS: [&str; 3] = ["one", "two", "three"]; | error: this is a block expression, not an array - --> $DIR/issue-87830-try-brackets-for-arrays.rs:12:5 + --> $DIR/issue-87830-try-brackets-for-arrays.rs:14:5 | LL | {1, 2, 3}; | ^^^^^^^^^ @@ -41,10 +41,34 @@ LL + [1, 2, 3]; | error: expected one of `.`, `;`, `?`, `}`, or an operator, found `,` - --> $DIR/issue-87830-try-brackets-for-arrays.rs:17:6 + --> $DIR/issue-87830-try-brackets-for-arrays.rs:20:15 + | +LL | unsafe { 1, 2, 3 } + | ^ expected one of `.`, `;`, `?`, `}`, or an operator + +error: expected one of `.`, `;`, `?`, `}`, or an operator, found `,` + --> $DIR/issue-87830-try-brackets-for-arrays.rs:25:16 + | +LL | 'label: { 1, 2, 3 } + | ^ expected one of `.`, `;`, `?`, `}`, or an operator + +error: expected one of `.`, `;`, `?`, `}`, or an operator, found `,` + --> $DIR/issue-87830-try-brackets-for-arrays.rs:30:6 | LL | 1, 2, 3 | ^ expected one of `.`, `;`, `?`, `}`, or an operator -error: aborting due to 4 previous errors +error: expected one of `.`, `;`, `?`, `}`, or an operator, found `,` + --> $DIR/issue-87830-try-brackets-for-arrays.rs:35:18 + | +LL | || -> i32 { 1, 2, 3 }; + | ^ expected one of `.`, `;`, `?`, `}`, or an operator + +error: expected one of `.`, `;`, `?`, `}`, or an operator, found `,` + --> $DIR/issue-87830-try-brackets-for-arrays.rs:41:17 + | +LL | Casket::<{ 1, 2, 3 }>; + | ^ expected one of `.`, `;`, `?`, `}`, or an operator + +error: aborting due to 8 previous errors From 2e15f7b0ea873b1057a98a97294d409cff3bf5f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Mon, 27 Jul 2026 20:43:32 +0000 Subject: [PATCH 17/24] Better account for `Self` that might be a typo of `self` When in a method trying to access `Self` on its own, suggest `self`. When in any assoc fn trying to access `Self()`, suggest `Self { fields }` or using an enum variant. When enum has no variants, mention it. --- compiler/rustc_hir_typeck/src/expr.rs | 1 + .../rustc_hir_typeck/src/fn_ctxt/_impl.rs | 56 ++++++-- compiler/rustc_hir_typeck/src/pat.rs | 11 +- .../invalid-self-constructor-56835.stderr | 8 +- .../self-constructor-type-error-56199.rs | 31 +++++ .../self-constructor-type-error-56199.stderr | 129 +++++++++++++++++- 6 files changed, 213 insertions(+), 23 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index a8898acf3a415..c995cdee10fe9 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -617,6 +617,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { call_expr_and_args.map_or(expr.span, |(e, _)| e.span), expr.span, expr.hir_id, + call_expr_and_args.is_some(), ) .0 } diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs index e655e0857d858..ea2e3584b2db7 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs @@ -1006,6 +1006,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { span: Span, path_span: Span, hir_id: HirId, + has_args: bool, ) -> (Ty<'tcx>, Res) { let tcx = self.tcx; @@ -1253,17 +1254,50 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { "the `Self` constructor can only be used with tuple or unit structs", ); if let Some(adt_def) = ty.normalized.ty_adt_def() { - match adt_def.adt_kind() { - AdtKind::Enum => { - err.help("did you mean to use one of the enum's variants?"); - } - AdtKind::Struct | AdtKind::Union => { - err.span_suggestion( - span, - "use curly brackets", - "Self { /* fields */ }", - Applicability::HasPlaceholders, - ); + let def_id = self.body_def_id.to_def_id(); + if !has_args + && let Some(assoc) = tcx.opt_associated_item(def_id) + && assoc.is_method() + { + let self_ty = + tcx.fn_sig(def_id).instantiate_identity().skip_binder().inputs()[0]; + let applicability = if let ty::Adt(..) = self_ty.kind() { + // We're within a method that takes ownership of `Self`, likely a + // builder, so this is most likely a typo. + Applicability::MachineApplicable + } else { + // We still might have meant `self` instead of `Self`. + Applicability::MaybeIncorrect + }; + err.span_suggestion_verbose( + span, + format!( + "you might have meant to refer to the `self` binding of type \ + `{self_ty}`", + ), + "self".to_string(), + applicability, + ); + } else { + match adt_def.adt_kind() { + AdtKind::Enum => { + err.span_help( + tcx.def_span(adt_def.did()), + if adt_def.variants().is_empty() { + "the enum is unconstructable because it has no variants" + } else { + "you might have meant to use one of the enum's variants" + }, + ); + } + AdtKind::Struct | AdtKind::Union => { + err.span_suggestion_verbose( + span, + "use curly brackets", + "Self { /* fields */ }", + Applicability::HasPlaceholders, + ); + } } } } diff --git a/compiler/rustc_hir_typeck/src/pat.rs b/compiler/rustc_hir_typeck/src/pat.rs index caffef6a217a8..ec4483b62fe72 100644 --- a/compiler/rustc_hir_typeck/src/pat.rs +++ b/compiler/rustc_hir_typeck/src/pat.rs @@ -911,7 +911,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { rustc_hir::PatExprKind::Path(qpath) => { let (res, opt_ty, segments) = self.resolve_ty_and_res_fully_qualified_call(qpath, lt.hir_id, lt.span); - self.instantiate_value_path(segments, opt_ty, res, lt.span, lt.span, lt.hir_id).0 + self.instantiate_value_path( + segments, opt_ty, res, lt.span, lt.span, lt.hir_id, false, + ) + .0 } }; self.write_ty(lt.hir_id, ty); @@ -1624,7 +1627,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Find the type of the path pattern, for later checking. let (pat_ty, pat_res) = - self.instantiate_value_path(segments, opt_ty, res, span, span, path_id); + self.instantiate_value_path(segments, opt_ty, res, span, span, path_id, false); Ok(ResolvedPat { ty: pat_ty, kind: ResolvedPatKind::Path { res, pat_res, segments } }) } @@ -1784,8 +1787,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } // Type-check the path. - let (pat_ty, res) = - self.instantiate_value_path(segments, opt_ty, res, pat.span, pat.span, pat.hir_id); + let (pat_ty, res) = self + .instantiate_value_path(segments, opt_ty, res, pat.span, pat.span, pat.hir_id, false); if !pat_ty.is_fn() { return report_unexpected_res(res); } diff --git a/tests/ui/structs/invalid-self-constructor-56835.stderr b/tests/ui/structs/invalid-self-constructor-56835.stderr index 045781ec42bd2..9b25348d87d39 100644 --- a/tests/ui/structs/invalid-self-constructor-56835.stderr +++ b/tests/ui/structs/invalid-self-constructor-56835.stderr @@ -2,7 +2,13 @@ error: the `Self` constructor can only be used with tuple or unit structs --> $DIR/invalid-self-constructor-56835.rs:5:12 | LL | fn bar(Self(foo): Self) {} - | ^^^^^^^^^ help: use curly brackets: `Self { /* fields */ }` + | ^^^^^^^^^ + | +help: use curly brackets + | +LL - fn bar(Self(foo): Self) {} +LL + fn bar(Self { /* fields */ }: Self) {} + | error[E0164]: expected tuple struct or tuple variant, found self constructor `Self` --> $DIR/invalid-self-constructor-56835.rs:5:12 diff --git a/tests/ui/typeck/self-constructor-type-error-56199.rs b/tests/ui/typeck/self-constructor-type-error-56199.rs index b08d69189807a..34af8bed25c47 100644 --- a/tests/ui/typeck/self-constructor-type-error-56199.rs +++ b/tests/ui/typeck/self-constructor-type-error-56199.rs @@ -1,5 +1,8 @@ // https://github.com/rust-lang/rust/issues/56199 enum Foo {} +enum Lab { + Qux, +} struct Bar {} impl Foo { @@ -9,6 +12,12 @@ impl Foo { let _ = Self(); //~^ ERROR the `Self` constructor can only be used with tuple or unit structs } + fn foo_method(self) { + let _ = Self; + //~^ ERROR the `Self` constructor can only be used with tuple or unit structs + let _ = Self(); + //~^ ERROR the `Self` constructor can only be used with tuple or unit structs + } } impl Bar { @@ -18,6 +27,28 @@ impl Bar { let _ = Self(); //~^ ERROR the `Self` constructor can only be used with tuple or unit structs } + fn bar_method(self) { + let _ = Self; + //~^ ERROR the `Self` constructor can only be used with tuple or unit structs + let _ = Self(); + //~^ ERROR the `Self` constructor can only be used with tuple or unit structs + } +} + +impl Lab { + fn lab() { + let _ = Self; + //~^ ERROR the `Self` constructor can only be used with tuple or unit structs + let _ = Self(); + //~^ ERROR the `Self` constructor can only be used with tuple or unit structs + } + fn lab_method(self) { + let _ = Self; + //~^ ERROR the `Self` constructor can only be used with tuple or unit structs + let _ = Self(); + //~^ ERROR the `Self` constructor can only be used with tuple or unit structs + } } + fn main() {} diff --git a/tests/ui/typeck/self-constructor-type-error-56199.stderr b/tests/ui/typeck/self-constructor-type-error-56199.stderr index 6e9d0fcd90c05..d0d124c6f3149 100644 --- a/tests/ui/typeck/self-constructor-type-error-56199.stderr +++ b/tests/ui/typeck/self-constructor-type-error-56199.stderr @@ -1,30 +1,145 @@ error: the `Self` constructor can only be used with tuple or unit structs - --> $DIR/self-constructor-type-error-56199.rs:7:17 + --> $DIR/self-constructor-type-error-56199.rs:10:17 | LL | let _ = Self; | ^^^^ | - = help: did you mean to use one of the enum's variants? +help: the enum is unconstructable because it has no variants + --> $DIR/self-constructor-type-error-56199.rs:2:1 + | +LL | enum Foo {} + | ^^^^^^^^ error: the `Self` constructor can only be used with tuple or unit structs - --> $DIR/self-constructor-type-error-56199.rs:9:17 + --> $DIR/self-constructor-type-error-56199.rs:12:17 | LL | let _ = Self(); | ^^^^^^ | - = help: did you mean to use one of the enum's variants? +help: the enum is unconstructable because it has no variants + --> $DIR/self-constructor-type-error-56199.rs:2:1 + | +LL | enum Foo {} + | ^^^^^^^^ error: the `Self` constructor can only be used with tuple or unit structs --> $DIR/self-constructor-type-error-56199.rs:16:17 | LL | let _ = Self; - | ^^^^ help: use curly brackets: `Self { /* fields */ }` + | ^^^^ + | +help: you might have meant to refer to the `self` binding of type `Foo` (notice the capitalization) + | +LL - let _ = Self; +LL + let _ = self; + | error: the `Self` constructor can only be used with tuple or unit structs --> $DIR/self-constructor-type-error-56199.rs:18:17 | LL | let _ = Self(); - | ^^^^^^ help: use curly brackets: `Self { /* fields */ }` + | ^^^^^^ + | +help: the enum is unconstructable because it has no variants + --> $DIR/self-constructor-type-error-56199.rs:2:1 + | +LL | enum Foo {} + | ^^^^^^^^ + +error: the `Self` constructor can only be used with tuple or unit structs + --> $DIR/self-constructor-type-error-56199.rs:25:17 + | +LL | let _ = Self; + | ^^^^ + | +help: use curly brackets + | +LL | let _ = Self { /* fields */ }; + | ++++++++++++++++ + +error: the `Self` constructor can only be used with tuple or unit structs + --> $DIR/self-constructor-type-error-56199.rs:27:17 + | +LL | let _ = Self(); + | ^^^^^^ + | +help: use curly brackets + | +LL - let _ = Self(); +LL + let _ = Self { /* fields */ }; + | + +error: the `Self` constructor can only be used with tuple or unit structs + --> $DIR/self-constructor-type-error-56199.rs:31:17 + | +LL | let _ = Self; + | ^^^^ + | +help: you might have meant to refer to the `self` binding of type `Bar` (notice the capitalization) + | +LL - let _ = Self; +LL + let _ = self; + | + +error: the `Self` constructor can only be used with tuple or unit structs + --> $DIR/self-constructor-type-error-56199.rs:33:17 + | +LL | let _ = Self(); + | ^^^^^^ + | +help: use curly brackets + | +LL - let _ = Self(); +LL + let _ = Self { /* fields */ }; + | + +error: the `Self` constructor can only be used with tuple or unit structs + --> $DIR/self-constructor-type-error-56199.rs:40:17 + | +LL | let _ = Self; + | ^^^^ + | +help: you might have meant to use one of the enum's variants + --> $DIR/self-constructor-type-error-56199.rs:3:1 + | +LL | enum Lab { + | ^^^^^^^^ + +error: the `Self` constructor can only be used with tuple or unit structs + --> $DIR/self-constructor-type-error-56199.rs:42:17 + | +LL | let _ = Self(); + | ^^^^^^ + | +help: you might have meant to use one of the enum's variants + --> $DIR/self-constructor-type-error-56199.rs:3:1 + | +LL | enum Lab { + | ^^^^^^^^ + +error: the `Self` constructor can only be used with tuple or unit structs + --> $DIR/self-constructor-type-error-56199.rs:46:17 + | +LL | let _ = Self; + | ^^^^ + | +help: you might have meant to refer to the `self` binding of type `Lab` (notice the capitalization) + | +LL - let _ = Self; +LL + let _ = self; + | + +error: the `Self` constructor can only be used with tuple or unit structs + --> $DIR/self-constructor-type-error-56199.rs:48:17 + | +LL | let _ = Self(); + | ^^^^^^ + | +help: you might have meant to use one of the enum's variants + --> $DIR/self-constructor-type-error-56199.rs:3:1 + | +LL | enum Lab { + | ^^^^^^^^ -error: aborting due to 4 previous errors +error: aborting due to 12 previous errors From 09fb8491438cc7f31c67073cc101b551a9184263 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Mon, 9 Mar 2026 23:28:01 +0000 Subject: [PATCH 18/24] Detect more cases of method shadowing with incorrect arguments ``` error[E0061]: this method takes 0 arguments but 1 argument was supplied --> $DIR/shadowed-intrinsic-method.rs:18:7 | LL | a.borrow(()); | ^^^^^^ -- unexpected argument of type `()` | note: the `borrow` call is resolved to the method in `std::borrow::Borrow`, shadowing the method of the same name on the inherent impl for `A` --> $DIR/shadowed-intrinsic-method.rs:18:7 | LL | use std::borrow::Borrow; | ------------------- `std::borrow::Borrow` imported here ... LL | a.borrow(()); | ^^^^^^ refers to `std::borrow::Borrow::borrow` note: method defined here --> $SRC_DIR/core/src/borrow.rs:LL:COL help: you might have meant to call the other method; you can use the fully-qualified path to call it explicitly | LL - a.borrow(()); LL + A::borrow(&mut a, ()); | help: remove the extra argument | LL - a.borrow(()); LL + a.borrow(); | ``` Account for inherent methods Tweak wording on "other methods available" note Handle correct gramar in the face of a single other option, or many. --- compiler/rustc_hir_typeck/src/demand.rs | 105 ++++++++++------- .../rustc_hir_typeck/src/fn_ctxt/checks.rs | 2 + tests/ui/methods/shadowed-intrinsic-method.rs | 37 ++++++ .../methods/shadowed-intrinsic-method.stderr | 111 ++++++++++++++++++ ...thod-lookup-returns-sig-with-fewer-args.rs | 8 +- ...-lookup-returns-sig-with-fewer-args.stderr | 10 +- .../suggestions/shadowed-lplace-method.fixed | 2 +- .../suggestions/shadowed-lplace-method.stderr | 2 +- 8 files changed, 226 insertions(+), 51 deletions(-) create mode 100644 tests/ui/methods/shadowed-intrinsic-method.rs create mode 100644 tests/ui/methods/shadowed-intrinsic-method.stderr diff --git a/compiler/rustc_hir_typeck/src/demand.rs b/compiler/rustc_hir_typeck/src/demand.rs index d18745e24fd84..55eecef732aa4 100644 --- a/compiler/rustc_hir_typeck/src/demand.rs +++ b/compiler/rustc_hir_typeck/src/demand.rs @@ -1,5 +1,5 @@ -use rustc_errors::{Applicability, Diag, MultiSpan, listify}; -use rustc_hir::def::Res; +use rustc_errors::{Applicability, Diag, MultiSpan, listify, pluralize}; +use rustc_hir::def::{DefKind, Res}; use rustc_hir::intravisit::Visitor; use rustc_hir::{self as hir, find_attr}; use rustc_infer::infer::DefineOpaqueTypes; @@ -28,7 +28,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if expr_ty == expected { return; } - self.annotate_alternative_method_deref(err, expr, error); + self.annotate_alternative_method_deref_for_unop(err, expr, error); self.explain_self_literal(err, expr, expected, expr_ty); // Use `||` to give these suggestions a precedence @@ -752,11 +752,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { hir::ExprKind::Path(hir::QPath::Resolved( None, hir::Path { - res: - hir::def::Res::Def( - hir::def::DefKind::Static { .. } | hir::def::DefKind::Const, - def_id, - ), + res: hir::def::Res::Def(DefKind::Static { .. } | DefKind::Const, def_id), .. }, )) => { @@ -929,7 +925,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { false } - fn annotate_alternative_method_deref( + fn annotate_alternative_method_deref_for_unop( &self, err: &mut Diag<'_>, expr: &hir::Expr<'_>, @@ -949,7 +945,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let hir::ExprKind::Unary(hir::UnOp::Deref, deref) = lhs.kind else { return; }; - let hir::ExprKind::MethodCall(path, base, args, _) = deref.kind else { + self.annotate_alternative_method_deref(err, deref, Some(expected)) + } + + #[tracing::instrument(skip(self, err), level = "debug")] + pub(crate) fn annotate_alternative_method_deref( + &self, + err: &mut Diag<'_>, + expr: &hir::Expr<'_>, + expected: Option>, + ) { + let hir::ExprKind::MethodCall(path, base, args, _) = expr.kind else { return; }; let Some(self_ty) = self.typeck_results.borrow().expr_ty_adjusted_opt(base) else { @@ -959,7 +965,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let Ok(pick) = self.lookup_probe_for_diagnostic( path.ident, self_ty, - deref, + expr, probe::ProbeScope::TraitsInScope, None, ) else { @@ -969,10 +975,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let Ok(in_scope_methods) = self.probe_for_name_many( probe::Mode::MethodCall, path.ident, - Some(expected), + expected, probe::IsSuggestion(true), self_ty, - deref.hir_id, + expr.hir_id, probe::ProbeScope::TraitsInScope, ) else { return; @@ -984,10 +990,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let Ok(all_methods) = self.probe_for_name_many( probe::Mode::MethodCall, path.ident, - Some(expected), + expected, probe::IsSuggestion(true), self_ty, - deref.hir_id, + expr.hir_id, probe::ProbeScope::AllTraits, ) else { return; @@ -995,34 +1001,51 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let suggestions: Vec<_> = all_methods .into_iter() - .filter(|c| c.item.def_id != pick.item.def_id) - .map(|c| { + .filter_map(|c| { + if c.item.def_id == pick.item.def_id { + return None; + } let m = c.item; let generic_args = ty::GenericArgs::for_item(self.tcx, m.def_id, |param, _| { - self.var_for_def(deref.span, param) + self.var_for_def(expr.span, param) }); - let mutability = - match self.tcx.fn_sig(m.def_id).skip_binder().input(0).skip_binder().kind() { - ty::Ref(_, _, hir::Mutability::Mut) => "&mut ", - ty::Ref(_, _, _) => "&", - _ => "", - }; - vec![ - ( - deref.span.until(base.span), - format!( - "{}({}", - with_no_trimmed_paths!( - self.tcx.def_path_str_with_args(m.def_id, generic_args,) - ), - mutability, - ), - ), + let fn_sig = self.tcx.fn_sig(m.def_id); + if fn_sig.skip_binder().inputs().skip_binder().len() != args.len() + 1 { + return None; + } + let rcvr_ty = fn_sig.skip_binder().input(0).skip_binder(); + let (mutability, ty) = match rcvr_ty.kind() { + ty::Ref(_, ty, hir::Mutability::Mut) => ("&mut ", ty), + ty::Ref(_, ty, _) => ("&", ty), + _ => ("", &rcvr_ty), + }; + let path = match self.tcx.assoc_parent(m.def_id) { + Some((_, DefKind::Impl { of_trait: true })) => { + // We have `impl Trait for T {}`, suggest `::method`. + self.tcx.def_path_str_with_args(m.def_id, generic_args).to_string() + } + Some((_, DefKind::Impl { of_trait: false })) => { + if let ty::Adt(def, _) = ty.kind() { + // We have `impl T {}`, suggest `T::method`. + format!("{}::{}", self.tcx.def_path_str(def.did()), path.ident) + } else { + // This should be unreachable, as `impl &'a T {}` is invalid. + format!("{ty}::{}", path.ident) + } + } + // Fallback for arbitrary self types. + _ => with_no_trimmed_paths!( + self.tcx.def_path_str_with_args(m.def_id, generic_args) + ) + .to_string(), + }; + Some(vec![ + (expr.span.until(base.span), format!("{path}({}", mutability)), match &args { - [] => (base.span.shrink_to_hi().with_hi(deref.span.hi()), ")".to_string()), + [] => (base.span.shrink_to_hi().with_hi(expr.span.hi()), ")".to_string()), [first, ..] => (base.span.between(first.span), ", ".to_string()), }, - ] + ]) }) .collect(); if suggestions.is_empty() { @@ -1076,9 +1099,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ), ); if suggestions.len() > other_methods_in_scope.len() { + let n = suggestions.len() - other_methods_in_scope.len(); err.note(format!( - "additionally, there are {} other available methods that aren't in scope", - suggestions.len() - other_methods_in_scope.len() + "additionally, there {are} {n} other available method{s} that {are}n't in scope", + are = pluralize!("is", n), + s = pluralize!(n), )); } err.multipart_suggestions( @@ -1293,7 +1318,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let hir::def::Res::Def(kind, def_id) = path.res else { return; }; - let callable_kind = if matches!(kind, hir::def::DefKind::Ctor(_, _)) { + let callable_kind = if matches!(kind, DefKind::Ctor(_, _)) { CallableKind::Constructor } else { CallableKind::Function diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index 074b95321db3b..9965a9d071031 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -3186,6 +3186,8 @@ impl<'a, 'tcx> ArgMatchingCtxt<'a, 'tcx> { ); return; } + + self.annotate_alternative_method_deref(err, self.call_expr, None); } /// A "softer" version of the `demand_compatible`, which checks types without persisting them, diff --git a/tests/ui/methods/shadowed-intrinsic-method.rs b/tests/ui/methods/shadowed-intrinsic-method.rs new file mode 100644 index 0000000000000..8350d6a7ba5f0 --- /dev/null +++ b/tests/ui/methods/shadowed-intrinsic-method.rs @@ -0,0 +1,37 @@ +// Can't use rustfix because we provide two suggestions: +// to remove the arg for `Borrow::borrow` or to call `Type::borrow`. +use std::borrow::Borrow; + +struct A; + +impl A { fn borrow(&mut self, _: ()) {} } + +struct B; + +fn main() { + // The fully-qualified path for items within functions is unnameable from outside that function. + impl B { fn borrow(&mut self, _: ()) {} } + + struct C; + // The fully-qualified path for items within functions is unnameable from outside that function. + impl C { fn borrow(&mut self, _: ()) {} } + + let mut a = A; + a.borrow(()); //~ ERROR E0061 + // A::borrow(&mut a, ()); + let mut b = B; + b.borrow(()); //~ ERROR E0061 + // This currently suggests `main::::borrow`, which is not correct, it should be + // B::borrow(&mut b, ()); + let mut c = C; + c.borrow(()); //~ ERROR E0061 + // This currently suggests `main::C::borrow`, which is not correct, it should be + // C::borrow(&mut c, ()); +} + +fn foo() { + let mut b = B; + b.borrow(()); //~ ERROR E0061 + // This currently suggests `main::::borrow`, which is not correct, it should be + // B::borrow(&mut b, ()); +} diff --git a/tests/ui/methods/shadowed-intrinsic-method.stderr b/tests/ui/methods/shadowed-intrinsic-method.stderr new file mode 100644 index 0000000000000..a832714cd1f97 --- /dev/null +++ b/tests/ui/methods/shadowed-intrinsic-method.stderr @@ -0,0 +1,111 @@ +error[E0061]: this method takes 0 arguments but 1 argument was supplied + --> $DIR/shadowed-intrinsic-method.rs:20:7 + | +LL | a.borrow(()); + | ^^^^^^ -- unexpected argument of type `()` + | +note: the `borrow` call is resolved to the method in `std::borrow::Borrow`, shadowing the method of the same name on the inherent impl for `A` + --> $DIR/shadowed-intrinsic-method.rs:20:7 + | +LL | use std::borrow::Borrow; + | ------------------- `std::borrow::Borrow` imported here +... +LL | a.borrow(()); + | ^^^^^^ refers to `std::borrow::Borrow::borrow` +note: method defined here + --> $SRC_DIR/core/src/borrow.rs:LL:COL +help: you might have meant to call the other method; you can use the fully-qualified path to call it explicitly + | +LL - a.borrow(()); +LL + A::borrow(&mut a, ()); + | +help: remove the extra argument + | +LL - a.borrow(()); +LL + a.borrow(); + | + +error[E0061]: this method takes 0 arguments but 1 argument was supplied + --> $DIR/shadowed-intrinsic-method.rs:23:7 + | +LL | b.borrow(()); + | ^^^^^^ -- unexpected argument of type `()` + | +note: the `borrow` call is resolved to the method in `std::borrow::Borrow`, shadowing the method of the same name on the inherent impl for `main::` + --> $DIR/shadowed-intrinsic-method.rs:23:7 + | +LL | use std::borrow::Borrow; + | ------------------- `std::borrow::Borrow` imported here +... +LL | b.borrow(()); + | ^^^^^^ refers to `std::borrow::Borrow::borrow` +note: method defined here + --> $SRC_DIR/core/src/borrow.rs:LL:COL +help: you might have meant to call the other method; you can use the fully-qualified path to call it explicitly + | +LL - b.borrow(()); +LL + B::borrow(&mut b, ()); + | +help: remove the extra argument + | +LL - b.borrow(()); +LL + b.borrow(); + | + +error[E0061]: this method takes 0 arguments but 1 argument was supplied + --> $DIR/shadowed-intrinsic-method.rs:27:7 + | +LL | c.borrow(()); + | ^^^^^^ -- unexpected argument of type `()` + | +note: the `borrow` call is resolved to the method in `std::borrow::Borrow`, shadowing the method of the same name on the inherent impl for `main::C` + --> $DIR/shadowed-intrinsic-method.rs:27:7 + | +LL | use std::borrow::Borrow; + | ------------------- `std::borrow::Borrow` imported here +... +LL | c.borrow(()); + | ^^^^^^ refers to `std::borrow::Borrow::borrow` +note: method defined here + --> $SRC_DIR/core/src/borrow.rs:LL:COL +help: you might have meant to call the other method; you can use the fully-qualified path to call it explicitly + | +LL - c.borrow(()); +LL + C::borrow(&mut c, ()); + | +help: remove the extra argument + | +LL - c.borrow(()); +LL + c.borrow(); + | + +error[E0061]: this method takes 0 arguments but 1 argument was supplied + --> $DIR/shadowed-intrinsic-method.rs:34:7 + | +LL | b.borrow(()); + | ^^^^^^ -- unexpected argument of type `()` + | +note: the `borrow` call is resolved to the method in `std::borrow::Borrow`, shadowing the method of the same name on the inherent impl for `main::` + --> $DIR/shadowed-intrinsic-method.rs:34:7 + | +LL | use std::borrow::Borrow; + | ------------------- `std::borrow::Borrow` imported here +... +LL | b.borrow(()); + | ^^^^^^ refers to `std::borrow::Borrow::borrow` +note: method defined here + --> $SRC_DIR/core/src/borrow.rs:LL:COL +help: you might have meant to call the other method; you can use the fully-qualified path to call it explicitly + | +LL - b.borrow(()); +LL + B::borrow(&mut b, ()); + | +help: remove the extra argument + | +LL - b.borrow(()); +LL + b.borrow(); + | + +error: aborting due to 4 previous errors + +For more information about this error, try `rustc --explain E0061`. diff --git a/tests/ui/mismatched_types/diagnostic-method-lookup-returns-sig-with-fewer-args.rs b/tests/ui/mismatched_types/diagnostic-method-lookup-returns-sig-with-fewer-args.rs index fd41beecb0a2a..26365d4d3e359 100644 --- a/tests/ui/mismatched_types/diagnostic-method-lookup-returns-sig-with-fewer-args.rs +++ b/tests/ui/mismatched_types/diagnostic-method-lookup-returns-sig-with-fewer-args.rs @@ -1,7 +1,7 @@ fn main() { let target: Target = create_target(); - target.get(0); // correct arguments work - target.get(10.0); // (used to crash here) + target.unique_name(0); // correct arguments work + target.unique_name(10.0); // (used to crash here) //~^ ERROR mismatched types } @@ -12,14 +12,14 @@ fn create_target() -> T { // unimplemented trait, but contains function with the same name pub trait RandomTrait { - fn get(&mut self); // but less arguments + fn unique_name(&mut self); // but less arguments } struct Target; impl Target { // correct function with arguments - pub fn get(&self, data: i32) { + pub fn unique_name(&self, data: i32) { unimplemented!() } } diff --git a/tests/ui/mismatched_types/diagnostic-method-lookup-returns-sig-with-fewer-args.stderr b/tests/ui/mismatched_types/diagnostic-method-lookup-returns-sig-with-fewer-args.stderr index 0f86916fcdae4..43aa923a2a758 100644 --- a/tests/ui/mismatched_types/diagnostic-method-lookup-returns-sig-with-fewer-args.stderr +++ b/tests/ui/mismatched_types/diagnostic-method-lookup-returns-sig-with-fewer-args.stderr @@ -1,16 +1,16 @@ error[E0308]: mismatched types - --> $DIR/diagnostic-method-lookup-returns-sig-with-fewer-args.rs:4:16 + --> $DIR/diagnostic-method-lookup-returns-sig-with-fewer-args.rs:4:24 | -LL | target.get(10.0); // (used to crash here) - | --- ^^^^ expected `i32`, found floating-point number +LL | target.unique_name(10.0); // (used to crash here) + | ----------- ^^^^ expected `i32`, found floating-point number | | | arguments to this method are incorrect | note: method defined here --> $DIR/diagnostic-method-lookup-returns-sig-with-fewer-args.rs:22:12 | -LL | pub fn get(&self, data: i32) { - | ^^^ --------- +LL | pub fn unique_name(&self, data: i32) { + | ^^^^^^^^^^^ --------- error: aborting due to 1 previous error diff --git a/tests/ui/suggestions/shadowed-lplace-method.fixed b/tests/ui/suggestions/shadowed-lplace-method.fixed index 87db01a3b230b..e7f6df9fff8fb 100644 --- a/tests/ui/suggestions/shadowed-lplace-method.fixed +++ b/tests/ui/suggestions/shadowed-lplace-method.fixed @@ -6,5 +6,5 @@ use std::rc::Rc; fn main() { let rc = Rc::new(RefCell::new(true)); - *std::cell::RefCell::<_>::borrow_mut(&rc) = false; //~ ERROR E0308 + *RefCell::borrow_mut(&rc) = false; //~ ERROR E0308 } diff --git a/tests/ui/suggestions/shadowed-lplace-method.stderr b/tests/ui/suggestions/shadowed-lplace-method.stderr index aab9e442007ff..dfd52b9b5587b 100644 --- a/tests/ui/suggestions/shadowed-lplace-method.stderr +++ b/tests/ui/suggestions/shadowed-lplace-method.stderr @@ -19,7 +19,7 @@ LL | *rc.borrow_mut() = false; help: you might have meant to call the other method; you can use the fully-qualified path to call it explicitly | LL - *rc.borrow_mut() = false; -LL + *std::cell::RefCell::<_>::borrow_mut(&rc) = false; +LL + *RefCell::borrow_mut(&rc) = false; | error: aborting due to 1 previous error From c28b7a9083ff4ed1cd9582cfe307bace5481ee76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sat, 19 Sep 2026 22:23:01 +0000 Subject: [PATCH 19/24] Use verbose suggestion for parenthetical `Fn` notation and fully-qualified path on ambiguous assoc item --- .../src/hir_ty_lowering/errors.rs | 4 +- ...dyn-any-to-fn-with-missing-generics.stderr | 6 ++- .../opaque-used-in-extraneous-argument.stderr | 14 +++++- .../future-incompatible-lint-group.stderr | 7 ++- tests/ui/suggestions/fn-trait-notation.stderr | 21 +++++++-- .../assertion-left-right-goal.stderr | 28 ++++++++++-- ...ity-lint-ambiguous_associated_items.stderr | 7 ++- ...-args-issue-136407.feature_disabled.stderr | 43 ++++++++++++++++--- ...nboxed-closure-sugar-not-used-on-fn.stderr | 14 +++++- 9 files changed, 121 insertions(+), 23 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs index 2a3728573f31e..0e5c01be85c85 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs @@ -96,7 +96,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { // Do not suggest the other syntax if we are in trait impl: // the desugaring would contain an associated type constraint. if !is_impl { - err.span_suggestion( + err.span_suggestion_verbose( span, "use parenthetical notation instead", fn_trait_to_string(self.tcx(), trait_segment, true), @@ -2036,7 +2036,7 @@ impl<'a, 'tcx> rustc_errors::Diagnostic<'a, ()> for AmbiguityBetweenVariantAndAs could_refer_to(DefKind::Variant, variant_def_id, ""); could_refer_to(mode.def_kind_for_diagnostics(), item_def_id, " also"); - lint.span_suggestion( + lint.span_suggestion_verbose( span, "use fully-qualified syntax", format!("<{} as {}>::{}", self_ty, tcx.item_name(bound_def_id), segment_ident), diff --git a/tests/ui/cast/dyn-any-to-fn-with-missing-generics.stderr b/tests/ui/cast/dyn-any-to-fn-with-missing-generics.stderr index c9b49e9ed2b25..8cc48b64e79fe 100644 --- a/tests/ui/cast/dyn-any-to-fn-with-missing-generics.stderr +++ b/tests/ui/cast/dyn-any-to-fn-with-missing-generics.stderr @@ -2,11 +2,15 @@ error[E0658]: the precise format of `Fn`-family traits' type parameters is subje --> $DIR/dyn-any-to-fn-with-missing-generics.rs:9:39 | LL | println!("{:?}",(vfnfer[0] as dyn Fn)(3)); - | ^^ help: use parenthetical notation instead: `Fn() -> ()` + | ^^ | = note: see issue #29625 for more information = help: add `#![feature(unboxed_closures)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date +help: use parenthetical notation instead + | +LL | println!("{:?}",(vfnfer[0] as dyn Fn() -> ())(3)); + | ++++++++ error[E0107]: missing generics for trait `Fn` --> $DIR/dyn-any-to-fn-with-missing-generics.rs:9:39 diff --git a/tests/ui/impl-trait/opaque-used-in-extraneous-argument.stderr b/tests/ui/impl-trait/opaque-used-in-extraneous-argument.stderr index ffa58f4faeeee..6cec3ea88d515 100644 --- a/tests/ui/impl-trait/opaque-used-in-extraneous-argument.stderr +++ b/tests/ui/impl-trait/opaque-used-in-extraneous-argument.stderr @@ -37,22 +37,32 @@ error[E0658]: the precise format of `Fn`-family traits' type parameters is subje --> $DIR/opaque-used-in-extraneous-argument.rs:5:19 | LL | fn frob() -> impl Fn + '_ {} - | ^^^^^^^^^^^^^^^^^ help: use parenthetical notation instead: `Fn(P) -> T` + | ^^^^^^^^^^^^^^^^^ | = note: see issue #29625 for more information = help: add `#![feature(unboxed_closures)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date +help: use parenthetical notation instead + | +LL - fn frob() -> impl Fn + '_ {} +LL + fn frob() -> impl Fn(P) -> T + '_ {} + | error[E0658]: the precise format of `Fn`-family traits' type parameters is subject to change --> $DIR/opaque-used-in-extraneous-argument.rs:5:19 | LL | fn frob() -> impl Fn + '_ {} - | ^^^^^^^^^^^^^^^^^ help: use parenthetical notation instead: `Fn(P) -> T` + | ^^^^^^^^^^^^^^^^^ | = note: see issue #29625 for more information = help: add `#![feature(unboxed_closures)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` +help: use parenthetical notation instead + | +LL - fn frob() -> impl Fn + '_ {} +LL + fn frob() -> impl Fn(P) -> T + '_ {} + | error[E0061]: this function takes 0 arguments but 1 argument was supplied --> $DIR/opaque-used-in-extraneous-argument.rs:17:20 diff --git a/tests/ui/lint/future-incompatible-lint-group.stderr b/tests/ui/lint/future-incompatible-lint-group.stderr index 8f234c6216065..353009dd2a93b 100644 --- a/tests/ui/lint/future-incompatible-lint-group.stderr +++ b/tests/ui/lint/future-incompatible-lint-group.stderr @@ -12,7 +12,7 @@ error: ambiguous associated item --> $DIR/future-incompatible-lint-group.rs:19:17 | LL | fn foo() -> Self::V { 0 } - | ^^^^^^^ help: use fully-qualified syntax: `::V` + | ^^^^^^^ | note: `V` could refer to the variant defined here --> $DIR/future-incompatible-lint-group.rs:8:10 @@ -32,6 +32,11 @@ note: the lint level is defined here LL | #![deny(future_incompatible)] | ^^^^^^^^^^^^^^^^^^^ = note: `#[deny(ambiguous_associated_items)]` implied by `#[deny(future_incompatible)]` +help: use fully-qualified syntax + | +LL - fn foo() -> Self::V { 0 } +LL + fn foo() -> ::V { 0 } + | error: aborting due to 1 previous error; 1 warning emitted diff --git a/tests/ui/suggestions/fn-trait-notation.stderr b/tests/ui/suggestions/fn-trait-notation.stderr index ef7d5fe362820..37cfcf3b8b5f8 100644 --- a/tests/ui/suggestions/fn-trait-notation.stderr +++ b/tests/ui/suggestions/fn-trait-notation.stderr @@ -2,31 +2,46 @@ error[E0658]: the precise format of `Fn`-family traits' type parameters is subje --> $DIR/fn-trait-notation.rs:4:8 | LL | F: Fn, - | ^^^^^^^^^^^^^^^^^^^^^ help: use parenthetical notation instead: `Fn(i32) -> i32` + | ^^^^^^^^^^^^^^^^^^^^^ | = note: see issue #29625 for more information = help: add `#![feature(unboxed_closures)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date +help: use parenthetical notation instead + | +LL - F: Fn, +LL + F: Fn(i32) -> i32, + | error[E0658]: the precise format of `Fn`-family traits' type parameters is subject to change --> $DIR/fn-trait-notation.rs:6:8 | LL | G: Fn<(i32, i32, ), Output = (i32, i32)>, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use parenthetical notation instead: `Fn(i32, i32) -> (i32, i32)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: see issue #29625 for more information = help: add `#![feature(unboxed_closures)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date +help: use parenthetical notation instead + | +LL - G: Fn<(i32, i32, ), Output = (i32, i32)>, +LL + G: Fn(i32, i32) -> (i32, i32), + | error[E0658]: the precise format of `Fn`-family traits' type parameters is subject to change --> $DIR/fn-trait-notation.rs:7:8 | LL | H: Fn<(i32,), Output = i32>, - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use parenthetical notation instead: `Fn(i32) -> i32` + | ^^^^^^^^^^^^^^^^^^^^^^^^ | = note: see issue #29625 for more information = help: add `#![feature(unboxed_closures)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date +help: use parenthetical notation instead + | +LL - H: Fn<(i32,), Output = i32>, +LL + H: Fn(i32) -> i32, + | error[E0059]: type parameter to bare `Fn` trait must be a tuple --> $DIR/fn-trait-notation.rs:4:8 diff --git a/tests/ui/traits/next-solver/assertion-left-right-goal.stderr b/tests/ui/traits/next-solver/assertion-left-right-goal.stderr index 40986169b177b..e714a969df07b 100644 --- a/tests/ui/traits/next-solver/assertion-left-right-goal.stderr +++ b/tests/ui/traits/next-solver/assertion-left-right-goal.stderr @@ -2,44 +2,64 @@ error[E0658]: the precise format of `Fn`-family traits' type parameters is subje --> $DIR/assertion-left-right-goal.rs:6:28 | LL | async fn new() -> impl Fn<()> { - | ^^^^^^ help: use parenthetical notation instead: `Fn() -> ()` + | ^^^^^^ | = note: see issue #29625 for more information = help: add `#![feature(unboxed_closures)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date +help: use parenthetical notation instead + | +LL - async fn new() -> impl Fn<()> { +LL + async fn new() -> impl Fn() -> () { + | error[E0658]: the precise format of `Fn`-family traits' type parameters is subject to change --> $DIR/assertion-left-right-goal.rs:6:28 | LL | async fn new() -> impl Fn<()> { - | ^^^^^^ help: use parenthetical notation instead: `Fn() -> ()` + | ^^^^^^ | = note: see issue #29625 for more information = help: add `#![feature(unboxed_closures)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` +help: use parenthetical notation instead + | +LL - async fn new() -> impl Fn<()> { +LL + async fn new() -> impl Fn() -> () { + | error[E0658]: the precise format of `Fn`-family traits' type parameters is subject to change --> $DIR/assertion-left-right-goal.rs:6:28 | LL | async fn new() -> impl Fn<()> { - | ^^^^^^ help: use parenthetical notation instead: `Fn() -> ()` + | ^^^^^^ | = note: see issue #29625 for more information = help: add `#![feature(unboxed_closures)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` +help: use parenthetical notation instead + | +LL - async fn new() -> impl Fn<()> { +LL + async fn new() -> impl Fn() -> () { + | error[E0658]: the precise format of `Fn`-family traits' type parameters is subject to change --> $DIR/assertion-left-right-goal.rs:6:28 | LL | async fn new() -> impl Fn<()> { - | ^^^^^^ help: use parenthetical notation instead: `Fn() -> ()` + | ^^^^^^ | = note: see issue #29625 for more information = help: add `#![feature(unboxed_closures)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` +help: use parenthetical notation instead + | +LL - async fn new() -> impl Fn<()> { +LL + async fn new() -> impl Fn() -> () { + | error[E0308]: mismatched types --> $DIR/assertion-left-right-goal.rs:6:35 diff --git a/tests/ui/type-alias-enum-variants/enum-variant-priority-lint-ambiguous_associated_items.stderr b/tests/ui/type-alias-enum-variants/enum-variant-priority-lint-ambiguous_associated_items.stderr index 918d05b5d6781..1cb7b9df6ff7b 100644 --- a/tests/ui/type-alias-enum-variants/enum-variant-priority-lint-ambiguous_associated_items.stderr +++ b/tests/ui/type-alias-enum-variants/enum-variant-priority-lint-ambiguous_associated_items.stderr @@ -2,7 +2,7 @@ error: ambiguous associated item --> $DIR/enum-variant-priority-lint-ambiguous_associated_items.rs:32:15 | LL | fn f() -> Self::V { 0 } - | ^^^^^^^ help: use fully-qualified syntax: `::V` + | ^^^^^^^ | note: `V` could refer to the variant defined here --> $DIR/enum-variant-priority-lint-ambiguous_associated_items.rs:22:5 @@ -17,6 +17,11 @@ LL | type V; = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see issue #57644 = note: `#[deny(ambiguous_associated_items)]` (part of `#[deny(future_incompatible)]`) on by default +help: use fully-qualified syntax + | +LL - fn f() -> Self::V { 0 } +LL + fn f() -> ::V { 0 } + | error: aborting due to 1 previous error diff --git a/tests/ui/unboxed-closures/missing-fn-trait-args-issue-136407.feature_disabled.stderr b/tests/ui/unboxed-closures/missing-fn-trait-args-issue-136407.feature_disabled.stderr index 2a62cdfec2566..33b304d63e4bb 100644 --- a/tests/ui/unboxed-closures/missing-fn-trait-args-issue-136407.feature_disabled.stderr +++ b/tests/ui/unboxed-closures/missing-fn-trait-args-issue-136407.feature_disabled.stderr @@ -2,11 +2,15 @@ error[E0658]: the precise format of `Fn`-family traits' type parameters is subje --> $DIR/missing-fn-trait-args-issue-136407.rs:9:18 | LL | pub fn shared() {} - | ^^ help: use parenthetical notation instead: `Fn() -> ()` + | ^^ | = note: see issue #29625 for more information = help: add `#![feature(unboxed_closures)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date +help: use parenthetical notation instead + | +LL | pub fn shared ()>() {} + | ++++++++ error[E0107]: missing generics for trait `Fn` --> $DIR/missing-fn-trait-args-issue-136407.rs:9:18 @@ -18,11 +22,15 @@ error[E0658]: the precise format of `Fn`-family traits' type parameters is subje --> $DIR/missing-fn-trait-args-issue-136407.rs:13:19 | LL | pub fn mutable() {} - | ^^^^^ help: use parenthetical notation instead: `FnMut() -> ()` + | ^^^^^ | = note: see issue #29625 for more information = help: add `#![feature(unboxed_closures)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date +help: use parenthetical notation instead + | +LL | pub fn mutable ()>() {} + | ++++++++ error[E0107]: missing generics for trait `FnMut` --> $DIR/missing-fn-trait-args-issue-136407.rs:13:19 @@ -34,11 +42,15 @@ error[E0658]: the precise format of `Fn`-family traits' type parameters is subje --> $DIR/missing-fn-trait-args-issue-136407.rs:17:16 | LL | pub fn once() {} - | ^^^^^^ help: use parenthetical notation instead: `FnOnce() -> ()` + | ^^^^^^ | = note: see issue #29625 for more information = help: add `#![feature(unboxed_closures)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date +help: use parenthetical notation instead + | +LL | pub fn once ()>() {} + | ++++++++ error[E0107]: missing generics for trait `FnOnce` --> $DIR/missing-fn-trait-args-issue-136407.rs:17:16 @@ -50,11 +62,15 @@ error[E0658]: the precise format of `Fn`-family traits' type parameters is subje --> $DIR/missing-fn-trait-args-issue-136407.rs:21:24 | LL | pub fn async_shared() {} - | ^^^^^^^ help: use parenthetical notation instead: `AsyncFn() -> ()` + | ^^^^^^^ | = note: see issue #29625 for more information = help: add `#![feature(unboxed_closures)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date +help: use parenthetical notation instead + | +LL | pub fn async_shared ()>() {} + | ++++++++ error[E0107]: missing generics for trait `AsyncFn` --> $DIR/missing-fn-trait-args-issue-136407.rs:21:24 @@ -66,11 +82,15 @@ error[E0658]: the precise format of `Fn`-family traits' type parameters is subje --> $DIR/missing-fn-trait-args-issue-136407.rs:25:25 | LL | pub fn async_mutable() {} - | ^^^^^^^^^^ help: use parenthetical notation instead: `AsyncFnMut() -> ()` + | ^^^^^^^^^^ | = note: see issue #29625 for more information = help: add `#![feature(unboxed_closures)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date +help: use parenthetical notation instead + | +LL | pub fn async_mutable ()>() {} + | ++++++++ error[E0107]: missing generics for trait `AsyncFnMut` --> $DIR/missing-fn-trait-args-issue-136407.rs:25:25 @@ -82,11 +102,15 @@ error[E0658]: the precise format of `Fn`-family traits' type parameters is subje --> $DIR/missing-fn-trait-args-issue-136407.rs:29:22 | LL | pub fn async_once() {} - | ^^^^^^^^^^^ help: use parenthetical notation instead: `AsyncFnOnce() -> ()` + | ^^^^^^^^^^^ | = note: see issue #29625 for more information = help: add `#![feature(unboxed_closures)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date +help: use parenthetical notation instead + | +LL | pub fn async_once ()>() {} + | ++++++++ error[E0107]: missing generics for trait `AsyncFnOnce` --> $DIR/missing-fn-trait-args-issue-136407.rs:29:22 @@ -98,11 +122,16 @@ error[E0658]: the precise format of `Fn`-family traits' type parameters is subje --> $DIR/missing-fn-trait-args-issue-136407.rs:33:23 | LL | pub fn with_output>() {} - | ^^^^^^^^^^^^^^^ help: use parenthetical notation instead: `Fn() -> ()` + | ^^^^^^^^^^^^^^^ | = note: see issue #29625 for more information = help: add `#![feature(unboxed_closures)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date +help: use parenthetical notation instead + | +LL - pub fn with_output>() {} +LL + pub fn with_output ()>() {} + | error[E0107]: trait takes 1 generic argument but 0 generic arguments were supplied --> $DIR/missing-fn-trait-args-issue-136407.rs:33:23 diff --git a/tests/ui/unboxed-closures/unboxed-closure-sugar-not-used-on-fn.stderr b/tests/ui/unboxed-closures/unboxed-closure-sugar-not-used-on-fn.stderr index e6f34d7e3b4b7..05f960dcf89a8 100644 --- a/tests/ui/unboxed-closures/unboxed-closure-sugar-not-used-on-fn.stderr +++ b/tests/ui/unboxed-closures/unboxed-closure-sugar-not-used-on-fn.stderr @@ -2,21 +2,31 @@ error[E0658]: the precise format of `Fn`-family traits' type parameters is subje --> $DIR/unboxed-closure-sugar-not-used-on-fn.rs:3:17 | LL | fn bar1(x: &dyn Fn<(), Output=()>) { - | ^^^^^^^^^^^^^^^^^ help: use parenthetical notation instead: `Fn() -> ()` + | ^^^^^^^^^^^^^^^^^ | = note: see issue #29625 for more information = help: add `#![feature(unboxed_closures)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date +help: use parenthetical notation instead + | +LL - fn bar1(x: &dyn Fn<(), Output=()>) { +LL + fn bar1(x: &dyn Fn() -> ()) { + | error[E0658]: the precise format of `Fn`-family traits' type parameters is subject to change --> $DIR/unboxed-closure-sugar-not-used-on-fn.rs:7:28 | LL | fn bar2(x: &T) where T: Fn<()> { - | ^^^^^^ help: use parenthetical notation instead: `Fn() -> ()` + | ^^^^^^ | = note: see issue #29625 for more information = help: add `#![feature(unboxed_closures)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date +help: use parenthetical notation instead + | +LL - fn bar2(x: &T) where T: Fn<()> { +LL + fn bar2(x: &T) where T: Fn() -> () { + | error: aborting due to 2 previous errors From 9c1bd2de3001dcc0a4bbea6424951c8d6f84b7a3 Mon Sep 17 00:00:00 2001 From: Cameron Steffen Date: Thu, 23 Jul 2026 09:01:30 -0500 Subject: [PATCH 20/24] Add mod_id to TypeckRootCtxt Use ModId more for visibility checks from TypeckRootCtxt. This just simplifies things a bit and adds consistency. --- compiler/rustc_hir_typeck/src/expr.rs | 39 +++++++------------ .../rustc_hir_typeck/src/fn_ctxt/_impl.rs | 2 +- .../src/fn_ctxt/suggestions.rs | 9 +---- .../rustc_hir_typeck/src/method/suggest.rs | 13 +------ compiler/rustc_hir_typeck/src/pat.rs | 4 +- .../rustc_hir_typeck/src/typeck_root_ctxt.rs | 7 +++- 6 files changed, 26 insertions(+), 48 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index a8898acf3a415..e8eca56bc92d1 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -2240,9 +2240,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let private_fields: Vec<&ty::FieldDef> = variant .fields .iter() - .filter(|field| { - !field.vis.is_accessible_from(tcx.parent_module(expr.hir_id), tcx) - }) + .filter(|field| !field.vis.is_accessible_from(self.mod_id, tcx)) .collect(); if !private_fields.is_empty() { @@ -2714,7 +2712,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .iter() .filter(|field| { skip_fields.iter().all(|&skip| skip.ident.name != field.name) - && self.is_field_suggestable(field, expr.hir_id, expr.span) + && self.is_field_suggestable(field, expr.span) }) .map(|field| field.name) .collect() @@ -3269,7 +3267,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } // try to add a suggestion in case the field is a nested field of a field of the Adt - let mod_id = self.tcx.parent_module(expr.hir_id).to_def_id(); let (ty, unwrap) = if let ty::Adt(def, args) = base_ty.kind() && (self.tcx.is_diagnostic_item(sym::Result, def.did()) || self.tcx.is_diagnostic_item(sym::Option, def.did())) @@ -3280,9 +3277,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } else { (base_ty, "") }; - for found_fields in - self.get_field_candidates_considering_privacy_for_diag(span, ty, mod_id, expr.hir_id) - { + for found_fields in self.get_field_candidates_considering_privacy_for_diag(span, ty) { let field_names = found_fields.iter().map(|field| field.0.name).collect::>(); let mut candidate_fields: Vec<_> = found_fields .into_iter() @@ -3292,8 +3287,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { &|candidate_field, _| candidate_field == field, candidate_field, vec![], - mod_id, - expr.hir_id, ) }) .map(|mut field_path| { @@ -3354,8 +3347,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { &self, span: Span, base_ty: Ty<'tcx>, - mod_id: DefId, - hir_id: HirId, ) -> Vec)>> { debug!("get_field_candidates(span: {:?}, base_t: {:?}", span, base_ty); @@ -3379,15 +3370,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Some struct, e.g. some that impl `Deref`, have all private fields // because you're expected to deref them to access the _real_ fields. // This, for example, will help us suggest accessing a field through a `Box`. - if fields.iter().all(|field| !field.vis.is_accessible_from(mod_id, tcx)) { + if fields + .iter() + .all(|field| !field.vis.is_accessible_from(self.mod_id, tcx)) + { return None; } return Some( fields .iter() .filter(move |field| { - field.vis.is_accessible_from(mod_id, tcx) - && self.is_field_suggestable(field, hir_id, span) + field.vis.is_accessible_from(self.mod_id, tcx) + && self.is_field_suggestable(field, span) }) // For compile-time reasons put a limit on number of fields we search .take(100) @@ -3419,15 +3413,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { /// This method is called after we have encountered a missing field error to recursively /// search for the field - #[instrument(skip(self, matches, mod_id, hir_id), level = "debug")] + #[instrument(skip(self, matches), level = "debug")] pub(crate) fn check_for_nested_field_satisfying_condition_for_diag( &self, span: Span, matches: &impl Fn(Ident, Ty<'tcx>) -> bool, (candidate_name, candidate_ty): (Ident, Ty<'tcx>), mut field_path: Vec, - mod_id: DefId, - hir_id: HirId, ) -> Option> { if field_path.len() > 3 { // For compile-time reasons and to avoid infinite recursion we only check for fields @@ -3438,12 +3430,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if matches(candidate_name, candidate_ty) { return Some(field_path); } - for nested_fields in self.get_field_candidates_considering_privacy_for_diag( - span, - candidate_ty, - mod_id, - hir_id, - ) { + for nested_fields in + self.get_field_candidates_considering_privacy_for_diag(span, candidate_ty) + { // recursively search fields of `candidate_field` if it's a ty::Adt for field in nested_fields { if let Some(field_path) = self.check_for_nested_field_satisfying_condition_for_diag( @@ -3451,8 +3440,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { matches, field, field_path.clone(), - mod_id, - hir_id, ) { return Some(field_path); } diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs index e655e0857d858..1aac339a29ee0 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs @@ -1238,7 +1238,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let (ctor_kind, ctor_def_id) = adt_def.non_enum_variant().ctor.unwrap(); // Check the visibility of the ctor. let vis = tcx.visibility(ctor_def_id); - if !vis.is_accessible_from(tcx.parent_module(hir_id).to_def_id(), tcx) { + if !vis.is_accessible_from(self.mod_id, tcx) { self.dcx() .emit_err(CtorIsPrivate { span, def: tcx.def_path_str(adt_def.did()) }); } diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs index ab8020a3182e0..8895faef0890f 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs @@ -2285,14 +2285,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } - pub(crate) fn is_field_suggestable( - &self, - field: &ty::FieldDef, - hir_id: HirId, - span: Span, - ) -> bool { + pub(crate) fn is_field_suggestable(&self, field: &ty::FieldDef, span: Span) -> bool { // The field must be visible in the containing module. - field.vis.is_accessible_from(self.tcx.parent_module(hir_id), self.tcx) + field.vis.is_accessible_from(self.mod_id, self.tcx) // The field must not be unstable. && !matches!( self.tcx.eval_stability(field.did, None, rustc_span::DUMMY_SP, None), diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index 545516f2e41a0..1627da52d7d0e 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -2836,8 +2836,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { _ => None, }); if let Some((field, field_ty)) = field_receiver { - let scope = tcx.parent_module_from_def_id(self.body_def_id); - let is_accessible = field.vis.is_accessible_from(scope, tcx); + let is_accessible = field.vis.is_accessible_from(self.mod_id, tcx); if is_accessible { if let Some((what, _, _)) = self.extract_callable_info(field_ty) { @@ -3199,13 +3198,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { return_type: Option>, ) { if let SelfSource::MethodCall(expr) = source { - let mod_id = self.tcx.parent_module(expr.hir_id).to_def_id(); - for fields in self.get_field_candidates_considering_privacy_for_diag( - span, - actual, - mod_id, - expr.hir_id, - ) { + for fields in self.get_field_candidates_considering_privacy_for_diag(span, actual) { let call_expr = self.tcx.hir_expect_expr(self.tcx.parent_hir_id(expr.hir_id)); let lang_items = self.tcx.lang_items(); @@ -3240,8 +3233,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }, candidate_field, vec![], - mod_id, - expr.hir_id, ) }) .map(|field_path| { diff --git a/compiler/rustc_hir_typeck/src/pat.rs b/compiler/rustc_hir_typeck/src/pat.rs index caffef6a217a8..10046d90e9fab 100644 --- a/compiler/rustc_hir_typeck/src/pat.rs +++ b/compiler/rustc_hir_typeck/src/pat.rs @@ -2163,7 +2163,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let accessible_unmentioned_fields: Vec<_> = unmentioned_fields .iter() .copied() - .filter(|(field, _)| self.is_field_suggestable(field, pat.hir_id, pat.span)) + .filter(|(field, _)| self.is_field_suggestable(field, pat.span)) .collect(); if !has_rest_pat { @@ -2336,7 +2336,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); if let [(field_def, field)] = unmentioned_fields.as_slice() - && self.is_field_suggestable(field_def, pat.hir_id, pat.span) + && self.is_field_suggestable(field_def, pat.span) { let suggested_name = find_best_match_for_name(&[field.name], pat_field.ident.name, None); diff --git a/compiler/rustc_hir_typeck/src/typeck_root_ctxt.rs b/compiler/rustc_hir_typeck/src/typeck_root_ctxt.rs index 37b753062c708..1c037cc2e2d7e 100644 --- a/compiler/rustc_hir_typeck/src/typeck_root_ctxt.rs +++ b/compiler/rustc_hir_typeck/src/typeck_root_ctxt.rs @@ -5,7 +5,7 @@ use rustc_hir::def_id::LocalDefId; use rustc_hir::{self as hir, HirId, HirIdMap}; use rustc_infer::infer::{InferCtxt, InferOk, OpaqueTypeStorageEntries, TyCtxtInferExt}; use rustc_middle::ty::{self, Ty, TyCtxt, TyVid, TypeVisitableExt, TypingMode}; -use rustc_span::def_id::LocalDefIdMap; +use rustc_span::def_id::{LocalDefIdMap, LocalModId}; use rustc_span::{Span, span_bug}; use rustc_trait_selection::traits::{self, FulfillmentEngine, FulfillmentError, TraitEngine}; use tracing::instrument; @@ -67,6 +67,9 @@ pub(crate) struct TypeckRootCtxt<'tcx> { /// we record that type variable here. This is later used to inform /// fallback. See the `fallback` module for details. pub(super) diverging_type_vars: RefCell>, + + /// Parent module + pub(super) mod_id: LocalModId, } impl<'tcx> Deref for TypeckRootCtxt<'tcx> { @@ -78,6 +81,7 @@ impl<'tcx> Deref for TypeckRootCtxt<'tcx> { impl<'tcx> TypeckRootCtxt<'tcx> { pub(crate) fn new(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> Self { + let mod_id = tcx.parent_module_from_def_id(def_id); let hir_owner = tcx.local_def_id_to_hir_id(def_id).owner; let infcx = tcx @@ -102,6 +106,7 @@ impl<'tcx> TypeckRootCtxt<'tcx> { deferred_asm_checks: RefCell::new(Vec::new()), deferred_repeat_expr_checks: RefCell::new(Vec::new()), diverging_type_vars: RefCell::new(Default::default()), + mod_id, } } From 06f21bf5fe782deca0739a9e37a463f326095078 Mon Sep 17 00:00:00 2001 From: Cameron Steffen Date: Thu, 23 Jul 2026 11:30:20 -0500 Subject: [PATCH 21/24] Restrict Visibility methods to ModId We generally expect Visibility to have ModId or LocalModId, so it seems good to restrict the impls as such. There is just one error path needing adjustment to check that we actually have a ModId. It should be okay since, if it is not a module, an error will be emitted elsewhere. --- compiler/rustc_middle/src/ty/mod.rs | 16 ++++++++-------- compiler/rustc_resolve/src/lib.rs | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index e23ce6b246f12..08c65bc11f2ff 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -428,19 +428,19 @@ impl Visibility { } } -impl> Visibility { - /// Returns `true` if an item with this visibility is accessible from the given module. - pub fn is_accessible_from(self, module: impl Into, tcx: TyCtxt<'_>) -> bool { +impl> Visibility { + /// Returns `true` if an item with this visibility is accessible from the given definition. + pub fn is_accessible_from(self, def_id: impl Into, tcx: TyCtxt<'_>) -> bool { match self { // Public items are visible everywhere. Visibility::Public => true, - Visibility::Restricted(id) => tcx.is_descendant_of(module, id), + Visibility::Restricted(id) => tcx.is_descendant_of(def_id, id.into()), } } pub fn partial_cmp( self, - vis: Visibility>, + vis: Visibility>, tcx: TyCtxt<'_>, ) -> Option { match (self, vis) { @@ -449,18 +449,18 @@ impl> Visibility { (Visibility::Restricted(_), Visibility::Public) => Some(Ordering::Less), (Visibility::Restricted(lhs_id), Visibility::Restricted(rhs_id)) => { let (lhs_id, rhs_id) = (lhs_id.into(), rhs_id.into()); - tcx.def_id_partial_cmp(lhs_id, rhs_id) + tcx.def_id_partial_cmp(lhs_id.to_def_id(), rhs_id.to_def_id()) } } } } -impl + Debug + Copy> Visibility { +impl + Debug + Copy> Visibility { /// Returns `true` if this visibility is strictly larger than the given visibility. #[track_caller] pub fn greater_than( self, - vis: Visibility + Debug + Copy>, + vis: Visibility + Debug + Copy>, tcx: TyCtxt<'_>, ) -> bool { match self.partial_cmp(vis, tcx) { diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 87479a4fbedd0..e479ef6a621c9 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -2431,7 +2431,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { self.pat_span_map.insert(node, span); } - fn is_accessible_from(&self, vis: Visibility>, module: Module<'ra>) -> bool { + fn is_accessible_from(&self, vis: Visibility>, module: Module<'ra>) -> bool { vis.is_accessible_from(module.nearest_parent_mod(), self.tcx) } From e710a600578f20128a6fa8c2a086bd618e6fbfc4 Mon Sep 17 00:00:00 2001 From: Cameron Steffen Date: Thu, 23 Jul 2026 11:37:23 -0500 Subject: [PATCH 22/24] Prefer ModId in more places Especially in adjust_ident_and_get_scope and is_accessible_from. --- compiler/rustc_hir_analysis/src/collect.rs | 5 +++ .../src/hir_ty_lowering/errors.rs | 5 +-- .../src/hir_ty_lowering/mod.rs | 16 ++++---- compiler/rustc_hir_typeck/src/expr.rs | 16 +++----- compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs | 5 +++ compiler/rustc_hir_typeck/src/method/probe.rs | 3 +- .../rustc_hir_typeck/src/method/suggest.rs | 5 +-- compiler/rustc_middle/src/ty/mod.rs | 4 +- compiler/rustc_privacy/src/lib.rs | 40 +++++-------------- .../src/error_reporting/traits/suggestions.rs | 2 +- 10 files changed, 43 insertions(+), 58 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index e581774747601..f1c330aaeed57 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -37,6 +37,7 @@ use rustc_middle::ty::{ self, AdtKind, Const, IsSuggestable, Ty, TyCtxt, TypeVisitableExt, TypingMode, Unnormalized, fold_regions, }; +use rustc_span::def_id::LocalModId; use rustc_span::{DUMMY_SP, Ident, Span, Symbol, bug, kw, span_bug, sym}; use rustc_trait_selection::error_reporting::traits::suggestions::NextTypeParamName; use rustc_trait_selection::infer::InferCtxtExt; @@ -497,6 +498,10 @@ impl<'tcx> HirTyLowerer<'tcx> for ItemCtxt<'tcx> { self.item_def_id } + fn mod_id(&self) -> LocalModId { + self.tcx.parent_module_from_def_id(self.item_def_id) + } + fn re_infer(&self, span: Span, reason: RegionInferReason<'_>) -> ty::Region<'tcx> { if let RegionInferReason::ObjectLifetimeDefault(sugg_sp) = reason { // FIXME: Account for trailing plus `dyn Trait+`, the need of parens in diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs index 2a3728573f31e..93608d16b246c 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs @@ -199,8 +199,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { .visible_traits() .filter(|trait_def_id| { let viz = tcx.visibility(*trait_def_id); - let def_id = self.item_def_id(); - viz.is_accessible_from(def_id, tcx) + viz.is_accessible_from(self.mod_id(), tcx) }) .collect(); @@ -568,7 +567,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { .map(|impl_def_id| tcx.impl_trait_header(impl_def_id)) .filter(|header| { // Consider only accessible traits - tcx.visibility(trait_def_id).is_accessible_from(self.item_def_id(), tcx) + tcx.visibility(trait_def_id).is_accessible_from(self.mod_id(), tcx) && header.polarity != ty::ImplPolarity::Negative }) .map(|header| header.trait_ref.instantiate_identity().skip_norm_wip().self_ty()) diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs index ecfe5d3c2c7c2..309757aa66021 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs @@ -48,7 +48,7 @@ use rustc_middle::ty::{ const_lit_matches_ty, fold_regions, }; use rustc_session::diagnostics::feature_err; -use rustc_span::def_id::ModId; +use rustc_span::def_id::{LocalModId, ModId}; use rustc_span::{DUMMY_SP, Ident, Span, bug, kw, span_bug, sym}; use rustc_trait_selection::infer::InferCtxtExt; use rustc_trait_selection::traits::{self, FulfillmentError}; @@ -142,6 +142,9 @@ pub trait HirTyLowerer<'tcx> { /// Returns the [`LocalDefId`] of the overarching item whose constituents get lowered. fn item_def_id(&self) -> LocalDefId; + /// Returns the containing module. + fn mod_id(&self) -> LocalModId; + /// Returns the region to use when a lifetime is omitted (and not elided). fn re_infer(&self, span: Span, reason: RegionInferReason<'_>) -> ty::Region<'tcx>; @@ -1814,7 +1817,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { ) -> Option<(ty::AssocItem, /*scope*/ ModId)> { let tcx = self.tcx(); - let (ident, def_scope) = tcx.adjust_ident_and_get_scope(ident, scope, self.item_def_id()); + let (ident, def_scope) = tcx.adjust_ident_and_get_scope(ident, scope, self.mod_id()); // We have already adjusted the item name above, so compare with `.normalize_to_macros_2_0()` // instead of calling `filter_by_name_and_kind` which would needlessly normalize the // `ident` again and again. @@ -1879,7 +1882,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { }) // Consider only accessible traits && tcx.visibility(*trait_def_id) - .is_accessible_from(self.item_def_id(), tcx) + .is_accessible_from(self.mod_id(), tcx) && tcx.all_impls(*trait_def_id) .any(|impl_def_id| { let header = tcx.impl_trait_header(impl_def_id); @@ -3424,7 +3427,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { } hir::TyKind::FieldOf(ty, hir::TyFieldPath { variant, field }) => self.lower_field_of( self.lower_ty(ty), - self.item_def_id(), + self.mod_id(), ty.span, hir_ty.hir_id, *variant, @@ -3478,7 +3481,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { fn lower_field_of( &self, ty: Ty<'tcx>, - item_def_id: LocalDefId, + mod_id: LocalModId, ty_span: Span, hir_id: HirId, variant: Option, @@ -3528,8 +3531,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { } (FIRST_VARIANT, def.non_enum_variant()) }; - let (ident, def_scope) = - tcx.adjust_ident_and_get_scope(field, def.did(), item_def_id); + let (ident, def_scope) = tcx.adjust_ident_and_get_scope(field, def.did(), mod_id); if let Some((field_idx, field)) = variant .fields .iter_enumerated() diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index e8eca56bc92d1..f33046c1561cb 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -2788,11 +2788,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { return Ty::new_error(self.tcx(), guar); } - let (ident, def_scope) = self.tcx.adjust_ident_and_get_scope( - field, - base_def.did(), - self.body_def_id, - ); + let (ident, def_scope) = + self.tcx.adjust_ident_and_get_scope(field, base_def.did(), self.mod_id); if let Some((idx, field)) = self.find_adt_field(*base_def, ident) { self.write_field_index(expr.hir_id, idx); @@ -3851,11 +3848,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .emit(); break; }; - let (subident, sub_def_scope) = self.tcx.adjust_ident_and_get_scope( - subfield, - variant.def_id, - self.body_def_id, - ); + let (subident, sub_def_scope) = + self.tcx.adjust_ident_and_get_scope(subfield, variant.def_id, self.mod_id); let Some((subindex, field)) = variant .fields @@ -3906,7 +3900,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let (ident, def_scope) = self.tcx.adjust_ident_and_get_scope( field, container_def.did(), - self.body_def_id, + self.mod_id, ); let fields = &container_def.non_enum_variant().fields; diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs index 3e2d35da5307d..b2b49dc06b7a6 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs @@ -22,6 +22,7 @@ use rustc_middle::ty::{ self, CantBeErased, Const, Flags, Ty, TyCtxt, TypeVisitableExt, TypingMode, Unnormalized, }; use rustc_session::Session; +use rustc_span::def_id::LocalModId; use rustc_span::{self, DUMMY_SP, ErrorGuaranteed, Ident, Span}; use rustc_trait_selection::error_reporting::TypeErrCtxt; use rustc_trait_selection::traits::{ @@ -239,6 +240,10 @@ impl<'tcx> HirTyLowerer<'tcx> for FnCtxt<'_, 'tcx> { self.body_def_id } + fn mod_id(&self) -> LocalModId { + self.mod_id + } + fn re_infer(&self, span: Span, reason: RegionInferReason<'_>) -> ty::Region<'tcx> { let v = match reason { RegionInferReason::Param(def) => { diff --git a/compiler/rustc_hir_typeck/src/method/probe.rs b/compiler/rustc_hir_typeck/src/method/probe.rs index 02b3255e795ac..79b8ef15d7e13 100644 --- a/compiler/rustc_hir_typeck/src/method/probe.rs +++ b/compiler/rustc_hir_typeck/src/method/probe.rs @@ -859,8 +859,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { let is_accessible = if let Some(name) = self.method_name { let item = candidate.item; let container_id = item.container_id(self.tcx); - let def_scope = - self.tcx.adjust_ident_and_get_scope(name, container_id, self.body_def_id).1; + let def_scope = self.tcx.adjust_ident_and_get_scope(name, container_id, self.mod_id).1; item.visibility(self.tcx).is_accessible_from(def_scope, self.tcx) } else { true diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index 1627da52d7d0e..bea91a9dc11b6 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -3982,11 +3982,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { { let parent_map = self.tcx.visible_parent_map(()); - let scope = self.tcx.parent_module_from_def_id(self.body_def_id); let (accessible_candidates, inaccessible_candidates): (Vec<_>, Vec<_>) = candidates.into_iter().partition(|id| { let vis = self.tcx.visibility(*id); - vis.is_accessible_from(scope, self.tcx) + vis.is_accessible_from(self.mod_id, self.tcx) // Visibility alone does not make `fn_name::Trait` an importable path. // We need to make sure all parent are modules, otherwise the path is not importable. && std::iter::successors(self.tcx.opt_parent(*id), |&id| self.tcx.opt_parent(id)) @@ -4044,7 +4043,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let accessible_sugg = sugg(accessible_candidates, true); let inaccessible_sugg = sugg(inaccessible_candidates, false); - let (module, _, _) = self.tcx.hir_get_module(scope); + let (module, _, _) = self.tcx.hir_get_module(self.mod_id); let span = module.spans.inject_use_span; handle_candidates(accessible_sugg, inaccessible_sugg, span); } diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index 08c65bc11f2ff..94345380a544e 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -2183,13 +2183,13 @@ impl<'tcx> TyCtxt<'tcx> { self, mut ident: Ident, scope: DefId, - item_id: LocalDefId, + mod_id: LocalModId, ) -> (Ident, ModId) { let scope = ident .span .normalize_to_macros_2_0_and_adjust(self.expn_that_defined(scope)) .and_then(|actual_expansion| actual_expansion.expn_data().parent_module) - .unwrap_or_else(|| self.parent_module_from_def_id(item_id).to_mod_id()); + .unwrap_or(mod_id.to_mod_id()); (ident, scope) } diff --git a/compiler/rustc_privacy/src/lib.rs b/compiler/rustc_privacy/src/lib.rs index 8430b78d75f1b..97475c8fdccf0 100644 --- a/compiler/rustc_privacy/src/lib.rs +++ b/compiler/rustc_privacy/src/lib.rs @@ -922,6 +922,7 @@ impl<'a, 'tcx> TestReachabilityVisitor<'a, 'tcx> { /// This pass performs remaining checks for fields in struct expressions and patterns. struct NamePrivacyVisitor<'tcx> { tcx: TyCtxt<'tcx>, + mod_id: LocalModId, maybe_typeck_results: Option<&'tcx ty::TypeckResults<'tcx>>, } @@ -938,7 +939,6 @@ impl<'tcx> NamePrivacyVisitor<'tcx> { // Checks that a field in a struct constructor (expression or pattern) is accessible. fn check_field( &self, - hir_id: hir::HirId, // ID of the field use use_ctxt: Span, // syntax context of the field name at the use site def: ty::AdtDef<'tcx>, // definition of the struct or enum field: &'tcx ty::FieldDef, @@ -949,8 +949,7 @@ impl<'tcx> NamePrivacyVisitor<'tcx> { // definition of the field let ident = Ident::new(sym::dummy, use_ctxt); - let (_, def_id) = - self.tcx.adjust_ident_and_get_scope(ident, def.did(), hir_id.owner.def_id); + let (_, def_id) = self.tcx.adjust_ident_and_get_scope(ident, def.did(), self.mod_id); !field.vis.is_accessible_from(def_id, self.tcx) } @@ -1023,7 +1022,6 @@ impl<'tcx> NamePrivacyVisitor<'tcx> { adt: ty::AdtDef<'tcx>, variant: &'tcx ty::VariantDef, fields: &[hir::ExprField<'tcx>], - hir_id: hir::HirId, span: Span, struct_span: Span, ) { @@ -1031,11 +1029,11 @@ impl<'tcx> NamePrivacyVisitor<'tcx> { for (vf_index, variant_field) in variant.fields.iter_enumerated() { let field = fields.iter().find(|f| self.typeck_results().field_index(f.hir_id) == vf_index); - let (hir_id, use_ctxt, span) = match field { - Some(field) => (field.hir_id, field.ident.span, field.span), - None => (hir_id, span, span), + let (use_ctxt, span) = match field { + Some(field) => (field.ident.span, field.span), + None => (span, span), }; - if self.check_field(hir_id, use_ctxt, adt, variant_field) { + if self.check_field(use_ctxt, adt, variant_field) { let name = match field { Some(field) => field.ident.name, None => variant_field.name, @@ -1069,31 +1067,16 @@ impl<'tcx> Visitor<'tcx> for NamePrivacyVisitor<'tcx> { // If the expression uses FRU we need to make sure all the unmentioned fields // are checked for privacy (RFC 736). Rather than computing the set of // unmentioned fields, just check them all. - self.check_expanded_fields( - adt, - variant, - fields, - base.hir_id, - base.span, - qpath.span(), - ); + self.check_expanded_fields(adt, variant, fields, base.span, qpath.span()); } hir::StructTailExpr::DefaultFields(span) => { - self.check_expanded_fields( - adt, - variant, - fields, - expr.hir_id, - span, - qpath.span(), - ); + self.check_expanded_fields(adt, variant, fields, span, qpath.span()); } hir::StructTailExpr::None | hir::StructTailExpr::NoneWithError(_) => { let mut failed_fields = vec![]; for field in fields { - let (hir_id, use_ctxt) = (field.hir_id, field.ident.span); let index = self.typeck_results().field_index(field.hir_id); - if self.check_field(hir_id, use_ctxt, adt, &variant.fields[index]) { + if self.check_field(field.ident.span, adt, &variant.fields[index]) { failed_fields.push((field.ident.name, field.ident.span, true)); } } @@ -1112,9 +1095,8 @@ impl<'tcx> Visitor<'tcx> for NamePrivacyVisitor<'tcx> { let variant = adt.variant_of_res(res); let mut failed_fields = vec![]; for field in fields { - let (hir_id, use_ctxt) = (field.hir_id, field.ident.span); let index = self.typeck_results().field_index(field.hir_id); - if self.check_field(hir_id, use_ctxt, adt, &variant.fields[index]) { + if self.check_field(field.ident.span, adt, &variant.fields[index]) { failed_fields.push((field.ident.name, field.ident.span, true)); } } @@ -1753,7 +1735,7 @@ pub fn provide(providers: &mut Providers) { fn check_mod_privacy(tcx: TyCtxt<'_>, mod_id: LocalModId) { // Check privacy of names not checked in previous compilation stages. - let mut visitor = NamePrivacyVisitor { tcx, maybe_typeck_results: None }; + let mut visitor = NamePrivacyVisitor { tcx, mod_id, maybe_typeck_results: None }; tcx.hir_visit_item_likes_in_module(mod_id, &mut visitor); // Check privacy of explicitly written types and traits as well as diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs index 8de0514b9ef27..fffe80be255ce 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -346,7 +346,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { let (adjusted_ident, def_scope) = self.tcx.adjust_ident_and_get_scope( field_ident, base_def.did(), - typeck_results.hir_owner.def_id, + self.tcx.parent_module_from_def_id(typeck_results.hir_owner.def_id), ); let Some((_, field_def)) = From 2532b72d46903b406fe00211c9698a58fdf6a2a1 Mon Sep 17 00:00:00 2001 From: Cameron Steffen Date: Thu, 23 Jul 2026 14:50:06 -0500 Subject: [PATCH 23/24] Use ModId more in late lint pass --- compiler/rustc_ast_lowering/src/index.rs | 5 ++--- compiler/rustc_hir/src/intravisit.rs | 10 ++++++--- compiler/rustc_hir_id/src/lib.rs | 14 ++++++++++++- .../rustc_hir_typeck/src/method/suggest.rs | 2 +- compiler/rustc_lint/src/late.rs | 21 ++++++++++--------- compiler/rustc_lint/src/levels.rs | 3 ++- compiler/rustc_lint/src/nonstandard_style.rs | 6 +++--- compiler/rustc_lint/src/passes.rs | 2 +- compiler/rustc_middle/src/hir/map.rs | 17 +++++++-------- compiler/rustc_passes/src/input_stats.rs | 4 ++-- src/librustdoc/html/span_map.rs | 7 ++++--- src/librustdoc/visit_ast.rs | 4 ++-- .../src/arbitrary_source_item_ordering.rs | 5 +++-- .../src/items_after_test_module.rs | 5 +++-- .../clippy_lints/src/redundant_test_prefix.rs | 2 +- 15 files changed, 63 insertions(+), 44 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/index.rs b/compiler/rustc_ast_lowering/src/index.rs index f1b6e196fe413..b00e98891d5e7 100644 --- a/compiler/rustc_ast_lowering/src/index.rs +++ b/compiler/rustc_ast_lowering/src/index.rs @@ -6,6 +6,7 @@ use rustc_hir::intravisit::Visitor; use rustc_hir::*; use rustc_index::IndexVec; use rustc_middle::ty::TyCtxt; +use rustc_span::def_id::CRATE_MOD_ID; use rustc_span::{DUMMY_SP, Span, span_bug}; use tracing::{debug, instrument}; @@ -48,9 +49,7 @@ pub(super) fn index_hir<'hir>( }; match item { - OwnerNode::Crate(citem) => { - collector.visit_mod(citem, citem.spans.inner_span, hir::CRATE_HIR_ID) - } + OwnerNode::Crate(citem) => collector.visit_mod(citem, citem.spans.inner_span, CRATE_MOD_ID), OwnerNode::Item(item) => collector.visit_item(item), OwnerNode::TraitItem(item) => collector.visit_trait_item(item), OwnerNode::ImplItem(item) => collector.visit_impl_item(item), diff --git a/compiler/rustc_hir/src/intravisit.rs b/compiler/rustc_hir/src/intravisit.rs index 9cd4b5d7d001f..fc8324fb443f8 100644 --- a/compiler/rustc_hir/src/intravisit.rs +++ b/compiler/rustc_hir/src/intravisit.rs @@ -68,7 +68,7 @@ use rustc_ast::Label; use rustc_ast::visit::{VisitorResult, try_visit, visit_opt, walk_list}; use rustc_attr_ir::Attribute; use rustc_hir_id::HirId; -use rustc_span::def_id::LocalDefId; +use rustc_span::def_id::{LocalDefId, LocalModId}; use rustc_span::{Ident, Span, Symbol}; use crate::hir::*; @@ -311,7 +311,7 @@ pub trait Visitor<'v>: Sized { fn visit_ident(&mut self, ident: Ident) -> Self::Result { walk_ident(self, ident) } - fn visit_mod(&mut self, m: &'v Mod<'v>, _s: Span, _n: HirId) -> Self::Result { + fn visit_mod(&mut self, m: &'v Mod<'v>, _s: Span, _id: LocalModId) -> Self::Result { walk_mod(self, m) } fn visit_foreign_item(&mut self, i: &'v ForeignItem<'v>) -> Self::Result { @@ -583,7 +583,11 @@ pub fn walk_item<'v, V: Visitor<'v>>(visitor: &mut V, item: &'v Item<'v>) -> V:: } ItemKind::Mod(ident, ref module) => { try_visit!(visitor.visit_ident(ident)); - try_visit!(visitor.visit_mod(module, item.span, item.hir_id())); + try_visit!(visitor.visit_mod( + module, + item.span, + LocalModId::new_unchecked(item.owner_id.def_id) + )); } ItemKind::ForeignMod { abi: _, items } => { walk_list!(visitor, visit_foreign_item_ref, items); diff --git a/compiler/rustc_hir_id/src/lib.rs b/compiler/rustc_hir_id/src/lib.rs index dce7e7fd31a36..0e01600f9cb3d 100644 --- a/compiler/rustc_hir_id/src/lib.rs +++ b/compiler/rustc_hir_id/src/lib.rs @@ -16,7 +16,7 @@ use rustc_data_structures::stable_hash::{ StableHash, StableHashCtxt, StableHasher, StableOrd, ToStableHashKey, }; use rustc_macros::{Decodable, Encodable, StableHash}; -use rustc_span::def_id::{CRATE_DEF_ID, DefId, DefIndex, DefPathHash, LocalDefId}; +use rustc_span::def_id::{CRATE_DEF_ID, DefId, DefIndex, DefPathHash, LocalDefId, LocalModId}; #[derive(Copy, Clone, PartialEq, Eq, Hash, Encodable, Decodable)] pub struct OwnerId { @@ -42,6 +42,12 @@ impl From for DefId { } } +impl From for OwnerId { + fn from(value: LocalModId) -> Self { + OwnerId { def_id: value.to_local_def_id() } + } +} + impl OwnerId { #[inline] pub fn to_def_id(self) -> DefId { @@ -141,6 +147,12 @@ impl fmt::Display for HirId { } } +impl From for HirId { + fn from(id: LocalModId) -> Self { + HirId::make_owner(id.to_local_def_id()) + } +} + rustc_data_structures::define_stable_id_collections!(HirIdMap, HirIdSet, HirIdMapEntry, HirId); rustc_data_structures::define_id_collections!( ItemLocalMap, diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index bea91a9dc11b6..aebfeac2e7e85 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -4043,7 +4043,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let accessible_sugg = sugg(accessible_candidates, true); let inaccessible_sugg = sugg(inaccessible_candidates, false); - let (module, _, _) = self.tcx.hir_get_module(self.mod_id); + let (module, _) = self.tcx.hir_get_module(self.mod_id); let span = module.spans.inject_use_span; handle_candidates(accessible_sugg, inaccessible_sugg, span); } diff --git a/compiler/rustc_lint/src/late.rs b/compiler/rustc_lint/src/late.rs index 0679d8abe3d78..ac185c65aa127 100644 --- a/compiler/rustc_lint/src/late.rs +++ b/compiler/rustc_lint/src/late.rs @@ -13,6 +13,7 @@ use rustc_middle::hir::nested_filter; use rustc_middle::ty::{self, TyCtxt}; use rustc_session::Session; use rustc_span::Span; +use rustc_span::def_id::CRATE_MOD_ID; use tracing::debug; use crate::passes::LateLintPassObject; @@ -75,9 +76,9 @@ impl<'tcx, T: LateLintPass<'tcx>> LateContextAndPass<'tcx, T> { self.context.param_env = old_param_env; } - fn process_mod(&mut self, m: &'tcx hir::Mod<'tcx>, n: HirId) { - lint_callback!(self, check_mod, m, n); - hir_visit::walk_mod(self, m); + fn process_mod(&mut self, module: &'tcx hir::Mod<'tcx>, id: LocalModId) { + lint_callback!(self, check_mod, module, id); + hir_visit::walk_mod(self, module); } } @@ -220,9 +221,9 @@ impl<'tcx, T: LateLintPass<'tcx>> hir_visit::Visitor<'tcx> for LateContextAndPas hir_visit::walk_ty(self, t); } - fn visit_mod(&mut self, m: &'tcx hir::Mod<'tcx>, _: Span, n: HirId) { + fn visit_mod(&mut self, m: &'tcx hir::Mod<'tcx>, _: Span, id: LocalModId) { if !self.context.only_module { - self.process_mod(m, n); + self.process_mod(m, id); } } @@ -385,17 +386,17 @@ fn late_lint_mod_inner<'tcx, T: LateLintPass<'tcx>>( actually_rustdoc: tcx.sess.opts.actually_rustdoc, }; - let (module, _span, hir_id) = tcx.hir_get_module(mod_id); + let (module, _span) = tcx.hir_get_module(mod_id); - cx.with_lint_attrs(hir_id, |cx| { + cx.with_lint_attrs(mod_id.into(), |cx| { // There is no module lint that will have the crate itself as an item, so check it here. - if hir_id == hir::CRATE_HIR_ID { + if mod_id == CRATE_MOD_ID { lint_callback!(cx, check_crate,); } - cx.process_mod(module, hir_id); + cx.process_mod(module, mod_id); - if hir_id == hir::CRATE_HIR_ID { + if mod_id == CRATE_MOD_ID { lint_callback!(cx, check_crate_post,); } }); diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs index 472835388620c..7f3d7fa8d2a10 100644 --- a/compiler/rustc_lint/src/levels.rs +++ b/compiler/rustc_lint/src/levels.rs @@ -26,6 +26,7 @@ use rustc_middle::lint::{ use rustc_middle::query::Providers; use rustc_middle::ty::{RegisteredTools, TyCtxt}; use rustc_session::Session; +use rustc_span::def_id::CRATE_MOD_ID; use rustc_span::{AttrId, DUMMY_SP, Span, Symbol, sym}; use tracing::{debug, instrument}; @@ -190,7 +191,7 @@ fn shallow_lint_levels_on(tcx: TyCtxt<'_>, owner: hir::OwnerId) -> ShallowLintLe hir::OwnerNode::ImplItem(item) => levels.visit_impl_item(item), hir::OwnerNode::Crate(mod_) => { levels.add_id(hir::CRATE_HIR_ID); - levels.visit_mod(mod_, mod_.spans.inner_span, hir::CRATE_HIR_ID) + levels.visit_mod(mod_, mod_.spans.inner_span, CRATE_MOD_ID) } hir::OwnerNode::Synthetic => unreachable!(), }, diff --git a/compiler/rustc_lint/src/nonstandard_style.rs b/compiler/rustc_lint/src/nonstandard_style.rs index a0db07e187cba..d1057c4f9ead3 100644 --- a/compiler/rustc_lint/src/nonstandard_style.rs +++ b/compiler/rustc_lint/src/nonstandard_style.rs @@ -11,7 +11,7 @@ use rustc_hir::{Attribute, GenericParamKind, PatExprKind, PatKind, find_attr}; use rustc_lint_defs::{declare_lint, declare_lint_pass}; use rustc_middle::hir::nested_filter::All; use rustc_middle::ty::AssocContainer; -use rustc_span::def_id::LocalDefId; +use rustc_span::def_id::{CRATE_MOD_ID, LocalDefId, LocalModId}; use rustc_span::{BytePos, Ident, Span, sym}; use rustc_structures::CrateType; @@ -328,8 +328,8 @@ impl NonSnakeCase { } impl<'tcx> LateLintPass<'tcx> for NonSnakeCase { - fn check_mod(&mut self, cx: &LateContext<'_>, _: &'tcx hir::Mod<'tcx>, id: hir::HirId) { - if id != hir::CRATE_HIR_ID { + fn check_mod(&mut self, cx: &LateContext<'_>, _: &'tcx hir::Mod<'tcx>, id: LocalModId) { + if id != CRATE_MOD_ID { return; } diff --git a/compiler/rustc_lint/src/passes.rs b/compiler/rustc_lint/src/passes.rs index f870bce669e58..6b7955db483f1 100644 --- a/compiler/rustc_lint/src/passes.rs +++ b/compiler/rustc_lint/src/passes.rs @@ -11,7 +11,7 @@ macro_rules! late_lint_methods { fn check_body_post(a: &rustc_hir::Body<'tcx>); fn check_crate(); fn check_crate_post(); - fn check_mod(a: &'tcx rustc_hir::Mod<'tcx>, b: rustc_hir::HirId); + fn check_mod(a: &'tcx rustc_hir::Mod<'tcx>, b: rustc_span::def_id::LocalModId); fn check_foreign_item(a: &'tcx rustc_hir::ForeignItem<'tcx>); fn check_item(a: &'tcx rustc_hir::Item<'tcx>); fn check_item_post(a: &'tcx rustc_hir::Item<'tcx>); diff --git a/compiler/rustc_middle/src/hir/map.rs b/compiler/rustc_middle/src/hir/map.rs index 1743f0d05dfcd..affadfd0c6aaf 100644 --- a/compiler/rustc_middle/src/hir/map.rs +++ b/compiler/rustc_middle/src/hir/map.rs @@ -409,11 +409,10 @@ impl<'tcx> TyCtxt<'tcx> { find_attr!(self.hir_krate_attrs(), RustcCoherenceIsCore) } - pub fn hir_get_module(self, module: LocalModId) -> (&'tcx Mod<'tcx>, Span, HirId) { - let hir_id = HirId::make_owner(module.to_local_def_id()); - match self.hir_owner_node(hir_id.owner) { - OwnerNode::Item(&Item { span, kind: ItemKind::Mod(_, m), .. }) => (m, span, hir_id), - OwnerNode::Crate(item) => (item, item.spans.inner_span, hir_id), + pub fn hir_get_module(self, module: LocalModId) -> (&'tcx Mod<'tcx>, Span) { + match self.hir_owner_node(module.into()) { + OwnerNode::Item(&Item { span, kind: ItemKind::Mod(_, m), .. }) => (m, span), + OwnerNode::Crate(item) => (item, item.spans.inner_span), node => panic!("not a module: {node:?}"), } } @@ -423,8 +422,8 @@ impl<'tcx> TyCtxt<'tcx> { where V: Visitor<'tcx>, { - let (top_mod, span, hir_id) = self.hir_get_module(CRATE_MOD_ID); - visitor.visit_mod(top_mod, span, hir_id) + let (top_mod, span) = self.hir_get_module(CRATE_MOD_ID); + visitor.visit_mod(top_mod, span, CRATE_MOD_ID) } /// Walks the attributes in a crate. @@ -1254,8 +1253,8 @@ fn upstream_crates(tcx: TyCtxt<'_>) -> Vec<(StableCrateId, Svh)> { pub(super) fn hir_module_items(tcx: TyCtxt<'_>, module_id: LocalModId) -> ModuleItems { let mut collector = ItemCollector::new(tcx, false); - let (hir_mod, span, hir_id) = tcx.hir_get_module(module_id); - collector.visit_mod(hir_mod, span, hir_id); + let (hir_mod, span) = tcx.hir_get_module(module_id); + collector.visit_mod(hir_mod, span, module_id); let ItemCollector { submodules, diff --git a/compiler/rustc_passes/src/input_stats.rs b/compiler/rustc_passes/src/input_stats.rs index f2a7cb6e46fca..961e6e63a81da 100644 --- a/compiler/rustc_passes/src/input_stats.rs +++ b/compiler/rustc_passes/src/input_stats.rs @@ -9,7 +9,7 @@ use rustc_data_structures::thousands::usize_with_underscores; use rustc_hir::{self as hir, AmbigArg, HirId, intravisit as hir_visit}; use rustc_middle::ty::TyCtxt; use rustc_span::Span; -use rustc_span::def_id::LocalDefId; +use rustc_span::def_id::{LocalDefId, LocalModId}; struct NodeStats { count: usize, @@ -277,7 +277,7 @@ impl<'v> hir_visit::Visitor<'v> for StatCollector<'v> { hir_visit::walk_body(self, b); } - fn visit_mod(&mut self, m: &'v hir::Mod<'v>, _s: Span, _n: HirId) { + fn visit_mod(&mut self, m: &'v hir::Mod<'v>, _s: Span, _id: LocalModId) { self.record("Mod", None, m); hir_visit::walk_mod(self, m) } diff --git a/src/librustdoc/html/span_map.rs b/src/librustdoc/html/span_map.rs index 424b3c94fcbb1..c1e6e23f41c38 100644 --- a/src/librustdoc/html/span_map.rs +++ b/src/librustdoc/html/span_map.rs @@ -8,6 +8,7 @@ use rustc_hir::intravisit::{self, Visitor}; use rustc_hir::{ExprKind, HirId, Item, ItemKind, Mod, Node, QPath}; use rustc_middle::hir::nested_filter; use rustc_middle::ty::{self, TyCtxt}; +use rustc_span::def_id::LocalModId; use rustc_span::{BytePos, ExpnKind}; use crate::clean::{self, PrimitiveType, rustc_span}; @@ -309,13 +310,13 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { } } - fn visit_mod(&mut self, m: &'tcx Mod<'tcx>, span: rustc_span::Span, id: HirId) { + fn visit_mod(&mut self, m: &'tcx Mod<'tcx>, span: rustc_span::Span, id: LocalModId) { // To make the difference between "mod foo {}" and "mod foo;". In case we "import" another // file, we want to link to it. Otherwise no need to create a link. if !span.overlaps(m.spans.inner_span) { // Now that we confirmed it's a file import, we want to get the span for the module // name only and not all the "mod foo;". - if let Node::Item(item) = self.tcx.hir_node(id) { + if let Node::Item(item) = self.tcx.hir_node_by_def_id(id.into()) { let (ident, _) = item.expect_mod(); self.matches.insert( ident.span.into(), @@ -324,7 +325,7 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { } } else { // If it's a "mod foo {}", we want to look to its documentation page. - self.extract_info_from_hir_id(id); + self.extract_info_from_hir_id(id.into()); } intravisit::walk_mod(self, m); } diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs index ae5a76545eefb..ee30c6d153c0b 100644 --- a/src/librustdoc/visit_ast.rs +++ b/src/librustdoc/visit_ast.rs @@ -14,7 +14,7 @@ use rustc_hir::{Node, find_attr}; use rustc_middle::hir::nested_filter; use rustc_middle::ty::TyCtxt; use rustc_span::Span; -use rustc_span::def_id::{CRATE_DEF_ID, LOCAL_CRATE}; +use rustc_span::def_id::{CRATE_DEF_ID, LOCAL_CRATE, LocalModId}; use rustc_span::symbol::{Symbol, kw}; use tracing::debug; @@ -642,7 +642,7 @@ impl<'tcx> Visitor<'tcx> for RustdocVisitor<'_, 'tcx> { self.is_importable_from_parent = prev; } - fn visit_mod(&mut self, _: &hir::Mod<'tcx>, _: Span, _: hir::HirId) { + fn visit_mod(&mut self, _: &hir::Mod<'tcx>, _: Span, _: LocalModId) { // Handled in `visit_item_inner` } diff --git a/src/tools/clippy/clippy_lints/src/arbitrary_source_item_ordering.rs b/src/tools/clippy/clippy_lints/src/arbitrary_source_item_ordering.rs index 8984184269421..5da0323167fe6 100644 --- a/src/tools/clippy/clippy_lints/src/arbitrary_source_item_ordering.rs +++ b/src/tools/clippy/clippy_lints/src/arbitrary_source_item_ordering.rs @@ -8,12 +8,13 @@ use clippy_utils::diagnostics::span_lint_and_note; use clippy_utils::is_cfg_test; use rustc_attr_ir::AttributeKind; use rustc_hir::{ - Attribute, FieldDef, HirId, ImplItemId, IsAuto, Item, ItemKind, Mod, OwnerId, QPath, TraitItemId, TyKind, Variant, + Attribute, FieldDef, ImplItemId, IsAuto, Item, ItemKind, Mod, OwnerId, QPath, TraitItemId, TyKind, Variant, VariantData, }; use rustc_lint::{LateContext, LateLintPass, LintContext, impl_lint_pass}; use rustc_middle::ty::{AssocKind, TyCtxt}; use rustc_span::{Ident, Symbol, bug}; +use rustc_span::def_id::LocalModId; declare_clippy_lint! { /// ### What it does @@ -486,7 +487,7 @@ impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering { } } - fn check_mod(&mut self, cx: &LateContext<'tcx>, module: &'tcx Mod<'tcx>, _: HirId) { + fn check_mod(&mut self, cx: &LateContext<'tcx>, module: &'tcx Mod<'tcx>, _: LocalModId) { struct CurItem<'a> { item: &'a Item<'a>, order: usize, diff --git a/src/tools/clippy/clippy_lints/src/items_after_test_module.rs b/src/tools/clippy/clippy_lints/src/items_after_test_module.rs index dac7a24bf2a8d..b09c77b849f5f 100644 --- a/src/tools/clippy/clippy_lints/src/items_after_test_module.rs +++ b/src/tools/clippy/clippy_lints/src/items_after_test_module.rs @@ -2,9 +2,10 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::source::SpanExt as _; use clippy_utils::{fulfill_or_allowed, is_cfg_test, is_from_proc_macro}; use rustc_errors::{Applicability, SuggestionStyle}; -use rustc_hir::{HirId, Item, ItemKind, Mod}; +use rustc_hir::{Item, ItemKind, Mod}; use rustc_lint::{LateContext, LateLintPass, declare_lint_pass}; use rustc_span::hygiene::AstPass; +use rustc_span::def_id::LocalModId; use rustc_span::{ExpnKind, sym}; declare_clippy_lint! { @@ -56,7 +57,7 @@ fn cfg_test_module<'tcx>(cx: &LateContext<'tcx>, item: &Item<'tcx>) -> bool { } impl LateLintPass<'_> for ItemsAfterTestModule { - fn check_mod(&mut self, cx: &LateContext<'_>, module: &Mod<'_>, _: HirId) { + fn check_mod(&mut self, cx: &LateContext<'_>, module: &Mod<'_>, _: LocalModId) { let mut items = module.item_ids.iter().map(|&id| cx.tcx.hir_item(id)); let Some((mod_pos, test_mod)) = items.by_ref().enumerate().find(|(_, item)| cfg_test_module(cx, item)) else { diff --git a/src/tools/clippy/clippy_lints/src/redundant_test_prefix.rs b/src/tools/clippy/clippy_lints/src/redundant_test_prefix.rs index 6f7750939fd95..db3814748719c 100644 --- a/src/tools/clippy/clippy_lints/src/redundant_test_prefix.rs +++ b/src/tools/clippy/clippy_lints/src/redundant_test_prefix.rs @@ -131,7 +131,7 @@ fn name_conflicts<'tcx>(cx: &LateContext<'tcx>, body: &'tcx Body<'_>, fn_name: S let id = body.id().hir_id; // Iterate over items in the same module/scope - let (module, _module_span, _module_hir) = tcx.hir_get_module(tcx.parent_module(id)); + let (module, _module_span) = tcx.hir_get_module(tcx.parent_module(id)); if module .item_ids .iter() From 837ce844f9b85ef4f2094907aee4fc6edfa694c5 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Sun, 20 Sep 2026 00:29:37 +0000 Subject: [PATCH 24/24] Add .seek_read_exact(), .seek_write_all() to std::os::windows::fs::FileExt * First pass at windows::fs::FileExt.seek_read_exact() * First pass at windows::fs::FileExt.seek_write_all() * Fix function signature in seek_read_exact(), duh * First pass at tests for .seek_read_exact(), seek_write_all() * Whitespace fix * Use hypothetical seek_read_exact_seek_write_all feature also for .seek_read_exact() * Tracking issues 162868 * Oops, fix seek_write_all() doc example, was using write_all_at() still * Add mocked test for windows FileExt trait * Spelling fixes * Expand test for windows FileExt trait to include almost all scenarios * Split three tests out of file_test_windows_fileext_trait() * Remove old versions of those 3 tests * Split remaining file_test_windows_fileext_trait() into case 4, 5 * More test cleanup, always test expected_offset where possible * Test read first for consistency * Use same doctsring examples as seek_read(), seek_write() * Missing period * Oops: actually call _exact(), _all() methods in case 2, 3 * Add missing seek_read_exact_seek_write_all feature flags in doc examples --- library/std/src/fs/tests.rs | 267 +++++++++++++++++++++++++++++++ library/std/src/os/windows/fs.rs | 119 ++++++++++++++ 2 files changed, 386 insertions(+) diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 148f1c32b08b9..a4c48ed0563ad 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -775,6 +775,273 @@ fn file_test_io_seek_read_write() { check!(fs::remove_file(&filename)); } +#[test] +#[cfg(windows)] +fn file_test_io_seek_read_exact_write_all() { + use crate::os::windows::fs::FileExt; + + let tmpdir = tmpdir(); + let filename = tmpdir.join("file_rt_io_file_test_seek_read_exact_write_all.txt"); + let mut buf = [0; 256]; + let write1 = "asdf"; + let write2 = "qwer-"; + let write3 = "-zxcv"; + let content = "qwer-asdf-zxcv"; + { + let oo = OpenOptions::new().create_new(true).write(true).read(true).clone(); + let mut rw = check!(oo.open(&filename)); + check!(rw.seek_write_all(write1.as_bytes(), 5)); + assert_eq!(check!(rw.stream_position()), 9); + check!(rw.seek_read_exact(&mut buf[..write1.len()], 5)); + assert_eq!(str::from_utf8(&buf[..write1.len()]), Ok(write1)); + assert_eq!(check!(rw.stream_position()), 9); + assert_eq!(check!(rw.seek(SeekFrom::Start(0))), 0); + assert_eq!(check!(rw.write(write2.as_bytes())), write2.len()); + assert_eq!(check!(rw.stream_position()), 5); + assert_eq!(check!(rw.read(&mut buf)), write1.len()); + assert_eq!(str::from_utf8(&buf[..write1.len()]), Ok(write1)); + assert_eq!(check!(rw.stream_position()), 9); + check!(rw.seek_read_exact(&mut buf[..write2.len()], 0)); + assert_eq!(str::from_utf8(&buf[..write2.len()]), Ok(write2)); + assert_eq!(check!(rw.stream_position()), 5); + check!(rw.seek_write_all(write3.as_bytes(), 9)); + assert_eq!(check!(rw.stream_position()), 14); + } + { + let mut read = check!(File::open(&filename)); + check!(read.seek_read_exact(&mut buf[..content.len()], 0)); + assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content)); + assert_eq!(check!(read.stream_position()), 14); + assert_eq!(check!(read.seek(SeekFrom::End(-5))), 9); + check!(read.seek_read_exact(&mut buf[..content.len()], 0)); + assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content)); + assert_eq!(check!(read.stream_position()), 14); + assert_eq!(check!(read.seek(SeekFrom::End(-5))), 9); + assert_eq!(check!(read.read(&mut buf)), write3.len()); + assert_eq!(str::from_utf8(&buf[..write3.len()]), Ok(write3)); + assert_eq!(check!(read.stream_position()), 14); + check!(read.seek_read_exact(&mut buf[..content.len()], 0)); + assert_eq!(str::from_utf8(&buf[..content.len()]), Ok(content)); + assert_eq!(check!(read.stream_position()), 14); + assert!(read.seek_read_exact(&mut buf, 14).is_err()); + assert!(read.seek_read_exact(&mut buf, 15).is_err()); + } + check!(fs::remove_file(&filename)); +} + +#[test] +#[cfg(windows)] +fn file_test_windows_fileext_trait_case_1() { + use crate::os::windows::fs::FileExt; + + // Test when seek_read_exact(), seek_write_all() are called with empty buffers. + struct MockFile {} + + impl FileExt for MockFile { + fn seek_read(&self, _buf: &mut [u8], _offset: u64) -> io::Result { + panic!("should not be called"); + } + + fn seek_write(&self, _buf: &[u8], _offset: u64) -> io::Result { + panic!("should not be called"); + } + } + + let mock_file = MockFile {}; + check!(mock_file.seek_read_exact(&mut [], 0)); + check!(mock_file.seek_write_all(&[], 0)); + check!(mock_file.seek_read_exact(&mut [], 420)); + check!(mock_file.seek_write_all(&[], 420)); +} + +#[test] +#[cfg(windows)] +fn file_test_windows_fileext_trait_case_2() { + use crate::os::windows::fs::FileExt; + + // Test when seek_read(), seek_write() return Ok(0) + struct MockFile { + expected_offset: u64, + } + + impl FileExt for MockFile { + fn seek_read(&self, _buf: &mut [u8], offset: u64) -> io::Result { + assert_eq!(offset, self.expected_offset); + Ok(0) + } + + fn seek_write(&self, _buf: &[u8], offset: u64) -> io::Result { + assert_eq!(offset, self.expected_offset); + Ok(0) + } + } + + { + let mock_file = MockFile { expected_offset: 0 }; + let mut buf = [0; 256]; + assert_eq!( + mock_file.seek_read_exact(&mut buf, 0).unwrap_err().kind(), + io::ErrorKind::UnexpectedEof + ); + assert_eq!(mock_file.seek_write_all(&buf, 0).unwrap_err().kind(), io::ErrorKind::WriteZero); + } + + { + let mock_file = MockFile { expected_offset: 420 }; + let mut buf = [0; 256]; + assert_eq!( + mock_file.seek_read_exact(&mut buf, 420).unwrap_err().kind(), + io::ErrorKind::UnexpectedEof + ); + assert_eq!( + mock_file.seek_write_all(&buf, 420).unwrap_err().kind(), + io::ErrorKind::WriteZero + ); + } +} + +#[test] +#[cfg(windows)] +fn file_test_windows_fileext_trait_case_3() { + use crate::os::windows::fs::FileExt; + + // Test that Err other than io::ErrorKind::Interrupted are propagated up. + struct MockFile { + expected_offset: u64, + } + + impl FileExt for MockFile { + fn seek_read(&self, _buf: &mut [u8], offset: u64) -> io::Result { + assert_eq!(offset, self.expected_offset); + Err(io::Error::new(io::ErrorKind::PermissionDenied, "seek_read")) + } + + fn seek_write(&self, _buf: &[u8], offset: u64) -> io::Result { + assert_eq!(offset, self.expected_offset); + Err(io::Error::new(io::ErrorKind::ConnectionRefused, "seek_write")) + } + } + + { + let mock_file = MockFile { expected_offset: 0 }; + let mut buf = [0; 256]; + assert_eq!( + mock_file.seek_read_exact(&mut buf, 0).unwrap_err().kind(), + io::ErrorKind::PermissionDenied + ); + assert_eq!( + mock_file.seek_write_all(&buf, 0).unwrap_err().kind(), + io::ErrorKind::ConnectionRefused + ); + } + + { + let mock_file = MockFile { expected_offset: 420 }; + let mut buf = [0; 256]; + assert_eq!( + mock_file.seek_read_exact(&mut buf, 420).unwrap_err().kind(), + io::ErrorKind::PermissionDenied + ); + assert_eq!( + mock_file.seek_write_all(&buf, 420).unwrap_err().kind(), + io::ErrorKind::ConnectionRefused + ); + } + // FIXME: Cover io::ErrorKind::Interrupted, but don't infinite loop ;) +} + +#[test] +#[cfg(windows)] +fn file_test_windows_fileext_trait_case_4() { + use crate::os::windows::fs::FileExt; + + const MSG: &[u8] = + b"The Rust programming language helps you write faster, more reliable software."; + + // Test when the entire read or write is satisfied by only one call to seek_read() or + // seek_write(), respectively. + struct MockFile { + expected_offset: u64, + } + + impl FileExt for MockFile { + fn seek_read(&self, buf: &mut [u8], offset: u64) -> io::Result { + assert_eq!(offset, self.expected_offset); + assert_eq!(buf.len(), MSG.len()); + assert_eq!(buf, &[0; MSG.len()]); + buf.copy_from_slice(MSG); + Ok(MSG.len()) + } + + fn seek_write(&self, buf: &[u8], offset: u64) -> io::Result { + assert_eq!(offset, self.expected_offset); + assert_eq!(buf.len(), MSG.len()); + assert_eq!(buf, MSG); + Ok(MSG.len()) + } + } + + { + let mock_file = MockFile { expected_offset: 0 }; + let mut buf = [0; MSG.len()]; + check!(mock_file.seek_read_exact(&mut buf, 0)); + assert_eq!(&buf, MSG); + check!(mock_file.seek_write_all(&buf, 0)); + } + + { + let mock_file = MockFile { expected_offset: 420 }; + let mut buf = [0; MSG.len()]; + check!(mock_file.seek_read_exact(&mut buf, 420)); + assert_eq!(&buf, MSG); + check!(mock_file.seek_write_all(&buf, 420)); + } +} + +#[test] +#[cfg(windows)] +fn file_test_windows_fileext_trait_case_5() { + use crate::os::windows::fs::FileExt; + + const MSG: &[u8] = + b"Rust is for students and those who are interested in learning about systems concepts."; + + // Test pathological case where seek_read(), seek_write() only do 1 byte per call, return Ok(1) + struct MockFile { + base_offset: u64, + } + + impl FileExt for MockFile { + fn seek_read(&self, buf: &mut [u8], offset: u64) -> io::Result { + let offset = (offset - self.base_offset) as usize; + buf[0..1].copy_from_slice(&MSG[offset..offset + 1]); + Ok(1) + } + + fn seek_write(&self, buf: &[u8], offset: u64) -> io::Result { + let offset = (offset - self.base_offset) as usize; + assert_eq!(buf[0..1], MSG[offset..offset + 1]); + Ok(1) + } + } + + { + let mock_file = MockFile { base_offset: 0 }; + let mut buf = [0; MSG.len()]; + check!(mock_file.seek_read_exact(&mut buf, 0)); + assert_eq!(&buf, MSG); + check!(mock_file.seek_write_all(&buf, 0)); + } + + { + let mock_file = MockFile { base_offset: 420 }; + let mut buf = [0; MSG.len()]; + check!(mock_file.seek_read_exact(&mut buf, 420)); + assert_eq!(&buf, MSG); + check!(mock_file.seek_write_all(&buf, 420)); + } +} + #[test] #[cfg(windows)] fn test_seek_read_buf() { diff --git a/library/std/src/os/windows/fs.rs b/library/std/src/os/windows/fs.rs index 21560638c1d0f..3e6a934f318b2 100644 --- a/library/std/src/os/windows/fs.rs +++ b/library/std/src/os/windows/fs.rs @@ -50,6 +50,69 @@ pub trait FileExt { #[stable(feature = "file_offset", since = "1.15.0")] fn seek_read(&self, buf: &mut [u8], offset: u64) -> io::Result; + /// Seeks to a given position and reads the exact number of bytes required to fill `buf`. + /// + /// The offset is relative to the start of the file and thus independent + /// from the current cursor. The current cursor **is** affected by this + /// function, it is set to the end of the read. + /// + /// Similar to [`io::Read::read_exact`] but uses [`seek_read`] instead of `read`. + /// + /// [`seek_read`]: FileExt::seek_read + /// + /// # Errors + /// + /// If this function encounters an error of the kind + /// [`io::ErrorKind::Interrupted`] then the error is ignored and the operation + /// will continue. + /// + /// If this function encounters an "end of file" before completely filling + /// the buffer, it returns an error of the kind [`io::ErrorKind::UnexpectedEof`]. + /// The contents of `buf` are unspecified in this case. + /// + /// If any other read error is encountered then this function immediately + /// returns. The contents of `buf` are unspecified in this case. + /// + /// If this function returns an error, it is unspecified how many bytes it + /// has read, but it will never read more than would be necessary to + /// completely fill the buffer. + /// + /// # Examples + /// + #[cfg_attr(windows, doc = "```no_run")] + #[cfg_attr(not(windows), doc = "```ignore (needs windows)")] + /// #![feature(seek_read_exact_seek_write_all)] + /// + /// use std::io; + /// use std::fs::File; + /// use std::os::windows::prelude::*; + /// + /// fn main() -> io::Result<()> { + /// let mut file = File::open("foo.txt")?; + /// let mut buffer = [0; 10]; + /// + /// // Read 10 bytes, starting 72 bytes from the + /// // start of the file. + /// file.seek_read_exact(&mut buffer[..], 72)?; + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "seek_read_exact_seek_write_all", issue = "162868")] + fn seek_read_exact(&self, mut buf: &mut [u8], mut offset: u64) -> io::Result<()> { + while !buf.is_empty() { + match self.seek_read(buf, offset) { + Ok(0) => break, + Ok(n) => { + buf = &mut buf[n..]; + offset += n as u64; + } + Err(ref e) if e.is_interrupted() => {} + Err(e) => return Err(e), + } + } + if !buf.is_empty() { Err(io::Error::READ_EXACT_EOF) } else { Ok(()) } + } + /// Seeks to a given position and reads some bytes into the buffer. /// /// This is equivalent to the [`seek_read`](FileExt::seek_read) method, except that it is passed @@ -122,6 +185,62 @@ pub trait FileExt { /// ``` #[stable(feature = "file_offset", since = "1.15.0")] fn seek_write(&self, buf: &[u8], offset: u64) -> io::Result; + + /// Seeks to a given position and attempts to write an entire buffer. + /// + /// The offset is relative to the start of the file and thus independent + /// from the current cursor. The current cursor **is** affected by this + /// function, it is set to the end of the write. + /// + /// This method will continuously call [`seek_write`] until there is no more data + /// to be written or an error of non-[`io::ErrorKind::Interrupted`] kind is + /// returned. This method will not return until the entire buffer has been + /// successfully written or such an error occurs. The first error that is + /// not of [`io::ErrorKind::Interrupted`] kind generated from this method will be + /// returned. + /// + /// # Errors + /// + /// This function will return the first error of + /// non-[`io::ErrorKind::Interrupted`] kind that [`seek_write`] returns. + /// + /// [`seek_write`]: FileExt::seek_write + /// + /// # Examples + /// + #[cfg_attr(windows, doc = "```no_run")] + #[cfg_attr(not(windows), doc = "```ignore (needs windows)")] + /// #![feature(seek_read_exact_seek_write_all)] + /// + /// use std::fs::File; + /// use std::os::windows::prelude::*; + /// + /// fn main() -> std::io::Result<()> { + /// let mut buffer = File::create("foo.txt")?; + /// + /// // Write a byte string starting 72 bytes from + /// // the start of the file. + /// buffer.seek_write_all(b"some bytes", 72)?; + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "seek_read_exact_seek_write_all", issue = "162868")] + fn seek_write_all(&self, mut buf: &[u8], mut offset: u64) -> io::Result<()> { + while !buf.is_empty() { + match self.seek_write(buf, offset) { + Ok(0) => { + return Err(io::Error::WRITE_ALL_EOF); + } + Ok(n) => { + buf = &buf[n..]; + offset += n as u64 + } + Err(ref e) if e.is_interrupted() => {} + Err(e) => return Err(e), + } + } + Ok(()) + } } #[stable(feature = "file_offset", since = "1.15.0")]