From dc14afd0fc0ab44f764777ec82198012cd442039 Mon Sep 17 00:00:00 2001 From: rajgandhi1 Date: Sun, 10 May 2026 20:47:36 +0530 Subject: [PATCH 01/13] fix improper ctypes in Znext solver --- .../rustc_lint/src/types/improper_ctypes.rs | 33 +++++++++++++------ ...sync-extern-fn-next-solver-issue-156352.rs | 15 +++++++++ 2 files changed, 38 insertions(+), 10 deletions(-) create mode 100644 tests/ui/lint/improper-ctypes/async-extern-fn-next-solver-issue-156352.rs diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index a1020962faedf..e495d870e9d3c 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -468,12 +468,14 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { base_ty: Unnormalized<'tcx, Ty<'tcx>>, base_fn_mode: CItemKind, ) -> Self { - ImproperCTypesVisitor { - cx, - base_ty: maybe_normalize_erasing_regions(cx, base_ty), - base_fn_mode, - cache: FxHashSet::default(), - } + // Don't normalize opaques: the new solver populates OpaqueTypeStorage during + // normalization, causing a delayed bug on InferCtxt drop (issue #156352). + let base_ty = if base_ty.skip_norm_wip().has_opaque_types() { + base_ty.skip_norm_wip() + } else { + maybe_normalize_erasing_regions(cx, base_ty) + }; + ImproperCTypesVisitor { cx, base_ty, base_fn_mode, cache: FxHashSet::default() } } /// Checks if the given indirection (box,ref,pointer) is "ffi-safe". @@ -894,6 +896,16 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { help: None, }, + // Can appear when the new solver reveals an opaque before the lint intercepts it. + ty::Closure(..) + | ty::CoroutineClosure(..) + | ty::Coroutine(..) + | ty::CoroutineWitness(..) => FfiUnsafe { + ty, + reason: msg!("closures and coroutines are not FFI-safe"), + help: None, + }, + ty::Param(..) | ty::Alias(ty::AliasTy { kind: ty::Projection { .. } | ty::Inherent { .. } | ty::Free { .. }, @@ -902,10 +914,6 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { | ty::Infer(..) | ty::Bound(..) | ty::Error(_) - | ty::Closure(..) - | ty::CoroutineClosure(..) - | ty::Coroutine(..) - | ty::CoroutineWitness(..) | ty::Placeholder(..) | ty::FnDef(..) => bug!("unexpected type in foreign function: {:?}", ty), } @@ -941,6 +949,11 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { state: VisitorState, ty: Unnormalized<'tcx, Ty<'tcx>>, ) -> FfiResult<'tcx> { + // Check before normalizing: the new solver can reveal opaques (e.g. + // `async extern fn` return → `Coroutine`), bypassing the opaque check below. + if let Some(res) = self.visit_for_opaque_ty(ty.skip_norm_wip()) { + return res; + } let ty = maybe_normalize_erasing_regions(self.cx, ty); if let Some(res) = self.visit_for_opaque_ty(ty) { return res; diff --git a/tests/ui/lint/improper-ctypes/async-extern-fn-next-solver-issue-156352.rs b/tests/ui/lint/improper-ctypes/async-extern-fn-next-solver-issue-156352.rs new file mode 100644 index 0000000000000..4624e0d0a81ea --- /dev/null +++ b/tests/ui/lint/improper-ctypes/async-extern-fn-next-solver-issue-156352.rs @@ -0,0 +1,15 @@ +//@ compile-flags: -Znext-solver=globally +//@ edition: 2021 +//@ check-pass + +// Regression test for . +// `async extern "system" fn` was ICE-ing with the new solver because +// `improper_ctypes_definitions` normalized the opaque return type all the way +// to a `Coroutine`, which hit an unexpected `bug!`. After the fix the lint +// fires with "opaque types have no C equivalent" instead of crashing. + +#![allow(improper_ctypes_definitions)] + +async extern "system" fn check_valid_subset(h: usize) {} + +fn main() {} From 9140c6c4023ac212116ab5a5bb39fdf97b1d3fd0 Mon Sep 17 00:00:00 2001 From: rajgandhi1 Date: Fri, 5 Jun 2026 15:40:57 +0530 Subject: [PATCH 02/13] constructs a TypingEnv with TypingMode::Borrowck now --- .../rustc_lint/src/types/improper_ctypes.rs | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index e495d870e9d3c..d25f843da8a41 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -147,7 +147,15 @@ fn maybe_normalize_erasing_regions<'tcx>( cx: &LateContext<'tcx>, value: Unnormalized<'tcx, Ty<'tcx>>, ) -> Ty<'tcx> { - cx.tcx.try_normalize_erasing_regions(cx.typing_env(), value).unwrap_or(value.skip_norm_wip()) + // Use `TypingMode::Borrowck` so the new solver doesn't reveal opaques and + // leak `OpaqueTypeStorage` state on `InferCtxt` drop (issue #156352). + let typing_env = if let Some(body_id) = cx.enclosing_body { + let body_def_id = cx.tcx.hir_enclosing_body_owner(body_id.hir_id); + ty::TypingEnv::new(cx.param_env, ty::TypingMode::borrowck(cx.tcx, body_def_id)) + } else { + cx.typing_env() + }; + cx.tcx.try_normalize_erasing_regions(typing_env, value).unwrap_or(value.skip_norm_wip()) } /// Check a variant of a non-exhaustive enum for improper ctypes @@ -468,8 +476,9 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { base_ty: Unnormalized<'tcx, Ty<'tcx>>, base_fn_mode: CItemKind, ) -> Self { - // Don't normalize opaques: the new solver populates OpaqueTypeStorage during - // normalization, causing a delayed bug on InferCtxt drop (issue #156352). + // Skip normalization for opaques: the new solver would put the revealed + // hidden type into the `InferCtxt`'s `OpaqueTypeStorage` and trigger a + // delayed bug on drop (issue #156352). let base_ty = if base_ty.skip_norm_wip().has_opaque_types() { base_ty.skip_norm_wip() } else { @@ -896,7 +905,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { help: None, }, - // Can appear when the new solver reveals an opaque before the lint intercepts it. + // Reachable when the new solver reveals a body's defining opaque. ty::Closure(..) | ty::CoroutineClosure(..) | ty::Coroutine(..) @@ -949,8 +958,9 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { state: VisitorState, ty: Unnormalized<'tcx, Ty<'tcx>>, ) -> FfiResult<'tcx> { - // Check before normalizing: the new solver can reveal opaques (e.g. - // `async extern fn` return → `Coroutine`), bypassing the opaque check below. + // Catch opaques before normalization so the new solver doesn't reveal them + // (e.g. `async extern fn` return → `Coroutine`) and we get the nicer + // "opaque types have no C equivalent" message. if let Some(res) = self.visit_for_opaque_ty(ty.skip_norm_wip()) { return res; } From d5435fceb27212b1fb2eaf2ca932f71ed8e487d5 Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Sat, 6 Jun 2026 03:08:40 +0800 Subject: [PATCH 03/13] Disable `tests/debuginfo/pretty-std.rs` `OsString` cdb check Since approx. Windows SDK 20348, the corresponding cdb (and/or its underlying WinDbg engine) changed or regressed the `OsStr`/`OsString` visualization, and no longer renders the emoji. Since approx. SDK/cdb 26100, the output formatting of the string containing UTF-8 (i.e. the multi-byte emoji grapheme) seems to have further regressed (e.g. the end quotation mark is no longer shown and command output becomes garbled). Relevant issues: * RUST-88840 * RUST-148743 * RUST-88796 --- tests/debuginfo/pretty-std.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/debuginfo/pretty-std.rs b/tests/debuginfo/pretty-std.rs index bf3102993d896..3ecaf02ab7d26 100644 --- a/tests/debuginfo/pretty-std.rs +++ b/tests/debuginfo/pretty-std.rs @@ -109,12 +109,15 @@ //@ cdb-check: [10] : 103 'g' [Type: char] //@ cdb-check: [11] : 33 '!' [Type: char] -//@ cdb-command: dx os_string -// NOTE: OSString is WTF-8 encoded which Windows debuggers don't understand. Verify the UTF-8 -// portion displays correctly. -//@ cdb-check:os_string : "IAMA OS string [...]" [Type: std::ffi::os_str::OsString] -//@ cdb-check: [] [Type: std::ffi::os_str::OsString] -//@ cdb-check: [chars] : "IAMA OS string [...]" +// FIXME(#88840, #148743, #88796): since approx. Windows SDK 20348, the corresponding cdb (and/or +// its underlying WinDbg engine) changed or regressed the `OsStr`/`OsString` visualization, and no +// longer renders the emoji. Since approx. 26100, the output formatting of the string containing +// UTF-8 (i.e. the multi-byte emoji grapheme) seems to have further regressed (e.g. the end +// quotation mark is no longer shown and command output becomes garbled). +//(DISABLED) @ cdb-command: dx os_string +//(DISABLED) @ cdb-check:os_string : "IAMA OS string 😃" [Type: std::ffi::os_str::OsString] +//(DISABLED) @ cdb-check: [] [Type: std::ffi::os_str::OsString] +//(DISABLED) @ cdb-check: [chars] : "IAMA OS string 😃" //@ cdb-command: dx some //@ cdb-check:some : Some [Type: enum2$ >] From 79df01ac96ac164c570bc16a1f3c566f7fc023d0 Mon Sep 17 00:00:00 2001 From: rajgandhi1 Date: Mon, 8 Jun 2026 15:33:35 +0530 Subject: [PATCH 04/13] change comment --- compiler/rustc_lint/src/types/improper_ctypes.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index d25f843da8a41..62a1ea5aac6ed 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -147,8 +147,9 @@ fn maybe_normalize_erasing_regions<'tcx>( cx: &LateContext<'tcx>, value: Unnormalized<'tcx, Ty<'tcx>>, ) -> Ty<'tcx> { - // Use `TypingMode::Borrowck` so the new solver doesn't reveal opaques and - // leak `OpaqueTypeStorage` state on `InferCtxt` drop (issue #156352). + // Use `TypingMode::Borrowck` so the new solver doesn't reveal opaque types since we're now + // past hir typeck. If we were to attempt to reveal more opaque types, dropping the + // `InferCtxt` would ICE (see #156352). let typing_env = if let Some(body_id) = cx.enclosing_body { let body_def_id = cx.tcx.hir_enclosing_body_owner(body_id.hir_id); ty::TypingEnv::new(cx.param_env, ty::TypingMode::borrowck(cx.tcx, body_def_id)) From c28609f4a02928c7c08438b12be8f3f9ec9b3868 Mon Sep 17 00:00:00 2001 From: rajgandhi1 Date: Mon, 8 Jun 2026 17:07:36 +0530 Subject: [PATCH 05/13] add comments to justify skip normalization --- compiler/rustc_lint/src/types/improper_ctypes.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index 62a1ea5aac6ed..b086d7e1fedb9 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -477,9 +477,9 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { base_ty: Unnormalized<'tcx, Ty<'tcx>>, base_fn_mode: CItemKind, ) -> Self { - // Skip normalization for opaques: the new solver would put the revealed - // hidden type into the `InferCtxt`'s `OpaqueTypeStorage` and trigger a - // delayed bug on drop (issue #156352). + // Skip normalization for opaques: even in `TypingMode::Borrowck` the body's own + // defining opaques still get revealed, leaving entries in `OpaqueTypeStorage` that + // ICE on `InferCtxt` drop (issue #156352). let base_ty = if base_ty.skip_norm_wip().has_opaque_types() { base_ty.skip_norm_wip() } else { @@ -906,7 +906,10 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { help: None, }, - // Reachable when the new solver reveals a body's defining opaque. + // Safety net for when normalization reveals a body's own defining opaque + // (e.g. `async extern fn`'s `impl Future` → `Coroutine`); the nicer + // "opaque types have no C equivalent" message comes from `visit_for_opaque_ty` + // in `check_type` before normalization (issue #156352). ty::Closure(..) | ty::CoroutineClosure(..) | ty::Coroutine(..) From 45d2cb8f1af00d76d4d98ba5e3fb7954135be167 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Fri, 5 Jun 2026 15:55:17 +0200 Subject: [PATCH 06/13] Avoid passing the parent (== owner) node id to `lower_generics` and load it from the per-owner info instead --- compiler/rustc_ast_lowering/src/item.rs | 35 ++++++++----------------- 1 file changed, 11 insertions(+), 24 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index 8ae2f3bda9f92..81b6e88fef82f 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -325,7 +325,6 @@ impl<'hir> LoweringContext<'_, 'hir> { let ident = self.lower_ident(*ident); let (generics, (ty, rhs)) = self.lower_generics( generics, - id, ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| { let ty = this.lower_ty_alloc( @@ -379,7 +378,7 @@ impl<'hir> LoweringContext<'_, 'hir> { ); let itctx = ImplTraitContext::Universal; - let (generics, decl) = this.lower_generics(generics, id, itctx, |this| { + let (generics, decl) = this.lower_generics(generics, itctx, |this| { this.lower_fn_decl(decl, id, *fn_sig_span, FnDeclKind::Fn, coroutine_kind) }); let sig = hir::FnSig { @@ -433,7 +432,6 @@ impl<'hir> LoweringContext<'_, 'hir> { add_ty_alias_where_clause(&mut generics, after_where_clause, true); let (generics, ty) = self.lower_generics( &generics, - id, ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| match ty { None => { @@ -460,7 +458,6 @@ impl<'hir> LoweringContext<'_, 'hir> { let ident = self.lower_ident(*ident); let (generics, variants) = self.lower_generics( generics, - id, ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| { this.arena.alloc_from_iter( @@ -474,7 +471,6 @@ impl<'hir> LoweringContext<'_, 'hir> { let ident = self.lower_ident(*ident); let (generics, struct_def) = self.lower_generics( generics, - id, ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| this.lower_variant_data(hir_id, i, struct_def), ); @@ -484,7 +480,6 @@ impl<'hir> LoweringContext<'_, 'hir> { let ident = self.lower_ident(*ident); let (generics, vdata) = self.lower_generics( generics, - id, ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| this.lower_variant_data(hir_id, i, vdata), ); @@ -512,7 +507,7 @@ impl<'hir> LoweringContext<'_, 'hir> { // parent lifetime. let itctx = ImplTraitContext::Universal; let (generics, (of_trait, lowered_ty)) = - self.lower_generics(ast_generics, id, itctx, |this| { + self.lower_generics(ast_generics, itctx, |this| { let of_trait = of_trait .as_deref() .map(|of_trait| this.lower_trait_impl_header(of_trait)); @@ -554,7 +549,6 @@ impl<'hir> LoweringContext<'_, 'hir> { let ident = self.lower_ident(*ident); let (generics, (safety, items, bounds)) = self.lower_generics( generics, - id, ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| { let bounds = this.lower_param_bounds( @@ -585,7 +579,6 @@ impl<'hir> LoweringContext<'_, 'hir> { let ident = self.lower_ident(*ident); let (generics, bounds) = self.lower_generics( generics, - id, ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| { this.lower_param_bounds( @@ -797,14 +790,13 @@ impl<'hir> LoweringContext<'_, 'hir> { ForeignItemKind::Fn(Fn { sig, ident, generics, define_opaque, .. }) => { let fdec = &sig.decl; let itctx = ImplTraitContext::Universal; - let (generics, (decl, fn_args)) = - self.lower_generics(generics, i.id, itctx, |this| { - ( - // Disallow `impl Trait` in foreign items. - this.lower_fn_decl(fdec, i.id, sig.span, FnDeclKind::ExternFn, None), - this.lower_fn_params_to_idents(fdec), - ) - }); + let (generics, (decl, fn_args)) = self.lower_generics(generics, itctx, |this| { + ( + // Disallow `impl Trait` in foreign items. + this.lower_fn_decl(fdec, i.id, sig.span, FnDeclKind::ExternFn, None), + this.lower_fn_params_to_idents(fdec), + ) + }); // Unmarked safety in unsafe block defaults to unsafe. let header = self.lower_fn_header(sig.header, hir::Safety::Unsafe, attrs); @@ -994,7 +986,6 @@ impl<'hir> LoweringContext<'_, 'hir> { }) => { let (generics, kind) = self.lower_generics( generics, - i.id, ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| { let ty = this.lower_ty_alloc( @@ -1096,7 +1087,6 @@ impl<'hir> LoweringContext<'_, 'hir> { add_ty_alias_where_clause(&mut generics, after_where_clause, false); let (generics, kind) = self.lower_generics( &generics, - i.id, ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| { let ty = ty.as_ref().map(|x| { @@ -1259,7 +1249,6 @@ impl<'hir> LoweringContext<'_, 'hir> { *ident, self.lower_generics( generics, - i.id, ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| { let ty = this.lower_ty_alloc( @@ -1304,7 +1293,6 @@ impl<'hir> LoweringContext<'_, 'hir> { *ident, self.lower_generics( &generics, - i.id, ImplTraitContext::Disallowed(ImplTraitPosition::Generic), |this| match ty { None => { @@ -1743,7 +1731,7 @@ impl<'hir> LoweringContext<'_, 'hir> { ) -> (&'hir hir::Generics<'hir>, hir::FnSig<'hir>) { let header = self.lower_fn_header(sig.header, hir::Safety::Safe, attrs); let itctx = ImplTraitContext::Universal; - let (generics, decl) = self.lower_generics(generics, id, itctx, |this| { + let (generics, decl) = self.lower_generics(generics, itctx, |this| { this.lower_fn_decl(&sig.decl, id, sig.span, kind, coroutine_kind) }); (generics, hir::FnSig { header, decl, span: self.lower_span(sig.span) }) @@ -1891,7 +1879,6 @@ impl<'hir> LoweringContext<'_, 'hir> { fn lower_generics( &mut self, generics: &Generics, - parent_node_id: NodeId, itctx: ImplTraitContext, f: impl FnOnce(&mut Self) -> T, ) -> (&'hir hir::Generics<'hir>, T) { @@ -1925,7 +1912,7 @@ impl<'hir> LoweringContext<'_, 'hir> { .collect(); // Introduce extra lifetimes if late resolution tells us to. - let extra_lifetimes = self.resolver.extra_lifetime_params(parent_node_id); + let extra_lifetimes = self.resolver.extra_lifetime_params(self.owner.id); params.extend(extra_lifetimes.into_iter().map(|&(ident, node_id, kind)| { self.lifetime_res_to_generic_param( ident, From b86914313c7542203d5bf47950e84b2014538e96 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Tue, 2 Jun 2026 13:42:14 +0200 Subject: [PATCH 07/13] Add more tests --- .../trait-bounds/duplicate-relaxed-bounds.rs | 15 +++++++++++++ .../duplicate-relaxed-bounds.stderr | 21 +++++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/tests/ui/trait-bounds/duplicate-relaxed-bounds.rs b/tests/ui/trait-bounds/duplicate-relaxed-bounds.rs index a1681ddec77b0..d240ce3f98c6a 100644 --- a/tests/ui/trait-bounds/duplicate-relaxed-bounds.rs +++ b/tests/ui/trait-bounds/duplicate-relaxed-bounds.rs @@ -10,6 +10,13 @@ trait Trait { type Type: ?Sized + ?Sized; //~^ ERROR duplicate relaxed `Sized` bounds //~| ERROR duplicate relaxed `Sized` bounds + + // Make sure, should we ever allow such where bounds, + // we still detect duplicate relaxed bounds. + type Type2: ?Sized + where + Self::Type: ?Sized; + //~^ ERROR this relaxed bound is not permitted here } // We used to emit an additional error about "multiple relaxed default bounds". @@ -19,4 +26,12 @@ trait Trait { fn not_dupes() {} //~^ ERROR bound modifier `?` can only be applied to `Sized` +// Demonstrate that we do detect dupes from where bounds +fn where_dupes() +//~^ ERROR duplicate relaxed `Sized` bounds +where + T: ?Sized, +{ +} + fn main() {} diff --git a/tests/ui/trait-bounds/duplicate-relaxed-bounds.stderr b/tests/ui/trait-bounds/duplicate-relaxed-bounds.stderr index ccc723fc7a653..1bed289eed1c2 100644 --- a/tests/ui/trait-bounds/duplicate-relaxed-bounds.stderr +++ b/tests/ui/trait-bounds/duplicate-relaxed-bounds.stderr @@ -1,3 +1,11 @@ +error: this relaxed bound is not permitted here + --> $DIR/duplicate-relaxed-bounds.rs:18:21 + | +LL | Self::Type: ?Sized; + | ^^^^^^ + | + = note: in this context, relaxed bounds are only allowed on type parameters defined on the closest item + error[E0203]: duplicate relaxed `Sized` bounds --> $DIR/duplicate-relaxed-bounds.rs:1:13 | @@ -23,11 +31,20 @@ LL | fn dupes() {} | ^^^^^^^^^ error: bound modifier `?` can only be applied to `Sized` - --> $DIR/duplicate-relaxed-bounds.rs:19:26 + --> $DIR/duplicate-relaxed-bounds.rs:26:26 | LL | fn not_dupes() {} | ^^^^^^^^^ +error[E0203]: duplicate relaxed `Sized` bounds + --> $DIR/duplicate-relaxed-bounds.rs:30:19 + | +LL | fn where_dupes() + | ^^^^^^ +... +LL | T: ?Sized, + | ^^^^^^ + error[E0203]: duplicate relaxed `Sized` bounds --> $DIR/duplicate-relaxed-bounds.rs:10:16 | @@ -42,6 +59,6 @@ LL | type Type: ?Sized + ?Sized; | = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` -error: aborting due to 7 previous errors +error: aborting due to 9 previous errors For more information about this error, try `rustc --explain E0203`. From a53c2c5ffc1dfa854b92f7a47fe5191917ffb9c1 Mon Sep 17 00:00:00 2001 From: Erick Tryzelaar Date: Thu, 4 Jun 2026 04:52:52 +0000 Subject: [PATCH 08/13] Extend rustc_public to add more ADT support This makes a few changes to rustc_public to make it a little easier to analyze ADT types. * It implements `CrateDef` and `CrateDefType` for `FieldDef`, which allows easy access to the underlying `DefId`, names, and tool annotations. * It adds `Crate::adts` to simplify stepping through all the ADTs in the crate. Note that I did use Gemini to assist with writing this patch, but I wrote most of it, reviewed all the vode, and verified the tests pass locally. --- .../rustc_public/src/compiler_interface.rs | 7 +++++ compiler/rustc_public/src/crate_def.rs | 26 ++++++++++++++++++ compiler/rustc_public/src/lib.rs | 7 ++++- compiler/rustc_public/src/ty.rs | 25 ++++------------- .../rustc_public_bridge/src/context/impls.rs | 10 +++++++ .../rustc_public/check_attribute.rs | 27 +++++++++++++++++-- .../ui-fulldeps/rustc_public/check_ty_fold.rs | 1 + 7 files changed, 80 insertions(+), 23 deletions(-) diff --git a/compiler/rustc_public/src/compiler_interface.rs b/compiler/rustc_public/src/compiler_interface.rs index 7953af37c1833..5512371b68b3a 100644 --- a/compiler/rustc_public/src/compiler_interface.rs +++ b/compiler/rustc_public/src/compiler_interface.rs @@ -136,6 +136,13 @@ impl<'tcx> CompilerInterface<'tcx> { }) } + pub(crate) fn crate_adts(&self, crate_num: CrateNum) -> Vec { + self.with_cx(|tables, cx| { + let krate = crate_num.internal(tables, cx.tcx); + cx.crate_adts(krate).iter().map(|did| tables.adt_def(*did)).collect() + }) + } + /// Retrieve all static items defined in this crate. pub(crate) fn crate_statics(&self, crate_num: CrateNum) -> Vec { self.with_cx(|tables, cx| { diff --git a/compiler/rustc_public/src/crate_def.rs b/compiler/rustc_public/src/crate_def.rs index 7773abdcb4d28..04ab2a1908e53 100644 --- a/compiler/rustc_public/src/crate_def.rs +++ b/compiler/rustc_public/src/crate_def.rs @@ -163,6 +163,32 @@ macro_rules! crate_def_with_ty { } } + impl CrateDefType for $name {} + }; + ( $(#[$attr:meta])* + $vis:vis $name:ident { + $( + $(#[$f_attr:meta])* + $f_vis:vis $f_name:ident: $f_ty:ty, + )* + } + ) => { + $(#[$attr])* + #[derive(Clone, PartialEq, Eq, Debug)] + $vis struct $name { + pub(crate) def: DefId, + $( + $(#[$f_attr])* + $f_vis $f_name: $f_ty, + )* + } + + impl CrateDef for $name { + fn def_id(&self) -> DefId { + self.def + } + } + impl CrateDefType for $name {} }; } diff --git a/compiler/rustc_public/src/lib.rs b/compiler/rustc_public/src/lib.rs index e1e02e282bf2e..be4886b8cceeb 100644 --- a/compiler/rustc_public/src/lib.rs +++ b/compiler/rustc_public/src/lib.rs @@ -35,7 +35,7 @@ pub use crate::error::*; use crate::mir::mono::StaticDef; use crate::mir::{Body, Mutability}; use crate::ty::{ - AssocItem, FnDef, ForeignModuleDef, ImplDef, ProvenanceMap, Span, TraitDef, Ty, + AdtDef, AssocItem, FnDef, ForeignModuleDef, ImplDef, ProvenanceMap, Span, TraitDef, Ty, serialize_index_impl, }; use crate::unstable::Stable; @@ -114,6 +114,11 @@ impl Crate { pub fn statics(&self) -> Vec { with(|cx| cx.crate_statics(self.id)) } + + /// Return a list of ADTs defined in this crate independent on their visibility. + pub fn adts(&self) -> Vec { + with(|cx| cx.crate_adts(self.id)) + } } #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Serialize)] diff --git a/compiler/rustc_public/src/ty.rs b/compiler/rustc_public/src/ty.rs index f71ca7213e9ed..951a701e7d439 100644 --- a/compiler/rustc_public/src/ty.rs +++ b/compiler/rustc_public/src/ty.rs @@ -897,26 +897,11 @@ impl VariantDef { } } -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub struct FieldDef { - /// The field definition. - pub(crate) def: DefId, - - /// The field name. - pub name: Symbol, -} - -impl FieldDef { - /// Retrieve the type of this field instantiating and normalizing it with the given arguments. - /// - /// This will assume the type can be instantiated with these arguments. - pub fn ty_with_args(&self, args: &GenericArgs) -> Ty { - with(|cx| cx.def_ty_with_args(self.def, args)) - } - - /// Retrieve the type of this field. - pub fn ty(&self) -> Ty { - with(|cx| cx.def_ty(self.def)) +crate_def_with_ty! { + #[derive(Serialize)] + pub FieldDef { + /// The field name. + pub name: Symbol, } } diff --git a/compiler/rustc_public_bridge/src/context/impls.rs b/compiler/rustc_public_bridge/src/context/impls.rs index 490dcbef8d79c..c309df59e9eca 100644 --- a/compiler/rustc_public_bridge/src/context/impls.rs +++ b/compiler/rustc_public_bridge/src/context/impls.rs @@ -110,6 +110,11 @@ impl<'tcx, B: Bridge> CompilerCtxt<'tcx, B> { matches!(self.tcx.def_kind(def_id), DefKind::Static { .. }).then(|| def_id) } + fn filter_adt_def(&self, def_id: DefId) -> Option { + matches!(self.tcx.def_kind(def_id), DefKind::Struct | DefKind::Enum | DefKind::Union) + .then(|| def_id) + } + pub fn target_endian(&self) -> Endian { self.tcx.data_layout.endian } @@ -152,6 +157,11 @@ impl<'tcx, B: Bridge> CompilerCtxt<'tcx, B> { filter_def_ids(self.tcx, crate_num, |def_id| self.filter_static_def(def_id)) } + /// Retrieve all ADTs defined in this crate. + pub fn crate_adts(&self, crate_num: CrateNum) -> Vec { + filter_def_ids(self.tcx, crate_num, |def_id| self.filter_adt_def(def_id)) + } + pub fn foreign_module(&self, mod_def: DefId) -> &ForeignModule { self.tcx.foreign_modules(mod_def.krate).get(&mod_def).unwrap() } diff --git a/tests/ui-fulldeps/rustc_public/check_attribute.rs b/tests/ui-fulldeps/rustc_public/check_attribute.rs index 393ff4c63c5ad..32ae1945ee3db 100644 --- a/tests/ui-fulldeps/rustc_public/check_attribute.rs +++ b/tests/ui-fulldeps/rustc_public/check_attribute.rs @@ -15,6 +15,7 @@ extern crate rustc_interface; #[macro_use] extern crate rustc_public; +use rustc_public::ty::AdtDef; use rustc_public::{CrateDef, CrateItems}; use std::io::Write; use std::ops::ControlFlow; @@ -25,14 +26,15 @@ const CRATE_NAME: &str = "input"; fn test_stable_mir() -> ControlFlow<()> { // Find items in the local crate. let items = rustc_public::all_local_items(); + let adts = rustc_public::local_crate().adts(); - test_tool(&items); + test_tool(&items, &adts); ControlFlow::Continue(()) } // Test tool attributes. -fn test_tool(items: &CrateItems) { +fn test_tool(items: &CrateItems, adts: &[AdtDef]) { let rustfmt_fn = *get_item(&items, "do_not_format").unwrap(); let rustfmt_attrs = rustfmt_fn.tool_attrs(&["rustfmt".to_string(), "skip".to_string()]); assert_eq!(rustfmt_attrs[0].as_str(), "#[rustfmt::skip]\n"); @@ -41,12 +43,24 @@ fn test_tool(items: &CrateItems) { let clippy_attrs = clippy_fn.tool_attrs(&["clippy".to_string(), "cyclomatic_complexity".to_string()]); assert_eq!(clippy_attrs[0].as_str(), "#[clippy::cyclomatic_complexity = \"100\"]\n"); + + let cake_struct = *get_adt(adts, "Cake").unwrap(); + let cake_variant = cake_struct.variants()[0]; + let fields = cake_variant.fields(); + let sugar_field = fields.iter().find(|f| f.name == "cake_sugar").unwrap(); + let cake_attrs = + sugar_field.tool_attrs(&["clippy".to_string(), "struct_field_names".to_string()]); + assert_eq!(cake_attrs[0].as_str(), "#[clippy::struct_field_names]\n"); } fn get_item<'a>(items: &'a CrateItems, name: &str) -> Option<&'a rustc_public::CrateItem> { items.iter().find(|crate_item| crate_item.trimmed_name() == name) } +fn get_adt<'a>(adts: &'a [AdtDef], name: &str) -> Option<&'a AdtDef> { + adts.iter().find(|adt| adt.trimmed_name() == name) +} + /// This test will generate and analyze a dummy crate using the stable mir. /// For that, it will first write the dummy crate into a file. /// Then it will create a `RustcPublic` using custom arguments and then @@ -87,6 +101,15 @@ fn generate_input(path: &str) -> std::io::Result<()> { #[derive(Debug, Clone, Copy)] struct Foo(u32); + // Field annotations. + pub struct Cake {{ + #[clippy::struct_field_names] + cake_sugar: u8, + #[clippy::struct_field_names] + cake_flour: u8, + cake_eggs: u8 + }} + // A rustfmt tool attribute. #[rustfmt::skip] fn do_not_format() {{}} diff --git a/tests/ui-fulldeps/rustc_public/check_ty_fold.rs b/tests/ui-fulldeps/rustc_public/check_ty_fold.rs index 9ac7f14570e57..5e3069123d5e9 100644 --- a/tests/ui-fulldeps/rustc_public/check_ty_fold.rs +++ b/tests/ui-fulldeps/rustc_public/check_ty_fold.rs @@ -16,6 +16,7 @@ extern crate rustc_interface; #[macro_use] extern crate rustc_public; +use rustc_public::CrateDefType; use rustc_public::mir::{ Body, FieldIdx, MirVisitor, Place, ProjectionElem, visit::{Location, PlaceContext}, From 656124ded88a772d26b7fffb99f1c75d558e5b4d Mon Sep 17 00:00:00 2001 From: CoCo-Japan-pan <115922543+CoCo-Japan-pan@users.noreply.github.com> Date: Thu, 4 Jun 2026 16:23:46 +0900 Subject: [PATCH 09/13] Reorder `impl` restriction rendering and add bottom margin --- src/librustdoc/html/render/print_item.rs | 8 ++++---- src/librustdoc/html/static/css/rustdoc.css | 4 ++++ .../impl/impl-restriction-document-private.rs | 8 ++++---- tests/rustdoc-html/impl/impl-restriction.rs | 8 ++++---- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 8385e691ed385..0ce02a4ee82eb 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -783,9 +783,6 @@ fn item_trait(cx: &Context<'_>, it: &clean::Item, t: &clean::Trait) -> impl fmt: } })?; - // Trait documentation - write!(w, "{}", document(cx, it, None, HeadingOffset::H2))?; - if let rustc_middle::ty::trait_def::ImplRestrictionKind::Restricted(def_id, _) = impl_restriction { @@ -793,7 +790,7 @@ fn item_trait(cx: &Context<'_>, it: &clean::Item, t: &clean::Trait) -> impl fmt: let v2; write!( w, - "
This trait cannot be implemented outside {}.
", + "
This trait cannot be implemented outside {}.
", if cx.cache().document_private { v1 = rustc_middle::ty::print::with_resolve_crate_name!(tcx.def_path_str(def_id)); @@ -805,6 +802,9 @@ fn item_trait(cx: &Context<'_>, it: &clean::Item, t: &clean::Trait) -> impl fmt: )?; } + // Trait documentation + write!(w, "{}", document(cx, it, None, HeadingOffset::H2))?; + fn trait_item(cx: &Context<'_>, m: &clean::Item, t: &clean::Item) -> impl fmt::Display { fmt::from_fn(|w| { let name = m.name.unwrap(); diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index da34deeefb197..1090d4e2feb01 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -1179,6 +1179,10 @@ div.where { margin-left: calc(var(--docblock-indent) + var(--impl-items-indent)); } +.impl-restriction { + margin-bottom: 0.75em; +} + #synthetic-implementors-list:not(.loaded), #implementors-list:not(.loaded) { /* To prevent layout shift when loading the page with extra implementors being loaded from JS, we hide the list until it's complete. */ diff --git a/tests/rustdoc-html/impl/impl-restriction-document-private.rs b/tests/rustdoc-html/impl/impl-restriction-document-private.rs index 6ea55eff54cf6..97975d4e0b6e1 100644 --- a/tests/rustdoc-html/impl/impl-restriction-document-private.rs +++ b/tests/rustdoc-html/impl/impl-restriction-document-private.rs @@ -2,14 +2,14 @@ #![crate_name = "c"] #![feature(impl_restriction)] -//@ matches c/trait.Foo.html '//*[@class="stab impl_restriction"]' \ +//@ matches c/trait.Foo.html '//*[@class="impl-restriction"]' \ // 'This trait cannot be implemented outside c.$' -//@ has c/trait.Foo.html '//*[@class="stab impl_restriction"]//code' 'c' +//@ has c/trait.Foo.html '//*[@class="impl-restriction"]//code' 'c' pub impl(crate) trait Foo {} pub mod inner { - //@ matches c/inner/trait.Bar.html '//*[@class="stab impl_restriction"]' \ + //@ matches c/inner/trait.Bar.html '//*[@class="impl-restriction"]' \ // 'This trait cannot be implemented outside c::inner.$' - //@ has c/inner/trait.Bar.html '//*[@class="stab impl_restriction"]//code' 'c::inner' + //@ has c/inner/trait.Bar.html '//*[@class="impl-restriction"]//code' 'c::inner' pub impl(self) trait Bar {} } diff --git a/tests/rustdoc-html/impl/impl-restriction.rs b/tests/rustdoc-html/impl/impl-restriction.rs index 11df67425f9b9..deb355959e7d2 100644 --- a/tests/rustdoc-html/impl/impl-restriction.rs +++ b/tests/rustdoc-html/impl/impl-restriction.rs @@ -1,14 +1,14 @@ #![crate_name = "c"] #![feature(impl_restriction)] -//@ matches c/trait.Foo.html '//*[@class="stab impl_restriction"]' \ +//@ matches c/trait.Foo.html '//*[@class="impl-restriction"]' \ // 'This trait cannot be implemented outside c.$' -//@ has c/trait.Foo.html '//*[@class="stab impl_restriction"]//code' 'c' +//@ has c/trait.Foo.html '//*[@class="impl-restriction"]//code' 'c' pub impl(crate) trait Foo {} pub mod inner { - //@ matches c/inner/trait.Bar.html '//*[@class="stab impl_restriction"]' \ + //@ matches c/inner/trait.Bar.html '//*[@class="impl-restriction"]' \ // 'This trait cannot be implemented outside c.$' - //@ has c/inner/trait.Bar.html '//*[@class="stab impl_restriction"]//code' 'c' + //@ has c/inner/trait.Bar.html '//*[@class="impl-restriction"]//code' 'c' pub impl(self) trait Bar {} } From 1b1e47550f3b2899beb8e2bb1a3a983ae2be39f0 Mon Sep 17 00:00:00 2001 From: steamproof <93405617+pbkx@users.noreply.github.com> Date: Mon, 8 Jun 2026 16:39:02 -0700 Subject: [PATCH 10/13] Add multibyte JSON diagnostic regression test --- .../multibyte-rendered-ansi-issue-157148.rs | 16 ++++++++++++++++ ...multibyte-rendered-ansi-issue-157148.stderr | 18 ++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 tests/ui/json/multibyte-rendered-ansi-issue-157148.rs create mode 100644 tests/ui/json/multibyte-rendered-ansi-issue-157148.stderr diff --git a/tests/ui/json/multibyte-rendered-ansi-issue-157148.rs b/tests/ui/json/multibyte-rendered-ansi-issue-157148.rs new file mode 100644 index 0000000000000..72e39856db9c2 --- /dev/null +++ b/tests/ui/json/multibyte-rendered-ansi-issue-157148.rs @@ -0,0 +1,16 @@ +// Regression test for . +// JSON rendered diagnostics should not ICE when spans involve non-ASCII source text. + +//@ edition: 2021 +//@ check-pass +//@ compile-flags: --error-format=json --json=diagnostic-rendered-ansi +//@ normalize-stderr: "(\\u001b\[[0-9;]+m)+" -> "[ANSI]" + +#![warn(unused_mut)] + +fn main() { + let mut x = 0; // 这是一段中文注释,用于在被下划线标注的行中加入多字节字符 + //~^ WARNING variable does not need to be mutable + //~| HELP remove this `mut` + let _ = x; +} diff --git a/tests/ui/json/multibyte-rendered-ansi-issue-157148.stderr b/tests/ui/json/multibyte-rendered-ansi-issue-157148.stderr new file mode 100644 index 0000000000000..268702281d88d --- /dev/null +++ b/tests/ui/json/multibyte-rendered-ansi-issue-157148.stderr @@ -0,0 +1,18 @@ +{"$message_type":"diagnostic","message":"variable does not need to be mutable","code":{"code":"unused_mut","explanation":null},"level":"warning","spans":[{"file_name":"$DIR/multibyte-rendered-ansi-issue-157148.rs","byte_start":365,"byte_end":370,"line_start":12,"line_end":12,"column_start":9,"column_end":14,"is_primary":true,"text":[{"text":" let mut x = 0; // 这是一段中文注释,用于在被下划线标注的行中加入多字节字符","highlight_start":9,"highlight_end":14}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"the lint level is defined here","code":null,"level":"note","spans":[{"file_name":"$DIR/multibyte-rendered-ansi-issue-157148.rs","byte_start":331,"byte_end":341,"line_start":9,"line_end":9,"column_start":9,"column_end":19,"is_primary":true,"text":[{"text":"#![warn(unused_mut)]","highlight_start":9,"highlight_end":19}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[],"rendered":null},{"message":"remove this `mut`","code":null,"level":"help","spans":[{"file_name":"$DIR/multibyte-rendered-ansi-issue-157148.rs","byte_start":365,"byte_end":369,"line_start":12,"line_end":12,"column_start":9,"column_end":13,"is_primary":true,"text":[{"text":" let mut x = 0; // 这是一段中文注释,用于在被下划线标注的行中加入多字节字符","highlight_start":9,"highlight_end":13}],"label":null,"suggested_replacement":"","suggestion_applicability":"MachineApplicable","expansion":null}],"children":[],"rendered":null}],"rendered":"[ANSI]warning[ANSI]: variable does not need to be mutable[ANSI] + [ANSI]--> [ANSI]$DIR/multibyte-rendered-ansi-issue-157148.rs:12:9 + [ANSI]|[ANSI] +[ANSI]LL[ANSI] [ANSI]|[ANSI] let mut x = 0; // 这是一段中文注释,用于在被下划线标注的行中加入多字节字符 + [ANSI]|[ANSI] [ANSI]----[ANSI]^[ANSI] + [ANSI]|[ANSI] [ANSI]|[ANSI] + [ANSI]|[ANSI] [ANSI]help: remove this `mut`[ANSI] + [ANSI]|[ANSI] +[ANSI]note[ANSI]: the lint level is defined here + [ANSI]--> [ANSI]$DIR/multibyte-rendered-ansi-issue-157148.rs:9:9 + [ANSI]|[ANSI] +[ANSI]LL[ANSI] [ANSI]|[ANSI] #![warn(unused_mut)] + [ANSI]|[ANSI] [ANSI]^^^^^^^^^^[ANSI] + +"} +{"$message_type":"diagnostic","message":"1 warning emitted","code":null,"level":"warning","spans":[],"children":[],"rendered":"[ANSI]warning[ANSI]: 1 warning emitted[ANSI] + +"} From 2a3a8dd3659316f54c1ce97a74a7f799c3a9eb54 Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 11 Mar 2026 17:34:32 +1000 Subject: [PATCH 11/13] Add splat builtin attribute & feature gate --- compiler/rustc_ast_passes/src/feature_gate.rs | 1 + .../rustc_attr_parsing/src/attributes/mod.rs | 1 + .../src/attributes/splat.rs | 16 ++++ compiler/rustc_attr_parsing/src/context.rs | 2 + compiler/rustc_feature/src/builtin_attrs.rs | 6 ++ compiler/rustc_feature/src/unstable.rs | 3 + .../rustc_hir/src/attrs/data_structures.rs | 3 + .../rustc_hir/src/attrs/encode_cross_crate.rs | 1 + compiler/rustc_passes/src/check_attr.rs | 1 + compiler/rustc_span/src/symbol.rs | 1 + tests/ui/feature-gates/feature-gate-splat.rs | 8 ++ .../feature-gates/feature-gate-splat.stderr | 14 +++ tests/ui/splat/splat-non-fn-arg.stderr | 90 +++++++++++++++++++ 13 files changed, 147 insertions(+) create mode 100644 compiler/rustc_attr_parsing/src/attributes/splat.rs create mode 100644 tests/ui/feature-gates/feature-gate-splat.rs create mode 100644 tests/ui/feature-gates/feature-gate-splat.stderr create mode 100644 tests/ui/splat/splat-non-fn-arg.stderr diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index 09672d1e69f11..fbe2251b5e688 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -500,6 +500,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) { gate_all!(pin_ergonomics, "pinned reference syntax is experimental"); gate_all!(postfix_match, "postfix match is experimental"); gate_all!(return_type_notation, "return type notation is experimental"); + gate_all!(splat, "`fn(#[splat] (a, ...))` is incomplete", "call as func((a, ...)) instead"); gate_all!(super_let, "`super let` is experimental"); gate_all!(try_blocks_heterogeneous, "`try bikeshed` expression is experimental"); gate_all!(unnamed_enum_variants, "unnamed enum variants are experimental"); diff --git a/compiler/rustc_attr_parsing/src/attributes/mod.rs b/compiler/rustc_attr_parsing/src/attributes/mod.rs index 325db4f0250b6..7ea4c7fc6fe73 100644 --- a/compiler/rustc_attr_parsing/src/attributes/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/mod.rs @@ -69,6 +69,7 @@ pub(crate) mod rustc_allocator; pub(crate) mod rustc_dump; pub(crate) mod rustc_internal; pub(crate) mod semantics; +pub(crate) mod splat; pub(crate) mod stability; pub(crate) mod test_attrs; pub(crate) mod traits; diff --git a/compiler/rustc_attr_parsing/src/attributes/splat.rs b/compiler/rustc_attr_parsing/src/attributes/splat.rs new file mode 100644 index 0000000000000..65fc9fb8123f4 --- /dev/null +++ b/compiler/rustc_attr_parsing/src/attributes/splat.rs @@ -0,0 +1,16 @@ +//! Attribute parsing for the `#[splat]` function argument overloading attribute. +//! This attribute modifies typecheck to support overload resolution, then modifies codegen for performance. + +use rustc_feature::AttributeStability; + +use super::prelude::*; + +pub(crate) struct SplatParser; + +impl NoArgsAttributeParser for SplatParser { + const PATH: &[Symbol] = &[sym::splat]; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Param)]); + const STABILITY: AttributeStability = + unstable!(splat, "the `#[splat]` attribute is experimental"); + const CREATE: fn(Span) -> AttributeKind = AttributeKind::Splat; +} diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 8da5058da7ac8..e3f376fa341ef 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -57,6 +57,7 @@ use crate::attributes::rustc_allocator::*; use crate::attributes::rustc_dump::*; use crate::attributes::rustc_internal::*; use crate::attributes::semantics::{ComptimeParser, *}; +use crate::attributes::splat::*; use crate::attributes::stability::*; use crate::attributes::test_attrs::*; use crate::attributes::traits::*; @@ -323,6 +324,7 @@ attribute_parsers!( Single>, Single>, Single>, + Single>, Single>, Single>, // tidy-alphabetical-end diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index 6f55becdc9a7a..a0adfd8814b72 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -208,6 +208,12 @@ pub static BUILTIN_ATTRIBUTES: &[Symbol] = &[ // - https://github.com/rust-lang/rust/issues/130494 sym::pin_v2, + // The `#[splat]` attribute is part of the `splat` experiment + // that improves the ergonomics of function overloading, tracked in: + // + // - https://github.com/rust-lang/rust/issues/153629 + sym::splat, + // ========================================================================== // Internal attributes: Stability, deprecation, and unsafe: // ========================================================================== diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index a8f0b4c38e828..e1f63b7c3dce8 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -721,6 +721,9 @@ declare_features! ( (unstable, sparc_target_feature, "1.84.0", Some(132783)), /// Allows specialization of implementations (RFC 1210). (incomplete, specialization, "1.7.0", Some(31844)), + /// Experimental "splatting" of function call arguments at the call site. + /// e.g. `foo(a, b, c)` calls `#[splat] fn foo((a: A, b: B, c: C))`. + (incomplete, splat, "CURRENT_RUSTC_VERSION", Some(153629)), /// Allows using `#[rustc_align_static(...)]` on static items. (unstable, static_align, "1.91.0", Some(146177)), /// Allows attributes on expressions and non-item statements. diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index f2b4041498c02..bdb181087122d 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -1589,6 +1589,9 @@ pub enum AttributeKind { reason: Option, }, + /// Represents `#[splat]` + Splat(Span), + /// Represents `#[stable]`, `#[unstable]` and `#[rustc_allowed_through_unstable_modules]`. Stability { stability: Stability, diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index 80eeb3400ec88..da03f039b8617 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -192,6 +192,7 @@ impl AttributeKind { RustcUnsafeSpecializationMarker => No, Sanitize { .. } => No, ShouldPanic { .. } => No, + Splat(..) => Yes, Stability { .. } => Yes, TargetFeature { .. } => No, TestRunner(..) => Yes, diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 5d12e762fdc0a..7df8c11ebb996 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -402,6 +402,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { AttributeKind::RustcUnsafeSpecializationMarker => (), AttributeKind::Sanitize { .. } => {} AttributeKind::ShouldPanic { .. } => (), + AttributeKind::Splat(..) => (), AttributeKind::Stability { .. } => (), AttributeKind::TestRunner(..) => (), AttributeKind::ThreadLocal => (), diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 75e2666db46cd..b846f2be1845f 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1976,6 +1976,7 @@ symbols! { specialization, speed, spirv, + splat, spotlight, sqrtf16, sqrtf32, diff --git a/tests/ui/feature-gates/feature-gate-splat.rs b/tests/ui/feature-gates/feature-gate-splat.rs new file mode 100644 index 0000000000000..ebcfc0e5a1d9f --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-splat.rs @@ -0,0 +1,8 @@ +#[rustfmt::skip] +fn tuple_args( + #[splat] //~ ERROR the `#[splat]` attribute is an experimental feature + (a, b, c): (u32, i8, char), +) { +} + +fn main() {} diff --git a/tests/ui/feature-gates/feature-gate-splat.stderr b/tests/ui/feature-gates/feature-gate-splat.stderr new file mode 100644 index 0000000000000..11ddc2a3b82e7 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-splat.stderr @@ -0,0 +1,14 @@ +error[E0658]: the `#[splat]` attribute is an experimental feature + --> $DIR/feature-gate-splat.rs:3:5 + | +LL | #[splat] + | ^^^^^^^^ + | + = note: see issue #153629 for more information + = help: add `#![feature(splat)]` 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: the `#[splat]` attribute is experimental + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/splat/splat-non-fn-arg.stderr b/tests/ui/splat/splat-non-fn-arg.stderr new file mode 100644 index 0000000000000..089636f3d4dff --- /dev/null +++ b/tests/ui/splat/splat-non-fn-arg.stderr @@ -0,0 +1,90 @@ +error: `#[splat]` attribute cannot be used on functions + --> $DIR/splat-non-fn-arg.rs:6:1 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can only be applied to function params + +error: `#[splat]` attribute cannot be used on traits + --> $DIR/splat-non-fn-arg.rs:9:1 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can only be applied to function params + +error: `#[splat]` attribute cannot be used on inherent impl blocks + --> $DIR/splat-non-fn-arg.rs:18:1 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can only be applied to function params + +error: `#[splat]` attribute cannot be used on inherent methods + --> $DIR/splat-non-fn-arg.rs:24:5 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can only be applied to function params + +error: `#[splat]` attribute cannot be used on inherent methods + --> $DIR/splat-non-fn-arg.rs:27:5 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can only be applied to function params + +error: `#[splat]` attribute cannot be used on trait impl blocks + --> $DIR/splat-non-fn-arg.rs:33:1 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can only be applied to function params + +error: `#[splat]` attribute cannot be used on foreign modules + --> $DIR/splat-non-fn-arg.rs:40:1 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can only be applied to function params + +error: `#[splat]` attribute cannot be used on foreign functions + --> $DIR/splat-non-fn-arg.rs:46:5 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can only be applied to function params + +error: `#[splat]` attribute cannot be used on modules + --> $DIR/splat-non-fn-arg.rs:50:1 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can only be applied to function params + +error: `#[splat]` attribute cannot be used on use statements + --> $DIR/splat-non-fn-arg.rs:53:1 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can only be applied to function params + +error: `#[splat]` attribute cannot be used on structs + --> $DIR/splat-non-fn-arg.rs:56:1 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can only be applied to function params + +error: aborting due to 11 previous errors + From 5a93925581ee6ddafbafc703aab0e6f327cc6547 Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 11 Mar 2026 17:34:32 +1000 Subject: [PATCH 12/13] Add AST validation for #[splat] --- .../rustc_ast_passes/src/ast_validation.rs | 48 +++++++++++- compiler/rustc_ast_passes/src/diagnostics.rs | 17 ++++ tests/ui/README.md | 6 ++ tests/ui/splat/splat-invalid.rs | 33 ++++++++ tests/ui/splat/splat-invalid.stderr | 77 +++++++++++++++++++ tests/ui/splat/splat-non-fn-arg.rs | 59 ++++++++++++++ 6 files changed, 238 insertions(+), 2 deletions(-) create mode 100644 tests/ui/splat/splat-invalid.rs create mode 100644 tests/ui/splat/splat-invalid.stderr create mode 100644 tests/ui/splat/splat-non-fn-arg.rs diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index 081de1ac2de6b..919a8e553263b 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -352,7 +352,8 @@ impl<'a> AstValidator<'a> { fn check_fn_decl(&self, fn_decl: &FnDecl, self_semantic: SelfSemantic) { self.check_decl_num_args(fn_decl); - self.check_decl_cvariadic_pos(fn_decl); + let c_variadic_span = self.check_decl_cvariadic_pos(fn_decl); + self.check_decl_splatting(fn_decl, c_variadic_span); self.check_decl_attrs(fn_decl); self.check_decl_self_param(fn_decl, self_semantic); } @@ -370,17 +371,59 @@ impl<'a> AstValidator<'a> { /// Emits an error if a function declaration has a variadic parameter in the /// beginning or middle of parameter list. /// Example: `fn foo(..., x: i32)` will emit an error. - fn check_decl_cvariadic_pos(&self, fn_decl: &FnDecl) { + /// If a C-variadic parameter is found, returns its span. + fn check_decl_cvariadic_pos(&self, fn_decl: &FnDecl) -> Option { + let mut c_variadic_span = None; + match &*fn_decl.inputs { [ps @ .., _] => { for Param { ty, span, .. } in ps { if let TyKind::CVarArgs = ty.kind { + c_variadic_span = Some(*span); self.dcx().emit_err(diagnostics::FnParamCVarArgsNotLast { span: *span }); } } } _ => {} } + + if let Some(Param { ty, span, .. }) = &fn_decl.inputs.last() + && let TyKind::CVarArgs = ty.kind + { + c_variadic_span = Some(*span); + } + + c_variadic_span + } + + /// Emits an error if a function declaration has more than one splatted argument, with a + /// C-variadic parameter, or a splat at an unsupported index (for performance). + /// Example: `fn foo(#[splat] x: (), #[splat] y: ())` will emit an error. + fn check_decl_splatting(&self, fn_decl: &FnDecl, c_variadic_span: Option) { + let (splatted_arg_indexes, mut splatted_spans): (Vec, Vec) = fn_decl + .inputs + .iter() + .enumerate() + .filter_map(|(index, arg)| { + arg.attrs + .iter() + .any(|attr| attr.has_name(sym::splat)) + .then_some((u16::try_from(index).unwrap(), arg.span)) + }) + .unzip(); + + // Multiple splatted arguments are invalid: we can't know which arguments go in each splat. + if splatted_arg_indexes.len() > 1 { + self.dcx() + .emit_err(diagnostics::DuplicateSplattedArgs { spans: splatted_spans.clone() }); + } + + if let Some(c_variadic_span) = c_variadic_span + && !splatted_spans.is_empty() + { + splatted_spans.push(c_variadic_span); + self.dcx().emit_err(diagnostics::CVarArgsAndSplat { spans: splatted_spans }); + } } fn check_decl_attrs(&self, fn_decl: &FnDecl) { @@ -396,6 +439,7 @@ impl<'a> AstValidator<'a> { sym::deny, sym::expect, sym::forbid, + sym::splat, sym::warn, ]; !attr.has_any_name(&arr) && rustc_attr_parsing::is_builtin_attr(*attr) diff --git a/compiler/rustc_ast_passes/src/diagnostics.rs b/compiler/rustc_ast_passes/src/diagnostics.rs index 89284ca3524fd..6877b33a564ff 100644 --- a/compiler/rustc_ast_passes/src/diagnostics.rs +++ b/compiler/rustc_ast_passes/src/diagnostics.rs @@ -123,6 +123,22 @@ pub(crate) struct FnParamCVarArgsNotLast { pub span: Span, } +#[derive(Diagnostic)] +#[diag("multiple `#[splat]`s are not allowed in the same function")] +#[help("remove `#[splat]` from all but one argument")] +pub(crate) struct DuplicateSplattedArgs { + #[primary_span] + pub spans: Vec, +} + +#[derive(Diagnostic)] +#[diag("`...` and `#[splat]` are not allowed in the same function")] +#[help("remove `#[splat]` or remove `...`")] +pub(crate) struct CVarArgsAndSplat { + #[primary_span] + pub spans: Vec, +} + #[derive(Diagnostic)] #[diag("documentation comments cannot be applied to function parameters")] pub(crate) struct FnParamDocComment { @@ -131,6 +147,7 @@ pub(crate) struct FnParamDocComment { pub span: Span, } +// FIXME(splat): add splat to the allowed built-in attributes when it is complete/stabilized #[derive(Diagnostic)] #[diag( "allow, cfg, cfg_attr, deny, expect, forbid, and warn are the only allowed built-in attributes in function parameters" diff --git a/tests/ui/README.md b/tests/ui/README.md index a1ec43ea3ba4f..6ffef1a1cd699 100644 --- a/tests/ui/README.md +++ b/tests/ui/README.md @@ -1291,6 +1291,12 @@ An assorted collection of tests that involves specific diagnostic spans. See [Tracking issue for specialization (RFC 1210) #31844](https://github.com/rust-lang/rust/issues/31844). +## `tests/ui/splat` + +Tests for the `#![feature(splat)]` attribute. + +See [Tracking Issue for argument splatting #153629](https://github.com/rust-lang/rust/issues/153629). + ## `tests/ui/stability-attribute/` Stability attributes used internally by the standard library: `#[stable()]` and `#[unstable()]`. diff --git a/tests/ui/splat/splat-invalid.rs b/tests/ui/splat/splat-invalid.rs new file mode 100644 index 0000000000000..314003459ef72 --- /dev/null +++ b/tests/ui/splat/splat-invalid.rs @@ -0,0 +1,33 @@ +//! Test using `#[splat]` incorrectly, in ways not covered by other tests. + +#![allow(incomplete_features)] +#![feature(splat)] +#![feature(c_variadic)] + +fn multisplat_bad(#[splat] (_a, _b): (u32, i8), #[splat] (_c, _d): (u32, i8)) {} +//~^ ERROR multiple `#[splat]`s are not allowed in the same function + +unsafe extern "C" fn splat_variadic(#[splat] (_a, _b): (u32, i8), varargs: ...) {} +//~^ ERROR `...` and `#[splat]` are not allowed in the same function + +unsafe extern "C" fn splat_variadic2(varargs: ..., #[splat] (_a, _b): (u32, i8)) {} +//~^ ERROR `...` and `#[splat]` are not allowed in the same function +//~| ERROR `...` must be the last argument of a C-variadic function + +extern "C" { + fn splat_variadic3(#[splat] (_a, _b): (u32, i8), ...) {} + //~^ ERROR incorrect function inside `extern` block + //~| ERROR `...` and `#[splat]` are not allowed in the same function + + fn splat_variadic4(..., #[splat] (_a, _b): (u32, i8)) {} + //~^ ERROR incorrect function inside `extern` block + //~| ERROR `...` and `#[splat]` are not allowed in the same function + //~| ERROR `...` must be the last argument of a C-variadic function + + // FIXME(splat): tuple layouts are unspecified. Should this error in addition to + // the existing `improper_ctypes` lint? + #[expect(improper_ctypes)] + fn bar_2(#[splat] _: (u32, i8)); +} + +fn main() {} diff --git a/tests/ui/splat/splat-invalid.stderr b/tests/ui/splat/splat-invalid.stderr new file mode 100644 index 0000000000000..d88cbf4ff0b6d --- /dev/null +++ b/tests/ui/splat/splat-invalid.stderr @@ -0,0 +1,77 @@ +error: multiple `#[splat]`s are not allowed in the same function + --> $DIR/splat-invalid.rs:7:19 + | +LL | fn multisplat_bad(#[splat] (_a, _b): (u32, i8), #[splat] (_c, _d): (u32, i8)) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: remove `#[splat]` from all but one argument + +error: `...` and `#[splat]` are not allowed in the same function + --> $DIR/splat-invalid.rs:10:37 + | +LL | unsafe extern "C" fn splat_variadic(#[splat] (_a, _b): (u32, i8), varargs: ...) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^ + | + = help: remove `#[splat]` or remove `...` + +error: `...` must be the last argument of a C-variadic function + --> $DIR/splat-invalid.rs:13:38 + | +LL | unsafe extern "C" fn splat_variadic2(varargs: ..., #[splat] (_a, _b): (u32, i8)) {} + | ^^^^^^^^^^^^ + +error: `...` and `#[splat]` are not allowed in the same function + --> $DIR/splat-invalid.rs:13:38 + | +LL | unsafe extern "C" fn splat_variadic2(varargs: ..., #[splat] (_a, _b): (u32, i8)) {} + | ^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: remove `#[splat]` or remove `...` + +error: incorrect function inside `extern` block + --> $DIR/splat-invalid.rs:18:8 + | +LL | extern "C" { + | ---------- `extern` blocks define existing foreign functions and functions inside of them cannot have a body +LL | fn splat_variadic3(#[splat] (_a, _b): (u32, i8), ...) {} + | ^^^^^^^^^^^^^^^ cannot have a body -- help: remove the invalid body: `;` + | + = help: you might have meant to write a function accessible through FFI, which can be done by writing `extern fn` outside of the `extern` block + = note: for more information, visit https://doc.rust-lang.org/std/keyword.extern.html + +error: `...` and `#[splat]` are not allowed in the same function + --> $DIR/splat-invalid.rs:18:24 + | +LL | fn splat_variadic3(#[splat] (_a, _b): (u32, i8), ...) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ + | + = help: remove `#[splat]` or remove `...` + +error: incorrect function inside `extern` block + --> $DIR/splat-invalid.rs:22:8 + | +LL | extern "C" { + | ---------- `extern` blocks define existing foreign functions and functions inside of them cannot have a body +... +LL | fn splat_variadic4(..., #[splat] (_a, _b): (u32, i8)) {} + | ^^^^^^^^^^^^^^^ cannot have a body -- help: remove the invalid body: `;` + | + = help: you might have meant to write a function accessible through FFI, which can be done by writing `extern fn` outside of the `extern` block + = note: for more information, visit https://doc.rust-lang.org/std/keyword.extern.html + +error: `...` must be the last argument of a C-variadic function + --> $DIR/splat-invalid.rs:22:24 + | +LL | fn splat_variadic4(..., #[splat] (_a, _b): (u32, i8)) {} + | ^^^ + +error: `...` and `#[splat]` are not allowed in the same function + --> $DIR/splat-invalid.rs:22:24 + | +LL | fn splat_variadic4(..., #[splat] (_a, _b): (u32, i8)) {} + | ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: remove `#[splat]` or remove `...` + +error: aborting due to 9 previous errors + diff --git a/tests/ui/splat/splat-non-fn-arg.rs b/tests/ui/splat/splat-non-fn-arg.rs new file mode 100644 index 0000000000000..6f2815e2b6579 --- /dev/null +++ b/tests/ui/splat/splat-non-fn-arg.rs @@ -0,0 +1,59 @@ +//! Test that using `#[splat]` on non-function-arguments is an error. + +#![allow(incomplete_features)] +#![feature(splat)] + +#[splat] //~ ERROR `#[splat]` attribute cannot be used on functions +fn tuple_args_bad((a, b): (u32, i8)) {} + +#[splat] //~ ERROR `#[splat]` attribute cannot be used on traits +trait FooTraitBad { + fn tuple_1(_: (u32,)); + + fn tuple_4(self, _: (u32, i8, (), f32)); +} + +struct Foo; + +#[splat] //~ ERROR `#[splat]` attribute cannot be used on inherent impl blocks +impl Foo { + fn tuple_1_bad((a,): (u32,)) {} +} + +impl Foo { + #[splat] //~ ERROR `#[splat]` attribute cannot be used on inherent methods + fn tuple_3_bad((a, b, c): (u32, i32, i8)) {} + + #[splat] //~ ERROR `#[splat]` attribute cannot be used on inherent methods + fn tuple_2_bad(self, (a, b): (u32, i8)) -> u32 { + a + } +} + +#[splat] //~ ERROR `#[splat]` attribute cannot be used on trait impl blocks +impl FooTraitBad for Foo { + fn tuple_1(_: (u32,)) {} + + fn tuple_4(self, _: (u32, i8, (), f32)) {} +} + +#[splat] //~ ERROR `#[splat]` attribute cannot be used on foreign modules +extern "C" { + fn foo_2(_: (u32, i8)); +} + +extern "C" { + #[splat] //~ ERROR `#[splat]` attribute cannot be used on foreign functions + fn bar_2_bad(_: (u32, i8)); +} + +#[splat] //~ ERROR `#[splat]` attribute cannot be used on modules +mod foo_mod {} + +#[splat] //~ ERROR `#[splat]` attribute cannot be used on use statements +use std::mem; + +#[splat] //~ ERROR `#[splat]` attribute cannot be used on structs +struct FooStruct; + +fn main() {} From 16aa9d077b83ede78c01aa136527169b593fecfd Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Tue, 2 Jun 2026 13:19:52 +0200 Subject: [PATCH 13/13] Report duplicate relaxed bounds during ast lowering --- compiler/rustc_ast_lowering/src/item.rs | 27 ++++---- compiler/rustc_ast_lowering/src/lib.rs | 68 ++++++++++++++----- .../src/hir_ty_lowering/bounds.rs | 48 ------------- .../trait-bounds/duplicate-relaxed-bounds.rs | 1 - .../duplicate-relaxed-bounds.stderr | 56 +++++++-------- 5 files changed, 90 insertions(+), 110 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index 81b6e88fef82f..f94c6491ea53e 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -11,6 +11,7 @@ use rustc_hir::{ }; use rustc_index::{IndexSlice, IndexVec}; use rustc_middle::span_bug; +use rustc_middle::ty::data_structures::IndexMap; use rustc_middle::ty::{ResolverAstLowering, TyCtxt}; use rustc_span::def_id::DefId; use rustc_span::edit_distance::find_best_match_for_name; @@ -1098,7 +1099,7 @@ impl<'hir> LoweringContext<'_, 'hir> { hir::TraitItemKind::Type( this.lower_param_bounds( bounds, - RelaxedBoundPolicy::Allowed, + RelaxedBoundPolicy::Allowed(&mut Default::default()), ImplTraitContext::Disallowed(ImplTraitPosition::Generic), ), ty, @@ -1886,6 +1887,9 @@ impl<'hir> LoweringContext<'_, 'hir> { assert!(self.impl_trait_bounds.is_empty()); let mut predicates: SmallVec<[hir::WherePredicate<'hir>; 4]> = SmallVec::new(); + // We need to make sure that generic params don't have multiple relaxed bounds for the same trait + // across generic param bounds and where bounds. + let mut dedup_map: IndexMap = Default::default(); predicates.extend(generics.params.iter().filter_map(|param| { self.lower_generic_bound_predicate( param.ident, @@ -1894,18 +1898,16 @@ impl<'hir> LoweringContext<'_, 'hir> { ¶m.bounds, param.colon_span, generics.span, - RelaxedBoundPolicy::Allowed, + RelaxedBoundPolicy::Allowed( + dedup_map.entry(self.local_def_id(param.id)).or_default(), + ), itctx, PredicateOrigin::GenericParam, ) })); - predicates.extend( - generics - .where_clause - .predicates - .iter() - .map(|predicate| self.lower_where_predicate(predicate, &generics.params)), - ); + predicates.extend(generics.where_clause.predicates.iter().map(|predicate| { + self.lower_where_predicate(predicate, &generics.params, &mut dedup_map) + })); let mut params: SmallVec<[hir::GenericParam<'hir>; 4]> = self .lower_generic_params_mut(&generics.params, hir::GenericParamSource::Generics) @@ -1981,7 +1983,7 @@ impl<'hir> LoweringContext<'_, 'hir> { bounds: &[GenericBound], colon_span: Option, parent_span: Span, - rbp: RelaxedBoundPolicy, + rbp: RelaxedBoundPolicy<'_>, itctx: ImplTraitContext, origin: PredicateOrigin, ) -> Option> { @@ -2046,6 +2048,7 @@ impl<'hir> LoweringContext<'_, 'hir> { &mut self, pred: &WherePredicate, params: &[ast::GenericParam], + dedup_map: &mut IndexMap>, ) -> hir::WherePredicate<'hir> { let hir_id = self.lower_node_id(pred.id); let span = self.lower_span(pred.span); @@ -2062,7 +2065,7 @@ impl<'hir> LoweringContext<'_, 'hir> { && let Res::Def(DefKind::TyParam, def_id) = res && params.iter().any(|p| def_id == self.local_def_id(p.id).to_def_id()) { - RelaxedBoundPolicy::Allowed + RelaxedBoundPolicy::Allowed(dedup_map.entry(def_id.expect_local()).or_default()) } else { RelaxedBoundPolicy::Forbidden(RelaxedBoundForbiddenReason::WhereBound) }; @@ -2092,7 +2095,7 @@ impl<'hir> LoweringContext<'_, 'hir> { ), bounds: self.lower_param_bounds( bounds, - RelaxedBoundPolicy::Allowed, + RelaxedBoundPolicy::Allowed(&mut Default::default()), ImplTraitContext::Disallowed(ImplTraitPosition::Bound), ), in_where_clause: true, diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 8b4a2795ec90c..a18f0c2f69548 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -43,14 +43,15 @@ use rustc_ast::visit::Visitor; use rustc_ast::{self as ast, *}; use rustc_attr_parsing::{AttributeParser, OmitDoc, Recovery, ShouldEmit}; use rustc_data_structures::fingerprint::Fingerprint; -use rustc_data_structures::fx::FxIndexSet; +use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; use rustc_data_structures::sorted_map::SortedMap; use rustc_data_structures::stable_hash::{StableHash, StableHasher}; use rustc_data_structures::steal::Steal; use rustc_data_structures::tagged_ptr::TaggedRef; +use rustc_errors::codes::*; use rustc_errors::{DiagArgFromDisplay, DiagCtxtHandle}; use rustc_hir::def::{DefKind, LifetimeRes, Namespace, PartialRes, PerNS, Res}; -use rustc_hir::def_id::{CRATE_DEF_ID, LOCAL_CRATE, LocalDefId}; +use rustc_hir::def_id::{CRATE_DEF_ID, DefId, LOCAL_CRATE, LocalDefId}; use rustc_hir::definitions::PerParentDisambiguatorState; use rustc_hir::lints::DelayedLint; use rustc_hir::{ @@ -319,11 +320,20 @@ impl<'tcx> ResolverAstLowering<'tcx> { /// /// Relaxed bounds should only be allowed in places where we later /// (namely during HIR ty lowering) perform *sized elaboration*. -#[derive(Clone, Copy, Debug)] -enum RelaxedBoundPolicy { - Allowed, +#[derive(Debug)] +enum RelaxedBoundPolicy<'a> { + /// The `DefId` refers to the trait that is being relaxed. + Allowed(&'a mut FxIndexMap), Forbidden(RelaxedBoundForbiddenReason), } +impl RelaxedBoundPolicy<'_> { + fn reborrow(&mut self) -> RelaxedBoundPolicy<'_> { + match self { + RelaxedBoundPolicy::Allowed(m) => RelaxedBoundPolicy::Allowed(m), + RelaxedBoundPolicy::Forbidden(reason) => RelaxedBoundPolicy::Forbidden(*reason), + } + } +} #[derive(Clone, Copy, Debug)] enum RelaxedBoundForbiddenReason { @@ -1551,9 +1561,13 @@ impl<'hir> LoweringContext<'_, 'hir> { } path } - ImplTraitContext::InBinding => hir::TyKind::TraitAscription( - self.lower_param_bounds(bounds, RelaxedBoundPolicy::Allowed, itctx), - ), + ImplTraitContext::InBinding => { + hir::TyKind::TraitAscription(self.lower_param_bounds( + bounds, + RelaxedBoundPolicy::Allowed(&mut Default::default()), + itctx, + )) + } ImplTraitContext::FeatureGated(position, feature) => { let guar = self .tcx @@ -1676,7 +1690,11 @@ impl<'hir> LoweringContext<'_, 'hir> { let opaque_ty_span = self.mark_span_with_reason(DesugaringKind::OpaqueTy, span, None); self.lower_opaque_inner(opaque_ty_node_id, origin, opaque_ty_span, |this| { - this.lower_param_bounds(bounds, RelaxedBoundPolicy::Allowed, itctx) + this.lower_param_bounds( + bounds, + RelaxedBoundPolicy::Allowed(&mut Default::default()), + itctx, + ) }) } @@ -1970,7 +1988,7 @@ impl<'hir> LoweringContext<'_, 'hir> { fn lower_param_bound( &mut self, tpb: &GenericBound, - rbp: RelaxedBoundPolicy, + rbp: RelaxedBoundPolicy<'_>, itctx: ImplTraitContext, ) -> hir::GenericBound<'hir> { match tpb { @@ -2209,7 +2227,7 @@ impl<'hir> LoweringContext<'_, 'hir> { fn lower_poly_trait_ref( &mut self, PolyTraitRef { bound_generic_params, modifiers, trait_ref, span, parens: _ }: &PolyTraitRef, - rbp: RelaxedBoundPolicy, + rbp: RelaxedBoundPolicy<'_>, itctx: ImplTraitContext, ) -> hir::PolyTraitRef<'hir> { let bound_generic_params = @@ -2233,7 +2251,7 @@ impl<'hir> LoweringContext<'_, 'hir> { &self, trait_ref: hir::TraitRef<'_>, span: Span, - rbp: RelaxedBoundPolicy, + rbp: RelaxedBoundPolicy<'_>, ) { // Even though feature `more_maybe_bounds` enables the user to relax all default bounds // other than `Sized` in a lot more positions (thereby bypassing the given policy), we don't @@ -2245,7 +2263,23 @@ impl<'hir> LoweringContext<'_, 'hir> { // question: E.g., `?Sized` & `?Move` most likely won't be allowed in all the same places). match rbp { - RelaxedBoundPolicy::Allowed => return, + RelaxedBoundPolicy::Allowed(dedup_map) => { + // `trait_def_id` only returns `None` for errors during resolution. + let Some(trait_def_id) = trait_ref.trait_def_id() else { return }; + let tcx = self.tcx; + let err = |s| { + let name = tcx.item_name(trait_def_id); + tcx.dcx() + .struct_span_err( + vec![span, s], + format!("duplicate relaxed `{name}` bounds"), + ) + .with_code(E0203) + .emit(); + }; + dedup_map.entry(trait_def_id).and_modify(|&mut s| err(s)).or_insert(span); + return; + } RelaxedBoundPolicy::Forbidden(reason) => { let gate = |context, subject| { let extended = self.tcx.features().more_maybe_bounds(); @@ -2307,7 +2341,7 @@ impl<'hir> LoweringContext<'_, 'hir> { fn lower_param_bounds( &mut self, bounds: &[GenericBound], - rbp: RelaxedBoundPolicy, + rbp: RelaxedBoundPolicy<'_>, itctx: ImplTraitContext, ) -> hir::GenericBounds<'hir> { self.arena.alloc_from_iter(self.lower_param_bounds_mut(bounds, rbp, itctx)) @@ -2316,10 +2350,10 @@ impl<'hir> LoweringContext<'_, 'hir> { fn lower_param_bounds_mut( &mut self, bounds: &[GenericBound], - rbp: RelaxedBoundPolicy, + mut rbp: RelaxedBoundPolicy<'_>, itctx: ImplTraitContext, ) -> impl Iterator> { - bounds.iter().map(move |bound| self.lower_param_bound(bound, rbp, itctx)) + bounds.iter().map(move |bound| self.lower_param_bound(bound, rbp.reborrow(), itctx)) } #[instrument(level = "debug", skip(self), ret)] @@ -2353,7 +2387,7 @@ impl<'hir> LoweringContext<'_, 'hir> { bounds, /* colon_span */ None, span, - RelaxedBoundPolicy::Allowed, + RelaxedBoundPolicy::Allowed(&mut Default::default()), ImplTraitContext::Universal, hir::PredicateOrigin::ImplTrait, ); diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs index 770ccc7dff51f..4dba70cbd56b0 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs @@ -14,7 +14,6 @@ use rustc_middle::ty::{ }; use rustc_span::{ErrorGuaranteed, Ident, Span, kw}; use rustc_trait_selection::traits; -use smallvec::SmallVec; use tracing::{debug, instrument}; use crate::errors; @@ -85,19 +84,6 @@ fn search_bounds_for<'tcx>( } } -fn collect_relaxed_bounds<'tcx>( - hir_bounds: &'tcx [hir::GenericBound<'tcx>], - context: ImpliedBoundsContext<'tcx>, -) -> SmallVec<[&'tcx PolyTraitRef<'tcx>; 1]> { - let mut relaxed_bounds: SmallVec<[_; 1]> = SmallVec::new(); - search_bounds_for(hir_bounds, context, |ptr| { - if matches!(ptr.modifiers.polarity, hir::BoundPolarity::Maybe(_)) { - relaxed_bounds.push(ptr); - } - }); - relaxed_bounds -} - fn collect_bounds<'a, 'tcx>( hir_bounds: &'a [hir::GenericBound<'tcx>], context: ImpliedBoundsContext<'tcx>, @@ -192,18 +178,6 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { } } ImpliedBoundsContext::TyParam(..) | ImpliedBoundsContext::AssociatedTypeOrImplTrait => { - // Report invalid relaxed bounds. - // FIXME: Since we only call this validation function here in this function, we only - // fully validate relaxed bounds in contexts where we perform - // "sized elaboration". In most cases that doesn't matter because we *usually* - // reject such relaxed bounds outright during AST lowering. - // However, this can easily get out of sync! Ideally, we would perform this step - // where we are guaranteed to catch *all* bounds like in - // `Self::lower_poly_trait_ref`. List of concrete issues: - // FIXME(more_maybe_bounds): We don't call this for trait object tys, supertrait - // bounds, trait alias bounds, assoc type bounds (ATB)! - let bounds = collect_relaxed_bounds(hir_bounds, context); - self.reject_duplicate_relaxed_bounds(bounds); } } @@ -287,28 +261,6 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { !find_attr!(self.tcx(), crate, RustcNoImplicitBounds) && !collected.any() } - fn reject_duplicate_relaxed_bounds(&self, relaxed_bounds: SmallVec<[&PolyTraitRef<'_>; 1]>) { - let tcx = self.tcx(); - - let mut grouped_bounds = FxIndexMap::<_, Vec<_>>::default(); - - for bound in &relaxed_bounds { - if let Res::Def(DefKind::Trait, trait_def_id) = bound.trait_ref.path.res { - grouped_bounds.entry(trait_def_id).or_default().push(bound.span); - } - } - - for (trait_def_id, spans) in grouped_bounds { - if spans.len() > 1 { - let name = tcx.item_name(trait_def_id); - self.dcx() - .struct_span_err(spans, format!("duplicate relaxed `{name}` bounds")) - .with_code(E0203) - .emit(); - } - } - } - pub(crate) fn require_bound_to_relax_default_trait( &self, trait_ref: hir::TraitRef<'_>, diff --git a/tests/ui/trait-bounds/duplicate-relaxed-bounds.rs b/tests/ui/trait-bounds/duplicate-relaxed-bounds.rs index d240ce3f98c6a..69f2ded988c8d 100644 --- a/tests/ui/trait-bounds/duplicate-relaxed-bounds.rs +++ b/tests/ui/trait-bounds/duplicate-relaxed-bounds.rs @@ -9,7 +9,6 @@ trait Trait { // even on *associated types* like here. Test that we no longer do that. type Type: ?Sized + ?Sized; //~^ ERROR duplicate relaxed `Sized` bounds - //~| ERROR duplicate relaxed `Sized` bounds // Make sure, should we ever allow such where bounds, // we still detect duplicate relaxed bounds. diff --git a/tests/ui/trait-bounds/duplicate-relaxed-bounds.stderr b/tests/ui/trait-bounds/duplicate-relaxed-bounds.stderr index 1bed289eed1c2..130f5e3640090 100644 --- a/tests/ui/trait-bounds/duplicate-relaxed-bounds.stderr +++ b/tests/ui/trait-bounds/duplicate-relaxed-bounds.stderr @@ -1,11 +1,3 @@ -error: this relaxed bound is not permitted here - --> $DIR/duplicate-relaxed-bounds.rs:18:21 - | -LL | Self::Type: ?Sized; - | ^^^^^^ - | - = note: in this context, relaxed bounds are only allowed on type parameters defined on the closest item - error[E0203]: duplicate relaxed `Sized` bounds --> $DIR/duplicate-relaxed-bounds.rs:1:13 | @@ -18,26 +10,22 @@ error[E0203]: duplicate relaxed `Iterator` bounds LL | fn dupes() {} | ^^^^^^^^^ ^^^^^^^^^ -error: bound modifier `?` can only be applied to `Sized` - --> $DIR/duplicate-relaxed-bounds.rs:1:31 +error[E0203]: duplicate relaxed `Sized` bounds + --> $DIR/duplicate-relaxed-bounds.rs:10:16 | -LL | fn dupes() {} - | ^^^^^^^^^ +LL | type Type: ?Sized + ?Sized; + | ^^^^^^ ^^^^^^ -error: bound modifier `?` can only be applied to `Sized` - --> $DIR/duplicate-relaxed-bounds.rs:1:43 +error: this relaxed bound is not permitted here + --> $DIR/duplicate-relaxed-bounds.rs:17:21 | -LL | fn dupes() {} - | ^^^^^^^^^ - -error: bound modifier `?` can only be applied to `Sized` - --> $DIR/duplicate-relaxed-bounds.rs:26:26 +LL | Self::Type: ?Sized; + | ^^^^^^ | -LL | fn not_dupes() {} - | ^^^^^^^^^ + = note: in this context, relaxed bounds are only allowed on type parameters defined on the closest item error[E0203]: duplicate relaxed `Sized` bounds - --> $DIR/duplicate-relaxed-bounds.rs:30:19 + --> $DIR/duplicate-relaxed-bounds.rs:29:19 | LL | fn where_dupes() | ^^^^^^ @@ -45,20 +33,24 @@ LL | fn where_dupes() LL | T: ?Sized, | ^^^^^^ -error[E0203]: duplicate relaxed `Sized` bounds - --> $DIR/duplicate-relaxed-bounds.rs:10:16 +error: bound modifier `?` can only be applied to `Sized` + --> $DIR/duplicate-relaxed-bounds.rs:1:31 | -LL | type Type: ?Sized + ?Sized; - | ^^^^^^ ^^^^^^ +LL | fn dupes() {} + | ^^^^^^^^^ -error[E0203]: duplicate relaxed `Sized` bounds - --> $DIR/duplicate-relaxed-bounds.rs:10:16 +error: bound modifier `?` can only be applied to `Sized` + --> $DIR/duplicate-relaxed-bounds.rs:1:43 | -LL | type Type: ?Sized + ?Sized; - | ^^^^^^ ^^^^^^ +LL | fn dupes() {} + | ^^^^^^^^^ + +error: bound modifier `?` can only be applied to `Sized` + --> $DIR/duplicate-relaxed-bounds.rs:25:26 | - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` +LL | fn not_dupes() {} + | ^^^^^^^^^ -error: aborting due to 9 previous errors +error: aborting due to 8 previous errors For more information about this error, try `rustc --explain E0203`.