From 7c96471b6f608388a011efdb63abf4298a1afe5f Mon Sep 17 00:00:00 2001 From: xmakro Date: Sat, 20 Jun 2026 11:36:42 -0700 Subject: [PATCH 01/35] perf: Make stable_crate_ids reads lock-free after crate loading --- compiler/rustc_interface/src/passes.rs | 4 ++-- .../rustc_metadata/src/rmeta/decoder/cstore_impl.rs | 12 ++++++------ compiler/rustc_resolve/src/lib.rs | 4 ++-- compiler/rustc_session/src/cstore.rs | 10 ++++++++++ 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index ce99b01637b04..41a4896d89e52 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -805,8 +805,8 @@ fn resolver_for_lowering_raw<'tcx>( ); let krate = configure_and_expand(krate, &pre_configured_attrs, &mut resolver); - // Make sure we don't mutate the cstore from here on. - tcx.untracked().cstore.freeze(); + // Don't mutate the cstore or stable crate id map from here on. + tcx.untracked().freeze_cstore(); let ResolverOutputs { global_ctxt: untracked_resolutions, diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs index cbd6afd68473a..be856f01a9429 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs @@ -555,15 +555,15 @@ pub(in crate::rmeta) fn provide(providers: &mut Providers) { ) }, crates: |tcx, ()| { - // The list of loaded crates is now frozen in query cache, - // so make sure cstore is not mutably accessed from here on. - tcx.untracked().cstore.freeze(); + // The loaded-crate list is now frozen in the query cache; stop + // mutating the cstore and stable crate id map from here on. + tcx.untracked().freeze_cstore(); tcx.arena.alloc_from_iter(CStore::from_tcx(tcx).iter_crate_data().map(|(cnum, _)| cnum)) }, used_crates: |tcx, ()| { - // The list of loaded crates is now frozen in query cache, - // so make sure cstore is not mutably accessed from here on. - tcx.untracked().cstore.freeze(); + // The loaded-crate list is now frozen in the query cache; stop + // mutating the cstore and stable crate id map from here on. + tcx.untracked().freeze_cstore(); tcx.arena.alloc_from_iter( CStore::from_tcx(tcx) .iter_crate_data() diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 061471ccc97e0..fd60094e21558 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -2091,8 +2091,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { .time("resolve_postprocess", || self.cstore_mut().postprocess(self.tcx, krate)); }); - // Make sure we don't mutate the cstore from here on. - self.tcx.untracked().cstore.freeze(); + // Don't mutate the cstore or stable crate id map from here on. + self.tcx.untracked().freeze_cstore(); } fn traits_in_scope( diff --git a/compiler/rustc_session/src/cstore.rs b/compiler/rustc_session/src/cstore.rs index 39fe9c80923ec..94237132bd393 100644 --- a/compiler/rustc_session/src/cstore.rs +++ b/compiler/rustc_session/src/cstore.rs @@ -224,3 +224,13 @@ pub struct Untracked { /// The interned [StableCrateId]s. pub stable_crate_ids: FreezeLock, } + +impl Untracked { + /// Freezes the cstore and, with it, the `StableCrateId` map, making reads of + /// both lock-free. The cstore is frozen first so any in-flight crate loading + /// (which writes the map) finishes before the map is frozen. + pub fn freeze_cstore(&self) { + self.cstore.freeze(); + self.stable_crate_ids.freeze(); + } +} From b429456924bb43549c98af77e80807f3e3fb98cf Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Tue, 23 Jun 2026 16:37:49 +0200 Subject: [PATCH 02/35] Move some methods that only need the region context onto that --- .../src/diagnostics/explain_borrow.rs | 2 +- .../src/diagnostics/region_errors.rs | 40 ++++++++++++------- .../src/diagnostics/region_name.rs | 6 +-- 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs b/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs index 5d154cc7b9050..f6abafb874165 100644 --- a/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs +++ b/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs @@ -706,7 +706,7 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> { Some(Cause::LiveVar(..) | Cause::DropVar(..)) | None => { // Here, under NLL: no cause was found. Under polonius: no cause was found, or a // boring local was found, which we ignore like NLLs do to match its diagnostics. - if let Some(region) = self.to_error_region_vid(borrow_region_vid) { + if let Some(region) = self.regioncx.to_error_region_vid(borrow_region_vid) { let (category, from_closure, span, region_name, path) = self.free_region_constraint_info(borrow_region_vid, region); if let Some(region_name) = region_name { diff --git a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs index e436d95cdb087..66d35f8b8bf2d 100644 --- a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs @@ -28,6 +28,7 @@ use rustc_trait_selection::traits::{Obligation, ObligationCtxt}; use tracing::{debug, instrument, trace}; use super::{LIMITATION_NOTE, OutlivesSuggestionBuilder, RegionName, RegionNameSource}; +use crate::consumers::RegionInferenceContext; use crate::nll::ConstraintDescription; use crate::region_infer::{BlameConstraint, TypeTest}; use crate::session_diagnostics::{ @@ -134,7 +135,7 @@ pub(crate) struct ErrorConstraintInfo<'tcx> { pub(super) span: Span, } -impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { +impl<'tcx> RegionInferenceContext<'tcx> { /// Converts a region inference variable into a `ty::Region` that /// we can use for error reporting. If `r` is universally bound, /// then we use the name that we have on record for it. If `r` is @@ -142,20 +143,20 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { /// to find a good name from that. Returns `None` if we can't find /// one (e.g., this is just some random part of the CFG). pub(super) fn to_error_region(&self, r: RegionVid) -> Option> { - self.to_error_region_vid(r).and_then(|r| self.regioncx.region_definition(r).external_name) + self.to_error_region_vid(r).and_then(|r| self.region_definition(r).external_name) } /// Returns the `RegionVid` corresponding to the region returned by /// `to_error_region`. pub(super) fn to_error_region_vid(&self, r: RegionVid) -> Option { - if self.regioncx.universal_regions().is_universal_region(r) { + if self.universal_regions().is_universal_region(r) { Some(r) } else { // We just want something nameable, even if it's not // actually an upper bound. - let upper_bound = self.regioncx.approx_universal_upper_bound(r); + let upper_bound = self.approx_universal_upper_bound(r); - if self.regioncx.upper_bound_in_region_scc(r, upper_bound) { + if self.upper_bound_in_region_scc(r, upper_bound) { self.to_error_region_vid(upper_bound) } else { None @@ -179,14 +180,16 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { if let Some(r) = self.to_error_region(fr) && let ty::ReLateParam(late_param) = r.kind() && let ty::LateParamRegionKind::ClosureEnv = late_param.kind - && let DefiningTy::Closure(_, args) = self.regioncx.universal_regions().defining_ty + && let DefiningTy::Closure(_, args) = self.universal_regions().defining_ty { return args.as_closure().kind() == ty::ClosureKind::FnMut; } false } +} +impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { // For generic associated types (GATs) which implied 'static requirement // from higher-ranked trait bounds (HRTB). Try to locate span of the trait // and the span which bounded to the trait for adding 'static lifetime suggestion @@ -309,12 +312,12 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { RegionErrorKind::TypeTestError { type_test } => { // Try to convert the lower-bound region into something named we can print for // the user. - let lower_bound_region = self.to_error_region(type_test.lower_bound); + let lower_bound_region = self.regioncx.to_error_region(type_test.lower_bound); let type_test_span = type_test.span; if let Some(lower_bound_region) = lower_bound_region { - let generic_ty = self.name_regions( + let generic_ty = self.regioncx.name_regions( self.infcx.tcx, type_test.generic_kind.to_ty(self.infcx.tcx), ); @@ -324,7 +327,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { self.body.source.def_id().expect_local(), type_test_span, Some(origin), - self.name_regions(self.infcx.tcx, type_test.generic_kind), + self.regioncx.name_regions(self.infcx.tcx, type_test.generic_kind), lower_bound_region, )); } else { @@ -450,7 +453,9 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { debug!("report_region_error: category={:?} {:?} {:?}", category, cause, variance_info); // Check if we can use one of the "nice region errors". - if let (Some(f), Some(o)) = (self.to_error_region(fr), self.to_error_region(outlived_fr)) { + if let (Some(f), Some(o)) = + (self.regioncx.to_error_region(fr), self.regioncx.to_error_region(outlived_fr)) + { let infer_err = self.infcx.err_ctxt(); let nice = NiceRegionError::new_from_span(&infer_err, self.mir_def_id(), cause.span, o, f); @@ -481,7 +486,9 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { d.note("meoow :c"); d } - (ConstraintCategory::Return(kind), true, false) if self.is_closure_fn_mut(fr) => { + (ConstraintCategory::Return(kind), true, false) + if self.regioncx.is_closure_fn_mut(fr) => + { self.report_fnmut_error(&errci, kind) } (ConstraintCategory::Assignment, true, false) @@ -736,7 +743,10 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { // Only show an extra note if we can find an 'error region' for both of the region // variables. This avoids showing a noisy note that just mentions 'synthetic' regions // that don't help the user understand the error. - match (self.to_error_region(errci.fr), self.to_error_region(errci.outlived_fr)) { + match ( + self.regioncx.to_error_region(errci.fr), + self.regioncx.to_error_region(errci.outlived_fr), + ) { (Some(f), Some(o)) => { self.maybe_suggest_constrain_dyn_trait_impl(&mut diag, f, o, category); @@ -842,7 +852,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { outlived_fr: RegionVid, ) { if let (Some(f), Some(outlived_f)) = - (self.to_error_region(fr), self.to_error_region(outlived_fr)) + (self.regioncx.to_error_region(fr), self.regioncx.to_error_region(outlived_fr)) { if outlived_f.kind() != ty::ReStatic { return; @@ -1013,7 +1023,9 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { } fn suggest_adding_lifetime_params(&self, diag: &mut Diag<'_>, sub: RegionVid, sup: RegionVid) { - let (Some(sub), Some(sup)) = (self.to_error_region(sub), self.to_error_region(sup)) else { + let (Some(sub), Some(sup)) = + (self.regioncx.to_error_region(sub), self.regioncx.to_error_region(sup)) + else { return; }; diff --git a/compiler/rustc_borrowck/src/diagnostics/region_name.rs b/compiler/rustc_borrowck/src/diagnostics/region_name.rs index a00d39eea12d3..b8037fc83aad0 100644 --- a/compiler/rustc_borrowck/src/diagnostics/region_name.rs +++ b/compiler/rustc_borrowck/src/diagnostics/region_name.rs @@ -283,7 +283,7 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> { /// named variants. #[instrument(level = "trace", skip(self))] fn give_name_from_error_region(&self, fr: RegionVid) -> Option { - let error_region = self.to_error_region(fr)?; + let error_region = self.regioncx.to_error_region(fr)?; let tcx = self.infcx.tcx; @@ -1015,7 +1015,7 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> { &self, fr: RegionVid, ) -> Option { - let ty::ReEarlyParam(region) = self.to_error_region(fr)?.kind() else { + let ty::ReEarlyParam(region) = self.regioncx.to_error_region(fr)?.kind() else { return None; }; if region.is_named() { @@ -1050,7 +1050,7 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> { &self, fr: RegionVid, ) -> Option { - let ty::ReEarlyParam(region) = self.to_error_region(fr)?.kind() else { + let ty::ReEarlyParam(region) = self.regioncx.to_error_region(fr)?.kind() else { return None; }; if region.is_named() { From 7ebb8596d118af0144d60d60c29135c1095a2bd5 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Wed, 24 Jun 2026 09:10:13 +0200 Subject: [PATCH 03/35] Consistently taint root cx Instead of having to taint both infcx and root_cx, we always just taint infcx (which usually gets automatically tainted from emitting diagnostics), and at the end (before dropping the infcx) we move the taint over to the root_cx --- .../rustc_borrowck/src/diagnostics/mod.rs | 61 ++++++++++--------- .../src/diagnostics/opaque_types.rs | 8 +-- compiler/rustc_borrowck/src/lib.rs | 8 +-- compiler/rustc_borrowck/src/type_check/mod.rs | 1 - 4 files changed, 38 insertions(+), 40 deletions(-) diff --git a/compiler/rustc_borrowck/src/diagnostics/mod.rs b/compiler/rustc_borrowck/src/diagnostics/mod.rs index 56e0c74c85e97..a5b7a26a10ab4 100644 --- a/compiler/rustc_borrowck/src/diagnostics/mod.rs +++ b/compiler/rustc_borrowck/src/diagnostics/mod.rs @@ -116,11 +116,42 @@ impl<'infcx, 'tcx> BorrowckDiagnosticsBuffer<'infcx, 'tcx> { pub(crate) fn buffer_non_error(&mut self, diag: Diag<'infcx, ()>) { self.buffered_diags.push(BufferedDiag::NonError(diag)); } + pub(crate) fn buffer_error(&mut self, diag: Diag<'infcx>) { + self.buffered_diags.push(BufferedDiag::Error(diag)); + } + + pub(crate) fn emit_errors(&mut self) -> Option { + let mut res = None; + + // Buffer any move errors that we collected and de-duplicated. + for (_, (_, diag)) in std::mem::take(&mut self.buffered_move_errors) { + // We have already set tainted for this error, so just buffer it. + self.buffer_error(diag); + } + for (_, (mut diag, count)) in std::mem::take(&mut self.buffered_mut_errors) { + if count > 10 { + diag.note(format!("...and {} other attempted mutable borrows", count - 10)); + } + self.buffer_error(diag); + } + + if !self.buffered_diags.is_empty() { + self.buffered_diags.sort_by_key(|buffered_diag| buffered_diag.sort_span()); + for buffered_diag in self.buffered_diags.drain(..) { + match buffered_diag { + BufferedDiag::Error(diag) => res = Some(diag.emit()), + BufferedDiag::NonError(diag) => diag.emit(), + } + } + } + + res + } } impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { pub(crate) fn buffer_error(&mut self, diag: Diag<'infcx>) { - self.diags_buffer.buffered_diags.push(BufferedDiag::Error(diag)); + self.diags_buffer.buffer_error(diag); } pub(crate) fn buffer_non_error(&mut self, diag: Diag<'infcx, ()>) { @@ -152,34 +183,6 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { self.diags_buffer.buffered_mut_errors.insert(span, (diag, count)); } - pub(crate) fn emit_errors(&mut self) -> Option { - let mut res = self.infcx.tainted_by_errors(); - - // Buffer any move errors that we collected and de-duplicated. - for (_, (_, diag)) in std::mem::take(&mut self.diags_buffer.buffered_move_errors) { - // We have already set tainted for this error, so just buffer it. - self.buffer_error(diag); - } - for (_, (mut diag, count)) in std::mem::take(&mut self.diags_buffer.buffered_mut_errors) { - if count > 10 { - diag.note(format!("...and {} other attempted mutable borrows", count - 10)); - } - self.buffer_error(diag); - } - - if !self.diags_buffer.buffered_diags.is_empty() { - self.diags_buffer.buffered_diags.sort_by_key(|buffered_diag| buffered_diag.sort_span()); - for buffered_diag in self.diags_buffer.buffered_diags.drain(..) { - match buffered_diag { - BufferedDiag::Error(diag) => res = Some(diag.emit()), - BufferedDiag::NonError(diag) => diag.emit(), - } - } - } - - res - } - pub(crate) fn has_buffered_diags(&self) -> bool { self.diags_buffer.buffered_diags.is_empty() } diff --git a/compiler/rustc_borrowck/src/diagnostics/opaque_types.rs b/compiler/rustc_borrowck/src/diagnostics/opaque_types.rs index 590c2c9c3965b..1df16595e2d86 100644 --- a/compiler/rustc_borrowck/src/diagnostics/opaque_types.rs +++ b/compiler/rustc_borrowck/src/diagnostics/opaque_types.rs @@ -28,11 +28,10 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { } let infcx = self.infcx; - let mut guar = None; let mut last_unexpected_hidden_region: Option<(Span, Ty<'_>, ty::OpaqueTypeKey<'tcx>)> = None; for error in errors { - guar = Some(match error { + match error { DeferredOpaqueTypeError::InvalidOpaqueTypeArgs(err) => err.report(infcx), DeferredOpaqueTypeError::LifetimeMismatchOpaqueParam(err) => { infcx.dcx().emit_err(err) @@ -81,11 +80,8 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { ) ), ), - }); + }; } - let guar = guar.unwrap(); - self.root_cx.set_tainted_by_errors(guar); - self.infcx.set_tainted_by_errors(guar); } /// Try to note when an opaque is involved in a borrowck error and that diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs index 94ad74b67b629..b9ff75421a52f 100644 --- a/compiler/rustc_borrowck/src/lib.rs +++ b/compiler/rustc_borrowck/src/lib.rs @@ -323,7 +323,6 @@ fn borrowck_collect_region_constraints<'tcx>( let input_promoted: &IndexSlice<_, _> = &promoted.borrow(); if let Some(e) = input_body.tainted_by_errors { infcx.set_tainted_by_errors(e); - root_cx.set_tainted_by_errors(e); } // Replace all regions with fresh inference variables. This @@ -571,15 +570,16 @@ fn borrowck_check_region_constraints<'tcx>( debug!("mbcx.used_mut: {:?}", mbcx.used_mut); mbcx.lint_unused_mut(); - if let Some(guar) = mbcx.emit_errors() { - mbcx.root_cx.set_tainted_by_errors(guar); - } let result = PropagatedBorrowCheckResults { closure_requirements: opt_closure_req, used_mut_upvars: mbcx.used_mut_upvars, }; + if let Some(guar) = mbcx.diags_buffer.emit_errors().or(infcx.tainted_by_errors()) { + root_cx.set_tainted_by_errors(guar); + } + if let Some(consumer) = &mut root_cx.consumer { consumer.insert_body( def, diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index 450ac46aab521..3dc104e251cbe 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -198,7 +198,6 @@ pub(crate) fn type_check<'tcx>( debug!("encountered an error region; removing constraints!"); constraints.outlives_constraints = Default::default(); constraints.type_tests = Default::default(); - root_cx.set_tainted_by_errors(guar); infcx.set_tainted_by_errors(guar); } From f44093d3136d935c649f3560081941a1a4d5e462 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Wed, 24 Jun 2026 09:10:13 +0200 Subject: [PATCH 04/35] Remove some unnecessary mutabilities --- compiler/rustc_borrowck/src/lib.rs | 2 +- compiler/rustc_borrowck/src/nll.rs | 2 +- compiler/rustc_borrowck/src/root_cx.rs | 2 +- compiler/rustc_borrowck/src/type_check/mod.rs | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs index b9ff75421a52f..e6d0163044947 100644 --- a/compiler/rustc_borrowck/src/lib.rs +++ b/compiler/rustc_borrowck/src/lib.rs @@ -718,7 +718,7 @@ impl<'tcx> Deref for BorrowckInferCtxt<'tcx> { } pub(crate) struct MirBorrowckCtxt<'a, 'infcx, 'tcx> { - root_cx: &'a mut BorrowCheckRootCtxt<'tcx>, + root_cx: &'a BorrowCheckRootCtxt<'tcx>, infcx: &'infcx BorrowckInferCtxt<'tcx>, body: &'a Body<'tcx>, move_data: &'a MoveData<'tcx>, diff --git a/compiler/rustc_borrowck/src/nll.rs b/compiler/rustc_borrowck/src/nll.rs index dd6eb17947577..b11f1868b64c9 100644 --- a/compiler/rustc_borrowck/src/nll.rs +++ b/compiler/rustc_borrowck/src/nll.rs @@ -111,7 +111,7 @@ pub(crate) fn compute_closure_requirements_modulo_opaques<'tcx>( /// /// This may result in errors being reported. pub(crate) fn compute_regions<'tcx>( - root_cx: &mut BorrowCheckRootCtxt<'tcx>, + root_cx: &BorrowCheckRootCtxt<'tcx>, infcx: &BorrowckInferCtxt<'tcx>, body: &Body<'tcx>, location_table: &PoloniusLocationTable, diff --git a/compiler/rustc_borrowck/src/root_cx.rs b/compiler/rustc_borrowck/src/root_cx.rs index a082aba35b8a7..e406f158db216 100644 --- a/compiler/rustc_borrowck/src/root_cx.rs +++ b/compiler/rustc_borrowck/src/root_cx.rs @@ -72,7 +72,7 @@ impl<'tcx> BorrowCheckRootCtxt<'tcx> { } pub(super) fn used_mut_upvars( - &mut self, + &self, nested_body_def_id: LocalDefId, ) -> &SmallVec<[FieldIdx; 8]> { &self.propagated_borrowck_results[&nested_body_def_id].used_mut_upvars diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index 3dc104e251cbe..b7bf4efcb6d38 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -95,7 +95,7 @@ mod relate_tys; /// - `move_data` -- move-data constructed when performing the maybe-init dataflow analysis /// - `location_map` -- map between MIR `Location` and `PointIndex` pub(crate) fn type_check<'tcx>( - root_cx: &mut BorrowCheckRootCtxt<'tcx>, + root_cx: &BorrowCheckRootCtxt<'tcx>, infcx: &BorrowckInferCtxt<'tcx>, body: &Body<'tcx>, promoted: &IndexSlice>, @@ -228,7 +228,7 @@ enum FieldAccessError { /// way, it accrues region constraints -- these can later be used by /// NLL region checking. struct TypeChecker<'a, 'tcx> { - root_cx: &'a mut BorrowCheckRootCtxt<'tcx>, + root_cx: &'a BorrowCheckRootCtxt<'tcx>, infcx: &'a BorrowckInferCtxt<'tcx>, last_span: Span, body: &'a Body<'tcx>, From 7102a6ca9fec52d986f03b6a5bd5d8bb315633ee Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Wed, 24 Jun 2026 12:17:24 +0200 Subject: [PATCH 05/35] Typeck children always have the same param env as their parent --- compiler/rustc_ty_utils/src/ty.rs | 3 +++ .../ui/lifetimes/issue-76168-hr-outlives-3.rs | 5 ++--- .../issue-76168-hr-outlives-3.stderr | 20 ++++--------------- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/compiler/rustc_ty_utils/src/ty.rs b/compiler/rustc_ty_utils/src/ty.rs index 367e2e1b97059..c7a3d7252e1ce 100644 --- a/compiler/rustc_ty_utils/src/ty.rs +++ b/compiler/rustc_ty_utils/src/ty.rs @@ -149,6 +149,9 @@ fn adt_sizedness_constraint<'tcx>( /// See `ParamEnv` struct definition for details. fn param_env(tcx: TyCtxt<'_>, def_id: DefId) -> ty::ParamEnv<'_> { + if tcx.is_typeck_child(def_id) { + return tcx.param_env(tcx.typeck_root_def_id(def_id)); + } // Compute the bounds on Self and the type parameters. let ty::InstantiatedPredicates { predicates, .. } = tcx.predicates_of(def_id).instantiate_identity(tcx); diff --git a/tests/ui/lifetimes/issue-76168-hr-outlives-3.rs b/tests/ui/lifetimes/issue-76168-hr-outlives-3.rs index 85eeb5d4c901e..77a1f0b4f25d6 100644 --- a/tests/ui/lifetimes/issue-76168-hr-outlives-3.rs +++ b/tests/ui/lifetimes/issue-76168-hr-outlives-3.rs @@ -8,10 +8,9 @@ async fn wrapper(f: F) //~| ERROR: expected a `FnOnce(&'a mut i32)` closure, found `i32` //~| ERROR: expected a `FnOnce(&'a mut i32)` closure, found `i32` where -F:, -for<'a> >::Output: Future + 'a, + F:, + for<'a> >::Output: Future + 'a, { - //~^ ERROR: expected a `FnOnce(&'a mut i32)` closure, found `i32` let mut i = 41; &mut i; } diff --git a/tests/ui/lifetimes/issue-76168-hr-outlives-3.stderr b/tests/ui/lifetimes/issue-76168-hr-outlives-3.stderr index 6da9f7380d597..bf70826165341 100644 --- a/tests/ui/lifetimes/issue-76168-hr-outlives-3.stderr +++ b/tests/ui/lifetimes/issue-76168-hr-outlives-3.stderr @@ -3,9 +3,9 @@ error[E0277]: expected a `FnOnce(&'a mut i32)` closure, found `i32` | LL | / async fn wrapper(f: F) ... | -LL | | F:, -LL | | for<'a> >::Output: Future + 'a, - | |__________________________________________________________________________^ expected an `FnOnce(&'a mut i32)` closure, found `i32` +LL | | F:, +LL | | for<'a> >::Output: Future + 'a, + | |______________________________________________________________________________^ expected an `FnOnce(&'a mut i32)` closure, found `i32` | = help: the trait `for<'a> FnOnce(&'a mut i32)` is not implemented for `i32` @@ -26,18 +26,6 @@ LL | async fn wrapper(f: F) = help: the trait `for<'a> FnOnce(&'a mut i32)` is not implemented for `i32` = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` -error[E0277]: expected a `FnOnce(&'a mut i32)` closure, found `i32` - --> $DIR/issue-76168-hr-outlives-3.rs:13:1 - | -LL | / { -LL | | -LL | | let mut i = 41; -LL | | &mut i; -LL | | } - | |_^ expected an `FnOnce(&'a mut i32)` closure, found `i32` - | - = help: the trait `for<'a> FnOnce(&'a mut i32)` is not implemented for `i32` - -error: aborting due to 4 previous errors +error: aborting due to 3 previous errors For more information about this error, try `rustc --explain E0277`. From 7cb94f0f36df282b6f2b6896b975f946069db233 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Wed, 24 Jun 2026 12:38:01 +0200 Subject: [PATCH 06/35] Don't compute opaque types for typeck children. They are always the same as their parent --- compiler/rustc_ty_utils/src/opaque_types.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_ty_utils/src/opaque_types.rs b/compiler/rustc_ty_utils/src/opaque_types.rs index d95624b9a24d2..939b51fa7dbc0 100644 --- a/compiler/rustc_ty_utils/src/opaque_types.rs +++ b/compiler/rustc_ty_utils/src/opaque_types.rs @@ -316,6 +316,9 @@ fn opaque_types_defined_by<'tcx>( tcx: TyCtxt<'tcx>, item: LocalDefId, ) -> &'tcx ty::List { + if tcx.is_typeck_child(item.to_def_id()) { + return tcx.opaque_types_defined_by(tcx.local_parent(item)); + } let kind = tcx.def_kind(item); trace!(?kind); let mut collector = OpaqueTypeCollector::new(tcx, item); @@ -331,13 +334,6 @@ fn opaque_types_defined_by<'tcx>( | DefKind::AnonConst => { collector.collect_taits_declared_in_body(); } - // Closures and coroutines are type checked with their parent - // Note that we also support `SyntheticCoroutineBody` since we create - // a MIR body for the def kind, and some MIR passes (like promotion) - // may require doing analysis using its typing env. - DefKind::Closure | DefKind::InlineConst | DefKind::SyntheticCoroutineBody => { - collector.opaques.extend(tcx.opaque_types_defined_by(tcx.local_parent(item))); - } DefKind::AssocTy | DefKind::TyAlias | DefKind::GlobalAsm => {} DefKind::OpaqueTy | DefKind::Mod @@ -348,6 +344,15 @@ fn opaque_types_defined_by<'tcx>( | DefKind::Trait | DefKind::ForeignTy | DefKind::TraitAlias + + // Closures and coroutines are type checked with their parent + // Note that we also support `SyntheticCoroutineBody` since we create + // a MIR body for the def kind, and some MIR passes (like promotion) + // may require doing analysis using its typing env. + | DefKind::Closure + | DefKind::InlineConst + | DefKind::SyntheticCoroutineBody + | DefKind::TyParam | DefKind::ConstParam | DefKind::Ctor(_, _) From 7ccc34feb25be450833e6ad132a3672dbbcee706 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Wed, 24 Jun 2026 16:37:31 +0200 Subject: [PATCH 07/35] Add a convenience helper for doing type ops --- compiler/rustc_borrowck/src/lib.rs | 9 +++++ .../src/type_check/constraint_conversion.rs | 8 ++--- .../src/type_check/free_region_relations.rs | 34 +++++++------------ .../src/type_check/liveness/trace.rs | 10 +++--- 4 files changed, 30 insertions(+), 31 deletions(-) diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs index e6d0163044947..cc01207141a28 100644 --- a/compiler/rustc_borrowck/src/lib.rs +++ b/compiler/rustc_borrowck/src/lib.rs @@ -48,6 +48,7 @@ use rustc_mir_dataflow::points::DenseLocationMap; use rustc_mir_dataflow::{Analysis, EntryStates, Results, ResultsVisitor, visit_results}; use rustc_session::lint::builtin::{TAIL_EXPR_DROP_ORDER, UNUSED_MUT}; use rustc_span::{ErrorGuaranteed, Span, Symbol}; +use rustc_trait_selection::traits::query::type_op::{QueryTypeOp, TypeOp, TypeOpOutput}; use smallvec::SmallVec; use tracing::{debug, instrument}; @@ -707,6 +708,14 @@ impl<'tcx> BorrowckInferCtxt<'tcx> { next_region } + + fn fully_perform + TypeVisitable>>( + &self, + q: Q, + span: Span, + ) -> Result>, ErrorGuaranteed> { + self.param_env.and(q).fully_perform(&self.infcx, self.root_def_id, span) + } } impl<'tcx> Deref for BorrowckInferCtxt<'tcx> { diff --git a/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs b/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs index 4d93fa08fe0bd..82e623bb42535 100644 --- a/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs +++ b/compiler/rustc_borrowck/src/type_check/constraint_conversion.rs @@ -11,7 +11,7 @@ use rustc_middle::ty::{ self, GenericArgKind, Ty, TyCtxt, TypeFoldable, TypeVisitableExt, elaborate, fold_regions, }; use rustc_span::Span; -use rustc_trait_selection::traits::query::type_op::{TypeOp, TypeOpOutput}; +use rustc_trait_selection::traits::query::type_op::TypeOpOutput; use tracing::{debug, instrument}; use crate::constraints::OutlivesConstraint; @@ -287,11 +287,7 @@ impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> { ConstraintCategory<'tcx>, )>, ) -> Ty<'tcx> { - match self.infcx.param_env.and(DeeplyNormalize { value: ty }).fully_perform( - self.infcx, - self.infcx.root_def_id, - self.span, - ) { + match self.infcx.fully_perform(DeeplyNormalize { value: ty }, self.span) { Ok(TypeOpOutput { output: ty, constraints, .. }) => { // FIXME(higher_ranked_auto): What should we do with the assumptions here? if let Some(QueryRegionConstraints { constraints, assumptions: _ }) = constraints { diff --git a/compiler/rustc_borrowck/src/type_check/free_region_relations.rs b/compiler/rustc_borrowck/src/type_check/free_region_relations.rs index 6c7beb85f1e8c..62becc8e298f4 100644 --- a/compiler/rustc_borrowck/src/type_check/free_region_relations.rs +++ b/compiler/rustc_borrowck/src/type_check/free_region_relations.rs @@ -10,7 +10,7 @@ use rustc_middle::mir::ConstraintCategory; use rustc_middle::traits::query::OutlivesBound; use rustc_middle::ty::{self, RegionVid, Ty, TypeVisitableExt}; use rustc_span::{ErrorGuaranteed, Span}; -use rustc_trait_selection::traits::query::type_op::{self, TypeOp}; +use rustc_trait_selection::traits::query::type_op; use tracing::{debug, instrument}; use type_op::TypeOpOutput; @@ -242,15 +242,14 @@ impl<'tcx> UniversalRegionRelationsBuilder<'_, 'tcx> { if let Some(c) = constraints_unnorm { constraints.push(c) } - let TypeOpOutput { output: norm_ty, constraints: constraints_normalize, .. } = - param_env - .and(DeeplyNormalize { value: ty }) - .fully_perform(self.infcx, self.infcx.root_def_id, span) - .unwrap_or_else(|guar| TypeOpOutput { - output: Ty::new_error(self.infcx.tcx, guar), - constraints: None, - error_info: None, - }); + let TypeOpOutput { output: norm_ty, constraints: constraints_normalize, .. } = self + .infcx + .fully_perform(DeeplyNormalize { value: ty }, span) + .unwrap_or_else(|guar| TypeOpOutput { + output: Ty::new_error(self.infcx.tcx, guar), + constraints: None, + error_info: None, + }); if let Some(c) = constraints_normalize { constraints.push(c) } @@ -299,9 +298,8 @@ impl<'tcx> UniversalRegionRelationsBuilder<'_, 'tcx> { if matches!(tcx.def_kind(defining_ty_def_id), DefKind::AssocFn | DefKind::AssocConst { .. }) { for &(ty, _) in tcx.assumed_wf_types(tcx.local_parent(defining_ty_def_id)) { - let result: Result<_, ErrorGuaranteed> = param_env - .and(DeeplyNormalize { value: ty }) - .fully_perform(self.infcx, self.infcx.root_def_id, span); + let result: Result<_, ErrorGuaranteed> = + self.infcx.fully_perform(DeeplyNormalize { value: ty }, span); let Ok(TypeOpOutput { output: norm_ty, constraints: c, .. }) = result else { continue; }; @@ -354,11 +352,7 @@ impl<'tcx> UniversalRegionRelationsBuilder<'_, 'tcx> { output: normalized_outlives, constraints: constraints_normalize, error_info: _, - }) = self.infcx.param_env.and(DeeplyNormalize { value: outlives }).fully_perform( - self.infcx, - self.infcx.root_def_id, - span, - ) + }) = self.infcx.fully_perform(DeeplyNormalize { value: outlives }, span) else { self.infcx.dcx().delayed_bug(format!("could not normalize {outlives:?}")); return; @@ -381,9 +375,7 @@ impl<'tcx> UniversalRegionRelationsBuilder<'_, 'tcx> { ) -> Option<&'tcx QueryRegionConstraints<'tcx>> { let TypeOpOutput { output: bounds, constraints, .. } = self .infcx - .param_env - .and(type_op::ImpliedOutlivesBounds { ty }) - .fully_perform(self.infcx, self.infcx.root_def_id, span) + .fully_perform(type_op::ImpliedOutlivesBounds { ty }, span) .map_err(|_: ErrorGuaranteed| debug!("failed to compute implied bounds {:?}", ty)) .ok()?; debug!(?bounds, ?constraints); diff --git a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs index 840210496eb44..5a06f54cd2e41 100644 --- a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs +++ b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs @@ -15,7 +15,7 @@ use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span}; use rustc_trait_selection::error_reporting::InferCtxtErrorExt; use rustc_trait_selection::traits::ObligationCtxt; use rustc_trait_selection::traits::query::dropck_outlives; -use rustc_trait_selection::traits::query::type_op::{DropckOutlives, TypeOp, TypeOpOutput}; +use rustc_trait_selection::traits::query::type_op::{DropckOutlives, TypeOpOutput}; use tracing::debug; use crate::polonius; @@ -638,9 +638,9 @@ impl<'tcx> LivenessContext<'_, '_, 'tcx> { ) -> DropData<'tcx> { debug!("compute_drop_data(dropped_ty={:?})", dropped_ty); - let op = typeck.infcx.param_env.and(DropckOutlives { dropped_ty }); + let goal = DropckOutlives { dropped_ty }; - match op.fully_perform(typeck.infcx, typeck.root_cx.root_def_id(), DUMMY_SP) { + match typeck.infcx.fully_perform(goal, DUMMY_SP) { Ok(TypeOpOutput { output, constraints, .. }) => { DropData { dropck_result: output, region_constraint_data: constraints } } @@ -655,7 +655,9 @@ impl<'tcx> LivenessContext<'_, '_, 'tcx> { typeck.infcx.probe(|_| { let ocx = ObligationCtxt::new_with_diagnostics(&typeck.infcx); let errors = match dropck_outlives::compute_dropck_outlives_with_errors( - &ocx, op, span, + &ocx, + typeck.infcx.param_env.and(goal), + span, ) { Ok(_) => ocx.evaluate_obligations_error_on_ambiguity(), Err(e) => e, From b2a46c9d9a3773d48f1e20585e9c26bf60d3b59c Mon Sep 17 00:00:00 2001 From: Obei Sideg Date: Wed, 24 Jun 2026 18:12:42 +0300 Subject: [PATCH 08/35] Move `check_ffi_pure` into the attribute parser convert `FfiPureParser` to a full `AttributeParser` so its `finalize` can check for a sibling `#[ffi_const]` and reject `#[ffi_pure]` during attribute parsing, replacing `check_ffi_pure` in `rustc_passes`. --- .../src/attributes/link_attrs.rs | 41 ++++++++++++++----- compiler/rustc_attr_parsing/src/context.rs | 2 +- .../src/session_diagnostics.rs | 7 ++++ compiler/rustc_passes/src/check_attr.rs | 9 +--- compiler/rustc_passes/src/diagnostics.rs | 7 ---- 5 files changed, 40 insertions(+), 26 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs index f260863840080..0ff97b47fc696 100644 --- a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs @@ -14,11 +14,11 @@ use super::util::parse_single_integer; use crate::attributes::AttributeSafety; use crate::attributes::cfg::parse_cfg_entry; use crate::session_diagnostics::{ - AsNeededCompatibility, BundleNeedsStatic, EmptyLinkName, ExportSymbolsNeedsStatic, - ImportNameTypeRaw, ImportNameTypeX86, IncompatibleWasmLink, InvalidLinkModifier, - InvalidMachoSection, InvalidMachoSectionReason, LinkFrameworkApple, LinkOrdinalOutOfRange, - LinkRequiresName, MultipleModifiers, NullOnLinkName, NullOnLinkSection, RawDylibOnlyWindows, - WholeArchiveNeedsStatic, + AsNeededCompatibility, BothFfiConstAndPure, BundleNeedsStatic, EmptyLinkName, + ExportSymbolsNeedsStatic, ImportNameTypeRaw, ImportNameTypeX86, IncompatibleWasmLink, + InvalidLinkModifier, InvalidMachoSection, InvalidMachoSectionReason, LinkFrameworkApple, + LinkOrdinalOutOfRange, LinkRequiresName, MultipleModifiers, NullOnLinkName, NullOnLinkSection, + RawDylibOnlyWindows, UnusedMultiple, WholeArchiveNeedsStatic, }; pub(crate) struct LinkNameParser; @@ -563,16 +563,37 @@ impl NoArgsAttributeParser for FfiConstParser { const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::FfiConst; } -pub(crate) struct FfiPureParser; -impl NoArgsAttributeParser for FfiPureParser { - const PATH: &[Symbol] = &[sym::ffi_pure]; +#[derive(Default)] +pub(crate) struct FfiPureParser { + first_span: Option, +} + +impl AttributeParser for FfiPureParser { + const ATTRIBUTES: AcceptMapping = + &[(&[sym::ffi_pure], template!(Word), unstable!(ffi_pure), |this, cx, args| { + let _ = cx.expect_no_args(args); + let span = cx.attr_span; + // `#[ffi_pure]` may only appear once; mirror the previous `OnDuplicate::Error`. + if let Some(first) = this.first_span { + cx.emit_err(UnusedMultiple { this: span, other: first, name: sym::ffi_pure }); + } else { + this.first_span = Some(span); + } + })]; const SAFETY: AttributeSafety = AttributeSafety::Unsafe { note: "`#[ffi_pure]` functions shall have no effects except for its return value, which shall not change across two consecutive function calls with the same parameters.", unsafe_since: None, }; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::ForeignFn)]); - const STABILITY: AttributeStability = unstable!(ffi_pure); - const CREATE: fn(Span) -> AttributeKind = AttributeKind::FfiPure; + + fn finalize(self, cx: &FinalizeContext<'_, '_>) -> Option { + let span = self.first_span?; + // `#[ffi_const]` functions cannot be `#[ffi_pure]`. + if cx.all_attrs.iter().any(|a| a.word_is(sym::ffi_const)) { + cx.emit_err(BothFfiConstAndPure { attr_span: span }); + } + Some(AttributeKind::FfiPure(span)) + } } pub(crate) struct RustcStdInternalSymbolParser; diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 14e443c922711..16458f65b1116 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -143,6 +143,7 @@ attribute_parsers!( ConfusablesParser, ConstStabilityParser, DocParser, + FfiPureParser, MacroUseParser, NakedParser, OnConstParser, @@ -246,7 +247,6 @@ attribute_parsers!( Single>, Single>, Single>, - Single>, Single>, Single>, Single>, diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs index 494824a8393b3..74b028ecc35be 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -13,6 +13,13 @@ use rustc_target::spec::TargetTuple; use crate::AttributeTemplate; use crate::context::Suggestion; +#[derive(Diagnostic)] +#[diag("`#[ffi_const]` function cannot be `#[ffi_pure]`", code = E0757)] +pub(crate) struct BothFfiConstAndPure { + #[primary_span] + pub attr_span: Span, +} + #[derive(Diagnostic)] #[diag("{$attr_str} attribute cannot have empty value")] pub(crate) struct DocAliasEmpty<'a> { diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 079fdd923ac0d..c91c1776c81f4 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -219,7 +219,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { AttributeKind::NonExhaustive(attr_span) => { self.check_non_exhaustive(*attr_span, span, target, item) } - &AttributeKind::FfiPure(attr_span) => self.check_ffi_pure(attr_span, attrs), AttributeKind::MayDangle(attr_span) => self.check_may_dangle(hir_id, *attr_span), AttributeKind::Link(_, attr_span) => self.check_link(hir_id, *attr_span, target), AttributeKind::MacroExport { span, .. } => { @@ -271,6 +270,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { AttributeKind::ExportStable => (), AttributeKind::Feature(..) => (), AttributeKind::FfiConst => (), + AttributeKind::FfiPure(..) => (), AttributeKind::Fundamental => (), AttributeKind::Ignore { .. } => (), AttributeKind::InstructionSet(..) => (), @@ -1112,13 +1112,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } } - fn check_ffi_pure(&self, attr_span: Span, attrs: &[Attribute]) { - if find_attr!(attrs, FfiConst) { - // `#[ffi_const]` functions cannot be `#[ffi_pure]` - self.dcx().emit_err(diagnostics::BothFfiConstAndPure { attr_span }); - } - } - /// Checks if `#[may_dangle]` is applied to a lifetime or type generic parameter in `Drop` impl. fn check_may_dangle(&self, hir_id: HirId, attr_span: Span) { if let hir::Node::GenericParam(param) = self.tcx.hir_node(hir_id) diff --git a/compiler/rustc_passes/src/diagnostics.rs b/compiler/rustc_passes/src/diagnostics.rs index f6c4b0cf77d12..2576b84716664 100644 --- a/compiler/rustc_passes/src/diagnostics.rs +++ b/compiler/rustc_passes/src/diagnostics.rs @@ -153,13 +153,6 @@ pub(crate) struct DocMaskedNotExternCrateSelf { pub item_span: Span, } -#[derive(Diagnostic)] -#[diag("`#[ffi_const]` function cannot be `#[ffi_pure]`", code = E0757)] -pub(crate) struct BothFfiConstAndPure { - #[primary_span] - pub attr_span: Span, -} - #[derive(Diagnostic)] #[diag("`#[optimize(none)]` cannot be used with `#[inline]` attributes")] pub(crate) struct BothOptimizeNoneAndInline { From c19c46298276396bb7b27498fd5f9877ec8fd844 Mon Sep 17 00:00:00 2001 From: Obei Sideg Date: Thu, 25 Jun 2026 00:15:36 +0300 Subject: [PATCH 09/35] Use `NoArgsAttributeParser` for parsing `#[ffi_pure]` --- .../src/attributes/link_attrs.rs | 30 +++++-------------- .../rustc_attr_parsing/src/attributes/mod.rs | 26 ++++++++++++++-- compiler/rustc_attr_parsing/src/context.rs | 2 +- 3 files changed, 33 insertions(+), 25 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs index 0ff97b47fc696..713d8db1524e0 100644 --- a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs @@ -18,7 +18,7 @@ use crate::session_diagnostics::{ ExportSymbolsNeedsStatic, ImportNameTypeRaw, ImportNameTypeX86, IncompatibleWasmLink, InvalidLinkModifier, InvalidMachoSection, InvalidMachoSectionReason, LinkFrameworkApple, LinkOrdinalOutOfRange, LinkRequiresName, MultipleModifiers, NullOnLinkName, NullOnLinkSection, - RawDylibOnlyWindows, UnusedMultiple, WholeArchiveNeedsStatic, + RawDylibOnlyWindows, WholeArchiveNeedsStatic, }; pub(crate) struct LinkNameParser; @@ -563,36 +563,22 @@ impl NoArgsAttributeParser for FfiConstParser { const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::FfiConst; } -#[derive(Default)] -pub(crate) struct FfiPureParser { - first_span: Option, -} - -impl AttributeParser for FfiPureParser { - const ATTRIBUTES: AcceptMapping = - &[(&[sym::ffi_pure], template!(Word), unstable!(ffi_pure), |this, cx, args| { - let _ = cx.expect_no_args(args); - let span = cx.attr_span; - // `#[ffi_pure]` may only appear once; mirror the previous `OnDuplicate::Error`. - if let Some(first) = this.first_span { - cx.emit_err(UnusedMultiple { this: span, other: first, name: sym::ffi_pure }); - } else { - this.first_span = Some(span); - } - })]; +pub(crate) struct FfiPureParser; +impl NoArgsAttributeParser for FfiPureParser { + const PATH: &[Symbol] = &[sym::ffi_pure]; const SAFETY: AttributeSafety = AttributeSafety::Unsafe { note: "`#[ffi_pure]` functions shall have no effects except for its return value, which shall not change across two consecutive function calls with the same parameters.", unsafe_since: None, }; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::ForeignFn)]); + const STABILITY: AttributeStability = unstable!(ffi_pure); + const CREATE: fn(Span) -> AttributeKind = AttributeKind::FfiPure; - fn finalize(self, cx: &FinalizeContext<'_, '_>) -> Option { - let span = self.first_span?; + fn finalize_check(attr_span: Span, cx: &FinalizeContext<'_, '_>) { // `#[ffi_const]` functions cannot be `#[ffi_pure]`. if cx.all_attrs.iter().any(|a| a.word_is(sym::ffi_const)) { - cx.emit_err(BothFfiConstAndPure { attr_span: span }); + cx.emit_err(BothFfiConstAndPure { attr_span }); } - Some(AttributeKind::FfiPure(span)) } } diff --git a/compiler/rustc_attr_parsing/src/attributes/mod.rs b/compiler/rustc_attr_parsing/src/attributes/mod.rs index 5c64e9f2eaed6..e04341508bb7e 100644 --- a/compiler/rustc_attr_parsing/src/attributes/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/mod.rs @@ -147,6 +147,14 @@ pub(crate) trait SingleAttributeParser: 'static { /// Converts a single syntactical attribute to a single semantic attribute, or [`AttributeKind`] fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option; + + /// Optional cross-attribute validation, run once during finalization after all + /// attributes on the item have been parsed. Unlike [`convert`](Self::convert), this + /// has access to the sibling attributes via [`FinalizeContext::all_attrs`], so it can + /// reject incompatible combinations. `attr_span` is the span of this attribute. + /// + /// Defaults to a no-op. + fn finalize_check(_attr_span: Span, _cx: &FinalizeContext<'_, '_>) {} } /// Use in combination with [`SingleAttributeParser`]. @@ -177,8 +185,10 @@ impl AttributeParser for Single { const ALLOWED_TARGETS: AllowedTargets = T::ALLOWED_TARGETS; const SAFETY: AttributeSafety = T::SAFETY; - fn finalize(self, _cx: &FinalizeContext<'_, '_>) -> Option { - Some(self.1?.0) + fn finalize(self, cx: &FinalizeContext<'_, '_>) -> Option { + let (kind, span) = self.1?; + T::finalize_check(span, cx); + Some(kind) } } @@ -258,6 +268,14 @@ pub(crate) trait NoArgsAttributeParser: 'static { /// Create the [`AttributeKind`] given attribute's [`Span`]. const CREATE: fn(Span) -> AttributeKind; + + /// Optional cross-attribute validation, run once during finalization after all + /// attributes on the item have been parsed. Has access to the sibling attributes via + /// [`FinalizeContext::all_attrs`], so it can reject incompatible combinations. + /// `attr_span` is the span of this attribute. + /// + /// Defaults to a no-op. + fn finalize_check(_attr_span: Span, _cx: &FinalizeContext<'_, '_>) {} } pub(crate) struct WithoutArgs(PhantomData); @@ -280,6 +298,10 @@ impl SingleAttributeParser for WithoutArgs { let _ = cx.expect_no_args(args); Some(T::CREATE(cx.attr_span)) } + + fn finalize_check(attr_span: Span, cx: &FinalizeContext<'_, '_>) { + T::finalize_check(attr_span, cx) + } } type ConvertFn = fn(ThinVec, Span) -> AttributeKind; diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 16458f65b1116..14e443c922711 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -143,7 +143,6 @@ attribute_parsers!( ConfusablesParser, ConstStabilityParser, DocParser, - FfiPureParser, MacroUseParser, NakedParser, OnConstParser, @@ -247,6 +246,7 @@ attribute_parsers!( Single>, Single>, Single>, + Single>, Single>, Single>, Single>, From 0d2070c00920a66be724f0492379ddbcb17de880 Mon Sep 17 00:00:00 2001 From: Yilin Chen <1479826151@qq.com> Date: Thu, 25 Jun 2026 10:57:28 +0800 Subject: [PATCH 10/35] Add safety section for SliceIndex::get_unchecked(mut) --- library/core/src/slice/index.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/core/src/slice/index.rs b/library/core/src/slice/index.rs index 51ab9b5fd1eac..b82d79232becc 100644 --- a/library/core/src/slice/index.rs +++ b/library/core/src/slice/index.rs @@ -139,6 +139,8 @@ pub impl(crate) const unsafe trait SliceIndex { /// Returns a pointer to the output at this location, without /// performing any bounds checking. /// + /// # Safety + /// /// Calling this method with an out-of-bounds index or a dangling `slice` pointer /// is *[undefined behavior]* even if the resulting pointer is not used. /// @@ -149,6 +151,8 @@ pub impl(crate) const unsafe trait SliceIndex { /// Returns a mutable pointer to the output at this location, without /// performing any bounds checking. /// + /// # Safety + /// /// Calling this method with an out-of-bounds index or a dangling `slice` pointer /// is *[undefined behavior]* even if the resulting pointer is not used. /// From ba2c9baccbb22e97aefe46724a36f2f95908250e Mon Sep 17 00:00:00 2001 From: joboet Date: Thu, 25 Jun 2026 15:51:06 +0200 Subject: [PATCH 11/35] std: truncate thread names on NetBSD --- library/std/src/sys/thread/unix.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/library/std/src/sys/thread/unix.rs b/library/std/src/sys/thread/unix.rs index eb3fcff05ea9e..dd7b7940de510 100644 --- a/library/std/src/sys/thread/unix.rs +++ b/library/std/src/sys/thread/unix.rs @@ -392,6 +392,7 @@ pub fn current_os_id() -> Option { target_os = "vxworks", target_os = "cygwin", target_vendor = "apple", + target_os = "netbsd", ))] fn truncate_cstr(cstr: &CStr) -> [libc::c_char; MAX_WITH_NUL] { let mut result = [0; MAX_WITH_NUL]; @@ -463,7 +464,12 @@ pub fn set_name(name: &CStr) { #[cfg(target_os = "netbsd")] pub fn set_name(name: &CStr) { + // See https://github.com/NetBSD/src/blob/8d40872b4c550a802379f3b9c22a40212d5e149d/lib/libpthread/pthread.h#L281 + // FIXME: move to libc. + const PTHREAD_MAX_NAMELEN_NP: usize = 32; + unsafe { + let name = truncate_cstr::<{ PTHREAD_MAX_NAMELEN_NP }>(name); let res = libc::pthread_setname_np( libc::pthread_self(), c"%s".as_ptr(), From 5ba35ba2599fdfc7f168a72b567909be49e769b6 Mon Sep 17 00:00:00 2001 From: Vilim Lendvaj Date: Thu, 25 Jun 2026 22:34:35 +0200 Subject: [PATCH 12/35] Eliminate double length check in `Vec::into_array` --- library/alloc/src/vec/mod.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/library/alloc/src/vec/mod.rs b/library/alloc/src/vec/mod.rs index b37f865020fd9..c3b86a635bd0f 100644 --- a/library/alloc/src/vec/mod.rs +++ b/library/alloc/src/vec/mod.rs @@ -1760,7 +1760,11 @@ impl Vec { #[must_use] pub fn into_array(self) -> Result, Self> { if self.len() == N { - Ok(self.into_boxed_slice().into_array().ok().unwrap()) + // SAFETY: `Box::into_array` is guaranteed to return `Ok` if the + // length of the slice is equal to `N`. + // `self.into_boxed_slice().len()` is equal to `self.len()`, + // which we just checked. + Ok(unsafe { self.into_boxed_slice().into_array().unwrap_unchecked() }) } else { Err(self) } From b6a4c3aeed60f9e75690618f461936494200f048 Mon Sep 17 00:00:00 2001 From: Chronocoder Date: Wed, 24 Jun 2026 15:19:10 -0400 Subject: [PATCH 13/35] Suggest `>=` for `=>` typo in closure and call argument positions The parser suggests replacing `=>` with `>=` when it looks like a typo in a comparison, but it skipped the suggestion whenever a comma was an expected token. That excluded closure bodies used as call arguments, such as `iter.position(|x| x => &y)`, which is the case in #149805. The comma exclusion was there to avoid suggesting `>=` for a missing comma between match arms, where `=>` is a real arm arrow. Those cases have a close brace in the expected token set, while the comparison cases do not, so gate on the close brace instead of the comma. Fixes #149805 --- compiler/rustc_parse/src/parser/diagnostics.rs | 4 +++- tests/ui/asm/parse-error.stderr | 6 ++++++ tests/ui/parser/eq-gt-to-gt-eq.fixed | 6 ++++++ tests/ui/parser/eq-gt-to-gt-eq.rs | 6 ++++++ tests/ui/parser/eq-gt-to-gt-eq.stderr | 14 +++++++++++++- 5 files changed, 34 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index fbceb185c190c..7040fc60e4f1a 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -602,7 +602,9 @@ impl<'a> Parser<'a> { // Look for usages of '=>' where '>=' was probably intended if self.token == token::FatArrow && expected.iter().any(|tok| matches!(tok, TokenType::Operator | TokenType::Le)) - && !expected.iter().any(|tok| matches!(tok, TokenType::FatArrow | TokenType::Comma)) + && !expected + .iter() + .any(|tok| matches!(tok, TokenType::FatArrow | TokenType::CloseBrace)) { err.span_suggestion( self.token.span, diff --git a/tests/ui/asm/parse-error.stderr b/tests/ui/asm/parse-error.stderr index 9bb7b28b4424e..0048fa5075db2 100644 --- a/tests/ui/asm/parse-error.stderr +++ b/tests/ui/asm/parse-error.stderr @@ -57,6 +57,12 @@ error: expected one of `!`, `,`, `.`, `::`, `?`, `{`, or an operator, found `=>` | LL | asm!("{}", in(reg) foo => bar); | ^^ expected one of 7 possible tokens + | +help: you might have meant to write a "greater than or equal to" comparison + | +LL - asm!("{}", in(reg) foo => bar); +LL + asm!("{}", in(reg) foo >= bar); + | error: expected a path for argument to `sym` --> $DIR/parse-error.rs:32:24 diff --git a/tests/ui/parser/eq-gt-to-gt-eq.fixed b/tests/ui/parser/eq-gt-to-gt-eq.fixed index abb328399be2a..93940a5971f08 100644 --- a/tests/ui/parser/eq-gt-to-gt-eq.fixed +++ b/tests/ui/parser/eq-gt-to-gt-eq.fixed @@ -43,3 +43,9 @@ fn b() { _ => todo!(), } } + +fn closure() { + let a = 0; + let b = 1; + let _ = [a].iter().any(|x| x >= &b); //~ERROR +} diff --git a/tests/ui/parser/eq-gt-to-gt-eq.rs b/tests/ui/parser/eq-gt-to-gt-eq.rs index 1f57fa8328198..92ca43ba3155b 100644 --- a/tests/ui/parser/eq-gt-to-gt-eq.rs +++ b/tests/ui/parser/eq-gt-to-gt-eq.rs @@ -43,3 +43,9 @@ fn b() { _ => todo!(), } } + +fn closure() { + let a = 0; + let b = 1; + let _ = [a].iter().any(|x| x => &b); //~ERROR +} diff --git a/tests/ui/parser/eq-gt-to-gt-eq.stderr b/tests/ui/parser/eq-gt-to-gt-eq.stderr index aa47ddecce9ef..d5cf71db2df38 100644 --- a/tests/ui/parser/eq-gt-to-gt-eq.stderr +++ b/tests/ui/parser/eq-gt-to-gt-eq.stderr @@ -109,5 +109,17 @@ LL - match a => b { LL + match a >= b { | -error: aborting due to 7 previous errors +error: expected one of `!`, `)`, `,`, `.`, `::`, `?`, `{`, or an operator, found `=>` + --> $DIR/eq-gt-to-gt-eq.rs:50:34 + | +LL | let _ = [a].iter().any(|x| x => &b); + | ^^ expected one of 8 possible tokens + | +help: you might have meant to write a "greater than or equal to" comparison + | +LL - let _ = [a].iter().any(|x| x => &b); +LL + let _ = [a].iter().any(|x| x >= &b); + | + +error: aborting due to 8 previous errors From 6b9de05805cc1301e26ca9214fc2e653873f382c Mon Sep 17 00:00:00 2001 From: Emmanuel Ugwu Date: Fri, 26 Jun 2026 03:23:03 +0000 Subject: [PATCH 14/35] Attribute docs `deprecated` , `warn`, `allow`, `cfg`, `deny`, and `forbid` * Added documentation for allow, cfg, deny, forbid, deprecated and warn attribute Signed-off-by: Emmanuel Ugwu * address review feedback Signed-off-by: Emmanuel Ugwu * address review feedback Signed-off-by: Emmanuel Ugwu * address review feedback Signed-off-by: Emmanuel Ugwu * address feedback Signed-off-by: Emmanuel Ugwu * add the right deprecated link Signed-off-by: Emmanuel Ugwu * add the right deprecated link Signed-off-by: Emmanuel Ugwu * fix typo Signed-off-by: Emmanuel Ugwu * fix phrasing Signed-off-by: Emmanuel Ugwu * fix phrasing Signed-off-by: Emmanuel Ugwu * address feedback Signed-off-by: Emmanuel Ugwu * address feedback and add link to rustc book Signed-off-by: Emmanuel Ugwu * address feedback Signed-off-by: Emmanuel Ugwu --- library/std/src/attribute_docs.rs | 250 ++++++++++++++++++++++++++++++ 1 file changed, 250 insertions(+) diff --git a/library/std/src/attribute_docs.rs b/library/std/src/attribute_docs.rs index 1d8dddb7ca54a..e8d36483f3139 100644 --- a/library/std/src/attribute_docs.rs +++ b/library/std/src/attribute_docs.rs @@ -85,3 +85,253 @@ /// [`unused_must_use`]: ../rustc/lints/listing/warn-by-default.html#unused-must-use /// [the `must_use` attribute]: ../reference/attributes/diagnostics.html#the-must_use-attribute mod must_use_attribute {} + +#[doc(attribute = "allow")] +// +/// The `allow` attribute suppresses lint diagnostics that would otherwise produce +/// warnings or errors. It can be used on any lint or lint group (except those +/// set to `forbid`). +/// +/// ```rust +/// #[allow(dead_code)] +/// fn unused_function() { +/// // ... +/// } +/// +/// fn main() { +/// // `unused_function` does not generate a compiler warning. +/// } +/// ``` +/// +/// Without `#[allow(dead_code)]`, the example above would emit: +/// +/// ```text +/// warning: function `unused_function` is never used +/// --> main.rs:1:4 +/// | +/// 1 | fn unused_function() { +/// | ^^^^^^^^^^^^^^^ +/// | +/// = note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default +/// +/// warning: 1 warning emitted +/// ``` +/// +/// Multiple lints can be set to `allow` at once with commas: +/// +/// ```rust +/// #[allow(unused_variables, unused_mut)] +/// fn main() { +/// let mut x: u32 = 42; +/// } +/// ``` +/// +/// This is mostly used to prevent lint warnings or errors while still under development. +/// +/// It cannot override a lint that has been set to `forbid`. +/// +/// It's also important to consider that overusing `allow` could make code harder to maintain +/// and possibly hide issues. To mitigate this issue, using the `expect` attribute is preferred. +/// +/// `allow` can be overridden by `warn`, `deny`, and `forbid`. +/// +/// The lint checks supported by rustc can be found via `rustc -W help`, +/// along with their default settings and are documented in [the `rustc` book]. +/// +/// [the `rustc` book]: ../rustc/lints/listing/index.html +/// +/// For more information, see the Reference on [the `allow` attribute]. +/// +/// [the `allow` attribute]: ../reference/attributes/diagnostics.html#lint-check-attributes +mod allow_attribute {} + +#[doc(attribute = "cfg")] +// +/// Used for conditional compilation. +/// +/// The `cfg` attribute allows compiling an item under specific conditions, otherwise it +/// will be ignored. +/// +/// ```rust +/// // Only compiles this function for Linux. +/// #[cfg(target_os = "linux")] +/// fn platform_specific() { +/// println!("Running on Linux"); +/// } +/// +/// // Only compiles this function if not for Linux. +/// #[cfg(not(target_os = "linux"))] +/// fn platform_specific() { +/// println!("Running on something else"); +/// } +/// ``` +/// +/// Depending on the platform you're targeting, only one of these two functions will be considered +/// during the compilation. +/// +/// Conditions can also be combined with `all(...)`, `any(...)`, and `not(...)`. +/// +/// * `all`: True if all given predicates are true. +/// * `any`: True if at least one of the given predicates is true. +/// * `not`: True if the predicate is false and false if the predicate is true. +/// +/// ```rust +/// #[cfg(all(unix, target_pointer_width = "64"))] +/// fn unix_64bit() { +/// } +/// ``` +/// +/// If you want to use this mechanism in an `if` condition in your code, you +/// can use the [`cfg!`] macro. To conditionally apply an attribute, +/// see [`cfg_attr`]. +/// +/// For more information, see the Reference on [the `cfg` attribute]. +/// +/// [`cfg_attr`]: ../reference/conditional-compilation.html#the-cfg_attr-attribute +/// [the `cfg` attribute]: ../reference/conditional-compilation.html#the-cfg-attribute +mod cfg_attribute {} + +#[doc(attribute = "deny")] +// +/// Emits an error, preventing the compilation from finishing, when a lint check has failed. +/// This is useful for enforcing rules or preventing certain patterns: +/// +/// ```rust,compile_fail +/// #[deny(unused)] +/// fn foo() { +/// let x = 42; // Emits an error because x is unused. +/// } +/// ``` +/// +/// `deny` can be overridden by `allow`, `warn`, and `forbid`: +/// +/// ```rust +/// #![deny(unused)] +/// +/// #[allow(unused)] // We override the `deny` for this function. +/// fn foo() { +/// let x = 42; // No lint emitted even though `x` is unused. +/// } +/// ``` +/// +/// Multiple lints can also be set to `deny` at once: +/// +/// ```rust,compile_fail +/// #![deny(unused_imports, unused_variables)] +/// use std::collections::*; +/// +/// fn main() { +/// let mut x = 10; +/// } +/// ``` +/// +/// The lint checks supported by rustc can be found via `rustc -W help`, +/// along with their default settings and are documented in [the `rustc` book]. +/// +/// [the `rustc` book]: ../rustc/lints/listing/index.html +/// +/// For more information, see the Reference on [the `deny` attribute]. +/// +/// [the `deny` attribute]: ../reference/attributes/diagnostics.html#lint-check-attributes +mod deny_attribute {} + +#[doc(attribute = "forbid")] +// +/// Emits an error, preventing the compilation from finishing, when a lint check has failed. +/// +/// A lint set to `forbid` cannot be overridden by `allow` or `warn`. +/// Attempting either will result in a compilation error. Writing `#[deny(...)]` on the same lint inside a +/// `forbid` scope is permitted, but has no effect; the lint remains at the `forbid` level. +/// +/// This is useful for enforcing strict policies that should not be relaxed +/// anywhere in the codebase. Example: +/// +/// ```rust +/// #![forbid(unsafe_code)] +/// +/// // This would cause a compilation error if uncommented: +/// // #[allow(unsafe_code)] // error: cannot override `forbid` +/// ``` +/// +/// Multiple lints can be set to `forbid` at once: +/// +/// ```rust +/// #![forbid(unsafe_code, unused)] +/// ``` +/// +/// The lint checks supported by rustc can be found via `rustc -W help`, +/// along with their default settings and are documented in [the `rustc` book]. +/// +/// [the `rustc` book]: ../rustc/lints/listing/index.html +/// +/// For more information, see the Reference on [the `forbid` attribute]. +/// +/// [the `forbid` attribute]: ../reference/attributes/diagnostics.html#lint-check-attributes +mod forbid_attribute {} + +#[doc(attribute = "deprecated")] +// +/// Emits a warning during compilation when an item with this attribute is used. +/// `since` and `note` are optional fields giving more detail about why the item is deprecated. +/// +/// * `since`: the version since when the item is deprecated. +/// * `note`: the reason why an item is deprecated. +/// +/// Example: +/// +/// ```rust +/// #[deprecated(since = "1.0.0", note = "Use bar instead")] +/// struct Foo; +/// struct Bar; +/// ``` +/// +/// `deprecated` attribute helps developers transition away from old code by providing warnings when +/// deprecated items are used. Note that during `Cargo` builds, warnings on dependencies get silenced +/// by default, so you may not see a deprecation warning unless you build that dependency directly. +/// +/// For more information, see the Reference on [the `deprecated` attribute]. +/// +/// [the `deprecated` attribute]: ../reference/attributes/diagnostics.html#the-deprecated-attribute +mod deprecated_attribute {} + +#[doc(attribute = "warn")] +// +/// Emits a warning during compilation when a lint check failed. +/// +/// Unlike `deny` or `forbid`, `warn` does not produce a hard error: the compilation continues, but +/// the compiler emits a warning message. `warn` can be overridden by `allow`, `deny`, and `forbid`. +/// +/// Example: +/// +/// ```rust,compile_fail +/// #![allow(unused)] +/// +/// #[warn(unused)] // We override the allowed `unused` lint. +/// fn foo() { +/// // This lint warns by default even without #[warn(unused)] being explicitly set +/// let x = 42; // warning: unused variable `x` +/// } +/// ``` +/// +/// +/// Many lints, including `unused`, are already set to `warn` by default so this attribute is +/// mainly useful for lints that are normally `allow` by default. +/// +/// Multiple lints can be set to `warn` at once: +/// +/// ```rust,compile_fail +/// #[warn(unused_mut, unused_variables)] +/// fn main() { +/// let mut x = 42; +/// } +/// ``` +/// +/// The lint checks supported by rustc can be found via `rustc -W help`, +/// along with their default settings and are documented in [the `rustc` book]. +/// +/// [the `rustc` book]: ../rustc/lints/listing/index.html +/// +/// For more information, see the Reference on [the `warn` attribute]. +/// +/// [the `warn` attribute]: ../reference/attributes/diagnostics.html#lint-check-attributes +mod warn_attribute {} From 9b188dd6c50ed504ed0eaf80dfe8f4ef1b56cf5c Mon Sep 17 00:00:00 2001 From: Scott McMurray Date: Sat, 23 May 2026 15:27:28 -0700 Subject: [PATCH 15/35] cg_LLVM: Stop needing an alloca for volatile loads And while I'm here, improve the tests to check that the unaligned ones are actually unaligned, since `unaligned_volatile_load::` doesn't actually test anything. --- compiler/rustc_codegen_gcc/src/builder.rs | 3 +- .../rustc_codegen_gcc/src/intrinsic/mod.rs | 7 +- compiler/rustc_codegen_llvm/src/builder.rs | 4 +- compiler/rustc_codegen_llvm/src/context.rs | 2 +- .../rustc_codegen_llvm/src/debuginfo/gdb.rs | 6 +- compiler/rustc_codegen_llvm/src/intrinsic.rs | 46 ++++--- .../rustc_codegen_ssa/src/traits/builder.rs | 2 +- tests/codegen-llvm/i128-x86-align.rs | 6 +- tests/codegen-llvm/intrinsics/volatile.rs | 121 +++++++++++++++++- 9 files changed, 161 insertions(+), 36 deletions(-) diff --git a/compiler/rustc_codegen_gcc/src/builder.rs b/compiler/rustc_codegen_gcc/src/builder.rs index 8ae4dedff8f28..6cbc0054cc015 100644 --- a/compiler/rustc_codegen_gcc/src/builder.rs +++ b/compiler/rustc_codegen_gcc/src/builder.rs @@ -993,7 +993,8 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { loaded_value.to_rvalue() } - fn volatile_load(&mut self, ty: Type<'gcc>, ptr: RValue<'gcc>) -> RValue<'gcc> { + fn volatile_load(&mut self, ty: Type<'gcc>, ptr: RValue<'gcc>, _: Align) -> RValue<'gcc> { + // FIXME(antoyo): set alignment. let ptr = self.context.new_cast(self.location, ptr, ty.make_volatile().make_pointer()); // (FractalFir): We insert a local here, to ensure this volatile load can't move across // blocks. diff --git a/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs b/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs index a12116d5b9d39..78a4c7e88c895 100644 --- a/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs +++ b/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs @@ -5,7 +5,7 @@ mod simd; use std::iter; use gccjit::{ComparisonOp, Function, FunctionType, RValue, ToRValue, Type, UnaryOp}; -use rustc_abi::{BackendRepr, HasDataLayout, WrappingRange}; +use rustc_abi::{Align, BackendRepr, HasDataLayout, WrappingRange}; use rustc_codegen_ssa::base::wants_msvc_seh; use rustc_codegen_ssa::common::IntPredicate; use rustc_codegen_ssa::errors::InvalidMonomorphization; @@ -368,8 +368,9 @@ impl<'a, 'gcc, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tc sym::volatile_load | sym::unaligned_volatile_load => { let ptr = args[0].immediate(); - let load = self.volatile_load(result.layout.gcc_type(self), ptr); - // FIXME(antoyo): set alignment. + let abi_align = result_layout.align.abi; + let ptr_align = if name == sym::volatile_load { abi_align } else { Align::ONE }; + let load = self.volatile_load(result.layout.gcc_type(self), ptr, ptr_align); if let BackendRepr::Scalar(scalar) = result.layout.backend_repr { self.to_immediate_scalar(load, scalar) } else { diff --git a/compiler/rustc_codegen_llvm/src/builder.rs b/compiler/rustc_codegen_llvm/src/builder.rs index afb6985d21a95..7535c41cd1ed3 100644 --- a/compiler/rustc_codegen_llvm/src/builder.rs +++ b/compiler/rustc_codegen_llvm/src/builder.rs @@ -682,9 +682,9 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> { } } - fn volatile_load(&mut self, ty: &'ll Type, ptr: &'ll Value) -> &'ll Value { + fn volatile_load(&mut self, ty: &'ll Type, ptr: &'ll Value, align: Align) -> &'ll Value { unsafe { - let load = llvm::LLVMBuildLoad2(self.llbuilder, ty, ptr, UNNAMED); + let load = self.load(ty, ptr, align); llvm::LLVMSetVolatile(load, llvm::TRUE); load } diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index 84661f5160b14..6198a98e5f7ae 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -978,7 +978,7 @@ impl<'ll, 'tcx> MiscCodegenMethods<'tcx> for CodegenCx<'ll, 'tcx> { } fn intrinsic_call_expects_place_always(&self, name: Symbol) -> bool { - matches!(name, sym::volatile_load | sym::unaligned_volatile_load | sym::black_box) + matches!(name, sym::black_box) } } diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs b/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs index f88f0c2fb0994..f5b843b4e3e74 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs @@ -1,5 +1,6 @@ // .debug_gdb_scripts binary section. +use rustc_abi::Align; use rustc_codegen_ssa::base::collect_debugger_visualizers_transitive; use rustc_codegen_ssa::traits::*; use rustc_hir::attrs::DebuggerVisualizerType; @@ -18,10 +19,7 @@ pub(crate) fn insert_reference_to_gdb_debug_scripts_section_global(bx: &mut Buil let gdb_debug_scripts_section = get_or_insert_gdb_debug_scripts_section_global(bx); // Load just the first byte as that's all that's necessary to force // LLVM to keep around the reference to the global. - let volatile_load_instruction = bx.volatile_load(bx.type_i8(), gdb_debug_scripts_section); - unsafe { - llvm::LLVMSetAlignment(volatile_load_instruction, 1); - } + bx.volatile_load(bx.type_i8(), gdb_debug_scripts_section, Align::ONE); } } diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index 1caa95f369360..98e2cbac03dfe 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -354,25 +354,39 @@ impl<'ll, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> { } sym::volatile_load | sym::unaligned_volatile_load => { - let result = PlaceRef { - val: result_place.unwrap(), - layout: result_layout, - }; - + // Note that we cannot just load the `llvm_type` because we should never load non-scalars. + // Trying to do so blows up horribly in some cases -- for example loading a + // `MaybeUninint<&dyn Trait>` would load as `{ [i64x2] }` which gives assertions later + // (if we're lucky) from things not being pointers that ought to be. let ptr = args[0].immediate(); - let load = self.volatile_load(result_layout.llvm_type(self), ptr); - let align = if name == sym::unaligned_volatile_load { - 1 + let abi_align = result_layout.align.abi; + let ptr_align = if name == sym::volatile_load { abi_align } else { Align::ONE }; + if result_layout.is_zst() { + return IntrinsicResult::Operand(OperandValue::ZeroSized); + } else if let BackendRepr::Scalar(scalar) = result_layout.backend_repr { + let load = self.volatile_load(self.type_from_scalar(scalar), ptr, ptr_align); + self.to_immediate_scalar(load, scalar) } else { - result_layout.align.bytes() as u32 - }; - unsafe { - llvm::LLVMSetAlignment(load, align); - } - if !result_layout.is_zst() { - self.store_to_place(load, result.val); + // One day Rust will probably want to define how we split up a volatile load + // of something that's *not* just an ordinary scalar, but for now we can just + // use an LLVM integer type of the correct width and let it split it however. + let llty = self.type_ix(result_layout.size.bits()); + let temp = if let Some(result_place) = result_place { + PlaceRef { + val: result_place, + layout: result_layout, + } + } else { + PlaceRef::alloca(self, result_layout) + }; + let llval = self.volatile_load(llty, ptr, ptr_align); + self.store(llval, temp.val.llval, abi_align); + return if result_place.is_none() { + IntrinsicResult::Operand(self.load_operand(temp).val) + } else { + IntrinsicResult::WroteIntoPlace + }; } - return IntrinsicResult::WroteIntoPlace; } sym::volatile_store => { let dst = args[0].deref(self.cx()); diff --git a/compiler/rustc_codegen_ssa/src/traits/builder.rs b/compiler/rustc_codegen_ssa/src/traits/builder.rs index d68549c6871f4..39c2529152566 100644 --- a/compiler/rustc_codegen_ssa/src/traits/builder.rs +++ b/compiler/rustc_codegen_ssa/src/traits/builder.rs @@ -242,7 +242,7 @@ pub trait BuilderMethods<'a, 'tcx>: fn alloca_with_ty(&mut self, layout: TyAndLayout<'tcx>) -> Self::Value; fn load(&mut self, ty: Self::Type, ptr: Self::Value, align: Align) -> Self::Value; - fn volatile_load(&mut self, ty: Self::Type, ptr: Self::Value) -> Self::Value; + fn volatile_load(&mut self, ty: Self::Type, ptr: Self::Value, align: Align) -> Self::Value; fn atomic_load( &mut self, ty: Self::Type, diff --git a/tests/codegen-llvm/i128-x86-align.rs b/tests/codegen-llvm/i128-x86-align.rs index 75802b0c5056a..998821d17e80c 100644 --- a/tests/codegen-llvm/i128-x86-align.rs +++ b/tests/codegen-llvm/i128-x86-align.rs @@ -5,8 +5,6 @@ // while rustc wants it to have 16 byte alignment. This test checks that we handle this // correctly. -// CHECK: %ScalarPair = type { i32, [3 x i32], i128 } - #![feature(core_intrinsics)] #[repr(C)] @@ -62,8 +60,8 @@ pub fn load_volatile(x: &ScalarPair) -> ScalarPair { // CHECK-SAME: dereferenceable(32) %_0, // CHECK-SAME: align 16 // CHECK-SAME: dereferenceable(32) %x - // CHECK: [[LOAD:%.*]] = load volatile %ScalarPair, ptr %x, align 16 - // CHECK-NEXT: store %ScalarPair [[LOAD]], ptr %_0, align 16 + // CHECK: [[LOAD:%.*]] = load volatile i256, ptr %x, align 16 + // CHECK-NEXT: store i256 [[LOAD]], ptr %_0, align 16 // CHECK-NEXT: ret void unsafe { std::intrinsics::volatile_load(x) } } diff --git a/tests/codegen-llvm/intrinsics/volatile.rs b/tests/codegen-llvm/intrinsics/volatile.rs index 2dea5ecb2ca92..4ba4c5edd2df6 100644 --- a/tests/codegen-llvm/intrinsics/volatile.rs +++ b/tests/codegen-llvm/intrinsics/volatile.rs @@ -5,6 +5,11 @@ use std::intrinsics; +#[repr(align(32))] +pub struct CustomZst; + +type UninitFatPointer = std::mem::MaybeUninit<&'static dyn std::fmt::Debug>; + // CHECK-LABEL: @volatile_copy_memory #[no_mangle] pub unsafe fn volatile_copy_memory(a: *mut u8, b: *const u8) { @@ -28,8 +33,62 @@ pub unsafe fn volatile_set_memory(a: *mut u8, b: u8) { // CHECK-LABEL: @volatile_load #[no_mangle] -pub unsafe fn volatile_load(a: *const u8) -> u8 { - // CHECK: load volatile +pub unsafe fn volatile_load(a: *const u16) -> u16 { + // CHECK: [[TEMP:%.+]] = load volatile i16, ptr %a + // CHECK-SAME: align 2{{,|$}} + // CHECK-NEXT: ret i16 [[TEMP]] + intrinsics::volatile_load(a) +} + +// CHECK-LABEL: @volatile_load_bool +#[no_mangle] +pub unsafe fn volatile_load_bool(a: *const bool) -> bool { + // CHECK: [[TEMP:%.+]] = load volatile i8, ptr %a + // CHECK-SAME: align 1{{,|$}} + // CHECK: [[TRUNC:%.+]] = trunc nuw i8 [[TEMP]] to i1 + // CHECK: ret i1 [[TRUNC]] + intrinsics::volatile_load(a) +} + +// CHECK-LABEL: @volatile_load_zst +#[no_mangle] +pub unsafe fn volatile_load_zst(a: *const CustomZst) -> CustomZst { + // CHECK: start: + // CHECK-NEXT: ret void + intrinsics::volatile_load(a) +} + +// CHECK-LABEL: @volatile_load_array +// CHECK-SAME: ptr{{.+}}sret([16 x i8]){{.+}}%_0 +#[no_mangle] +pub unsafe fn volatile_load_array(a: *const [u16; 8]) -> [u16; 8] { + // CHECK-NOT: alloca + // CHECK: [[TEMP:%.+]] = load volatile i128, ptr %a, + // CHECK-SAME: align 2{{,|$}} + // CHECK: store i128 [[TEMP]], ptr %_0, + // CHECK-SAME: align 2{{,|$}} + // CHECK-NEXT: ret void + intrinsics::volatile_load(a) +} + +// CHECK-LABEL: @volatile_load_fat +#[no_mangle] +pub unsafe fn volatile_load_fat(a: *const UninitFatPointer) -> UninitFatPointer { + // CHECK: [[ALLOCA:%.+]] = alloca + // CHECK-SAME: [[SIZE:4|8|16]] x i8 + // CHECK-SAME: align [[ALIGN:2|4|8]] + + // CHECK: [[TEMP:%.+]] = load volatile [[INT:i32|i64|i128]], ptr %a, + // CHECK-SAME: align [[ALIGN]]{{,|$}} + // CHECK: store [[INT]] [[TEMP]], ptr [[ALLOCA]], + // CHECK-SAME: align [[ALIGN]]{{,|$}} + + // CHECK: [[T0:%.+]] = load ptr, ptr [[ALLOCA]], align [[ALIGN]] + // CHECK: [[T1:%.+]] = getelementptr inbounds i8, ptr [[ALLOCA]] + // CHECK: [[T2:%.+]] = load ptr, ptr [[T1]], align [[ALIGN]] + // CHECK: [[P1:%.+]] = insertvalue { ptr, ptr } poison, ptr [[T0]], 0 + // CHECK: [[P2:%.+]] = insertvalue { ptr, ptr } [[P1]], ptr [[T2]], 1 + // CHECK: ret { ptr, ptr } [[P2]] intrinsics::volatile_load(a) } @@ -42,8 +101,62 @@ pub unsafe fn volatile_store(a: *mut u8, b: u8) { // CHECK-LABEL: @unaligned_volatile_load #[no_mangle] -pub unsafe fn unaligned_volatile_load(a: *const u8) -> u8 { - // CHECK: load volatile +pub unsafe fn unaligned_volatile_load(a: *const u16) -> u16 { + // CHECK: [[TEMP:%.+]] = load volatile i16, ptr %a + // CHECK-SAME: align 1{{,|$}} + // CHECK-NEXT: ret i16 [[TEMP]] + intrinsics::unaligned_volatile_load(a) +} + +// CHECK-LABEL: @unaligned_volatile_load_bool +#[no_mangle] +pub unsafe fn unaligned_volatile_load_bool(a: *const bool) -> bool { + // CHECK: [[TEMP:%.+]] = load volatile i8, ptr %a + // CHECK-SAME: align 1{{,|$}} + // CHECK: [[TRUNC:%.+]] = trunc nuw i8 [[TEMP]] to i1 + // CHECK: ret i1 [[TRUNC]] + intrinsics::unaligned_volatile_load(a) +} + +// CHECK-LABEL: @unaligned_volatile_load_zst +#[no_mangle] +pub unsafe fn unaligned_volatile_load_zst(a: *const CustomZst) -> CustomZst { + // CHECK: start: + // CHECK-NEXT: ret void + intrinsics::unaligned_volatile_load(a) +} + +// CHECK-LABEL: @unaligned_volatile_load_array +// CHECK-SAME: ptr{{.+}}sret([16 x i8]){{.+}}%_0, +#[no_mangle] +pub unsafe fn unaligned_volatile_load_array(a: *const [u16; 8]) -> [u16; 8] { + // CHECK-NOT: alloca + // CHECK: [[TEMP:%.+]] = load volatile i128, ptr %a, + // CHECK-SAME: align 1{{,|$}} + // CHECK: store i128 [[TEMP]], ptr %_0, + // CHECK-SAME: align 2{{,|$}} + // CHECK-NEXT: ret void + intrinsics::unaligned_volatile_load(a) +} + +// CHECK-LABEL: @unaligned_volatile_load_fat +#[no_mangle] +pub unsafe fn unaligned_volatile_load_fat(a: *const UninitFatPointer) -> UninitFatPointer { + // CHECK: [[ALLOCA:%.+]] = alloca + // CHECK-SAME: [[SIZE]] x i8 + // CHECK-SAME: align [[ALIGN]] + + // CHECK: [[TEMP:%.+]] = load volatile [[INT]], ptr %a, + // CHECK-SAME: align 1{{,|$}} + // CHECK: store [[INT]] [[TEMP]], ptr [[ALLOCA]], + // CHECK-SAME: align [[ALIGN]]{{,|$}} + + // CHECK: [[T0:%.+]] = load ptr, ptr [[ALLOCA]], align [[ALIGN]] + // CHECK: [[T1:%.+]] = getelementptr inbounds i8, ptr [[ALLOCA]] + // CHECK: [[T2:%.+]] = load ptr, ptr [[T1]], align [[ALIGN]] + // CHECK: [[P1:%.+]] = insertvalue { ptr, ptr } poison, ptr [[T0]], 0 + // CHECK: [[P2:%.+]] = insertvalue { ptr, ptr } [[P1]], ptr [[T2]], 1 + // CHECK: ret { ptr, ptr } [[P2]] intrinsics::unaligned_volatile_load(a) } From 6d6fcc9f77e4473047c3763c155a9cf5995c3142 Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Fri, 26 Jun 2026 14:36:44 +0900 Subject: [PATCH 16/35] add regression test for clone suggestion empty obligations --- ...constrained-impl-param-clone-suggestion.rs | 20 +++++++++++++++++++ ...trained-impl-param-clone-suggestion.stderr | 19 ++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 tests/ui/moves/unconstrained-impl-param-clone-suggestion.rs create mode 100644 tests/ui/moves/unconstrained-impl-param-clone-suggestion.stderr diff --git a/tests/ui/moves/unconstrained-impl-param-clone-suggestion.rs b/tests/ui/moves/unconstrained-impl-param-clone-suggestion.rs new file mode 100644 index 0000000000000..694c95da7b482 --- /dev/null +++ b/tests/ui/moves/unconstrained-impl-param-clone-suggestion.rs @@ -0,0 +1,20 @@ +// Regression test for https://github.com/rust-lang/rust/issues/148631 + +struct C; + +struct S(T); + +trait Tr {} + +impl Clone for S +//~^ ERROR the type parameter `T` is not constrained +where + S: Tr, +{ + fn clone(&self) -> Self { + *self + //~^ ERROR cannot move out of `*self` + } +} + +fn main() {} diff --git a/tests/ui/moves/unconstrained-impl-param-clone-suggestion.stderr b/tests/ui/moves/unconstrained-impl-param-clone-suggestion.stderr new file mode 100644 index 0000000000000..9cf17045b0296 --- /dev/null +++ b/tests/ui/moves/unconstrained-impl-param-clone-suggestion.stderr @@ -0,0 +1,19 @@ +error[E0207]: the type parameter `T` is not constrained by the impl trait, self type, or predicates + --> $DIR/unconstrained-impl-param-clone-suggestion.rs:9:6 + | +LL | impl Clone for S + | -^- + | || + | |unconstrained type parameter + | help: remove the unused type parameter `T` + +error[E0507]: cannot move out of `*self` which is behind a shared reference + --> $DIR/unconstrained-impl-param-clone-suggestion.rs:15:9 + | +LL | *self + | ^^^^^ move occurs because `*self` has type `S`, which does not implement the `Copy` trait + +error: aborting due to 2 previous errors + +Some errors have detailed explanations: E0207, E0507. +For more information about an error, try `rustc --explain E0207`. From f08762cf489011e9d91fb5dbe47f6d31bcb2e83e Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Fri, 26 Jun 2026 14:38:51 +0900 Subject: [PATCH 17/35] guard clone suggestion against empty obligation errors --- .../src/diagnostics/conflict_errors.rs | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs index caa566e79e29b..6acf685459cf3 100644 --- a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs @@ -1461,15 +1461,19 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { let cause = ObligationCause::misc(expr.span, self.mir_def_id()); ocx.register_bound(cause, self.infcx.param_env, ty, clone_trait); let errors = ocx.evaluate_obligations_error_on_ambiguity(); - if errors.iter().all(|error| { - match error.obligation.predicate.as_clause().and_then(|c| c.as_trait_clause()) { - Some(clause) => match clause.self_ty().skip_binder().kind() { - ty::Adt(def, _) => def.did().is_local() && clause.def_id() == clone_trait, - _ => false, - }, - None => false, - } - }) { + if !errors.is_empty() + && errors.iter().all(|error| { + match error.obligation.predicate.as_clause().and_then(|c| c.as_trait_clause()) { + Some(clause) => match clause.self_ty().skip_binder().kind() { + ty::Adt(def, _) => { + def.did().is_local() && clause.def_id() == clone_trait + } + _ => false, + }, + None => false, + } + }) + { let mut type_spans = vec![]; let mut types = FxIndexSet::default(); for clause in errors From c200d2fa5625ea8ddcd1e68ca65a6eaaf4b6cbfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Wed, 24 Jun 2026 14:53:00 +0200 Subject: [PATCH 18/35] fixup the refactoring errors in 156246 --- .../src/solve/eval_ctxt/mod.rs | 6 +-- .../src/solve/eval_ctxt/probe.rs | 6 +-- .../src/solve/normalizes_to.rs | 50 +++++++------------ .../src/solve/search_graph.rs | 15 ++---- .../src/solve/trait_goals.rs | 17 +++---- 5 files changed, 32 insertions(+), 62 deletions(-) diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs index 05532cdb8924b..fa6de4a431727 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs @@ -462,7 +462,7 @@ where Ok(i) => Ok(i), Err(NoSolutionOrRerunNonErased::NoSolution(NoSolution)) => Err(NoSolution), Err(NoSolutionOrRerunNonErased::RerunNonErased(_)) => { - // check th t the opaque_accesses state mirrors the result we got. + // Check that the opaque_accesses state mirrors the result we got. assert!(opaque_accesses.should_bail().is_err()); Err(NoSolution) } @@ -1442,7 +1442,7 @@ where uv: ty::UnevaluatedConst, ) -> Result, RerunNonErased> { if self.typing_mode().is_erased_not_coherence() { - self.opaque_accesses.rerun_always(RerunReason::EvaluateConst)?; + match self.opaque_accesses.rerun_always(RerunReason::EvaluateConst)? {} } Ok(self.delegate.evaluate_const(param_env, uv)) @@ -1515,7 +1515,7 @@ where symbol: I::Symbol, ) -> Result { if self.typing_mode().is_erased_not_coherence() { - self.opaque_accesses.rerun_always(RerunReason::MayUseUnstableFeature)?; + match self.opaque_accesses.rerun_always(RerunReason::MayUseUnstableFeature)? {} } Ok(may_use_unstable_feature(&**self.delegate, param_env, symbol)) diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/probe.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/probe.rs index 1c5d6e0b14c65..d9d18bdeea7e7 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/probe.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/probe.rs @@ -98,10 +98,8 @@ where outer.opaque_accesses.update(nested.opaque_accesses)?; - let r = match r.map_err_to_rerun()? { - Ok(i) => Ok(i), - Err(NoSolution) => Err(NoSolution), - }; + // Unwrap is unreachable, we would have returned on the line above. + let r = r.map_err_to_rerun().unwrap(); if !nested.inspect.is_noop() { let probe_kind = probe_kind(&r); diff --git a/compiler/rustc_next_trait_solver/src/solve/normalizes_to.rs b/compiler/rustc_next_trait_solver/src/solve/normalizes_to.rs index e0290dfbdebdf..eeba18434ccb8 100644 --- a/compiler/rustc_next_trait_solver/src/solve/normalizes_to.rs +++ b/compiler/rustc_next_trait_solver/src/solve/normalizes_to.rs @@ -81,15 +81,10 @@ where None }, |ecx| { - ecx.probe(|&result| ProbeKind::RigidAlias { result }) - .enter(|this| { - this.structurally_instantiate_normalizes_to_term( - goal, - goal.predicate.alias, - ); - this.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) - }) - .map_err(Into::into) + ecx.probe(|&result| ProbeKind::RigidAlias { result }).enter(|this| { + this.structurally_instantiate_normalizes_to_term(goal, goal.predicate.alias); + this.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) + }) }, ) } @@ -351,11 +346,9 @@ where GoalSource::Misc, goal.with(cx, PredicateKind::Ambiguous), )?; - return ecx - .evaluate_added_goals_and_make_canonical_response( - Certainty::Yes, - ) - .map_err(Into::into); + return ecx.evaluate_added_goals_and_make_canonical_response( + Certainty::Yes, + ); } // Outside of coherence, we treat the associated item as rigid instead. ty::TypingMode::Typeck { .. } @@ -367,11 +360,9 @@ where goal, goal.predicate.alias, ); - return ecx - .evaluate_added_goals_and_make_canonical_response( - Certainty::Yes, - ) - .map_err(Into::into); + return ecx.evaluate_added_goals_and_make_canonical_response( + Certainty::Yes, + ); } }; } @@ -401,10 +392,10 @@ where // This is not the case here and we only prefer adding an ambiguous // nested goal for consistency. ecx.add_goal(GoalSource::Misc, goal.with(cx, PredicateKind::Ambiguous))?; - return then(ecx, Certainty::Yes).map_err(Into::into); + return then(ecx, Certainty::Yes); } else { ecx.structurally_instantiate_normalizes_to_term(goal, goal.predicate.alias); - return then(ecx, Certainty::Yes).map_err(Into::into); + return then(ecx, Certainty::Yes); } } else { return error_response(ecx, cx.delay_bug("missing item")); @@ -472,7 +463,7 @@ where }; ecx.instantiate_normalizes_to_term(goal, term)?; - ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes).map_err(Into::into) + ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) }) } @@ -572,7 +563,6 @@ where pred, [(GoalSource::ImplWhereBound, goal.with(cx, output_is_sized_pred))], ) - .map_err(Into::into) } fn consider_builtin_async_fn_trait_candidates( @@ -759,8 +749,9 @@ where // and opaque types: If the `self_ty` is `Sized`, then the metadata is `()`. // FIXME(ptr_metadata): This impl overlaps with the other impls and shouldn't // exist. Instead, `Pointee` should be a supertrait of `Sized`. - let alias_bound_result = - ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|ecx| { + let alias_bound_result = ecx + .probe_builtin_trait_candidate(BuiltinImplSource::Misc) + .enter(|ecx| { let sized_predicate = ty::TraitRef::new( cx, cx.require_trait_lang_item(SolverTraitLangItem::Sized), @@ -769,12 +760,8 @@ where ecx.add_goal(GoalSource::Misc, goal.with(cx, sized_predicate))?; ecx.instantiate_normalizes_to_term(goal, Ty::new_unit(cx).into())?; ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) - }); - - let alias_bound_result = match alias_bound_result.map_err_to_rerun()? { - Ok(i) => Ok(i), - Err(NoSolution) => Err(NoSolution), - }; + }) + .map_err_to_rerun()?; // In case the dummy alias-bound candidate does not apply, we instead treat this projection // as rigid. @@ -900,7 +887,6 @@ where // but that's already proven by the generator being WF. [], ) - .map_err(Into::into) } fn consider_builtin_fused_iterator_candidate( diff --git a/compiler/rustc_next_trait_solver/src/solve/search_graph.rs b/compiler/rustc_next_trait_solver/src/solve/search_graph.rs index 549d5d903c045..778826ba60aee 100644 --- a/compiler/rustc_next_trait_solver/src/solve/search_graph.rs +++ b/compiler/rustc_next_trait_solver/src/solve/search_graph.rs @@ -4,7 +4,7 @@ use std::marker::PhantomData; use rustc_type_ir::data_structures::ensure_sufficient_stack; use rustc_type_ir::search_graph::{self, PathKind}; use rustc_type_ir::solve::{ - AccessedOpaques, CanonicalInput, Certainty, NoSolution, NoSolutionOrRerunNonErased, QueryResult, + AccessedOpaques, CanonicalInput, Certainty, NoSolution, QueryResult, RerunResultExt, }; use rustc_type_ir::{Interner, MayBeErased, TypingMode}; @@ -141,17 +141,8 @@ where ) -> (QueryResult, AccessedOpaques) { ensure_sufficient_stack(|| { EvalCtxt::enter_canonical(cx, search_graph, input, inspect, |ecx, goal| { - let result = ecx.compute_goal(goal); - - // if we're in `RerunNonErased`, don't even bother with inspect, - // and immediately return - let result = match result { - Ok(i) => Ok(i), - Err(NoSolutionOrRerunNonErased::NoSolution(NoSolution)) => Err(NoSolution), - Err(NoSolutionOrRerunNonErased::RerunNonErased(e)) => { - return Err(e.into()); - } - }; + // if we're in `RerunNonErased`, don't even bother with inspect, and immediately return + let result = ecx.compute_goal(goal).map_err_to_rerun()?; ecx.inspect.query_result(result); result.map_err(Into::into) 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 79e0af72710fb..5c002d09a75cc 100644 --- a/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs +++ b/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs @@ -119,7 +119,7 @@ where .map(|pred| goal.with(cx, pred)), )?; - then(ecx, maximal_certainty).map_err(Into::into) + then(ecx, maximal_certainty) }) } @@ -399,7 +399,6 @@ where pred, [(GoalSource::ImplWhereBound, goal.with(cx, output_is_sized_pred))], ) - .map_err(Into::into) } fn consider_builtin_async_fn_trait_candidates( @@ -450,7 +449,6 @@ where .chain(nested_preds.into_iter().map(|pred| goal.with(cx, pred))) .map(|goal| (GoalSource::ImplWhereBound, goal)), ) - .map_err(Into::into) } fn consider_builtin_async_fn_kind_helper_candidate( @@ -696,7 +694,7 @@ where goal.predicate.trait_ref.args.type_at(1), assume, )?; - ecx.evaluate_added_goals_and_make_canonical_response(certainty).map_err(Into::into) + ecx.evaluate_added_goals_and_make_canonical_response(certainty) }, ) } @@ -1085,7 +1083,6 @@ where ecx.try_evaluate_added_goals() }, ) - .map_err(Into::into) }) .is_ok() }; @@ -1124,11 +1121,9 @@ where return Err(NoSolution.into()); }; if matching_projections.next().is_some() { - return ecx - .evaluate_added_goals_and_make_canonical_response( - Certainty::AMBIGUOUS, - ) - .map_err(Into::into); + return ecx.evaluate_added_goals_and_make_canonical_response( + Certainty::AMBIGUOUS, + ); } ecx.enter_forall_with_assumptions( target_projection, @@ -1156,7 +1151,7 @@ where Goal::new(ecx.cx(), param_env, ty::OutlivesPredicate(a_region, b_region)), )?; - ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes).map_err(Into::into) + ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) }) } From 6c52a7089765e3afcb2394a229f7c25429e3fabf Mon Sep 17 00:00:00 2001 From: teor Date: Mon, 22 Jun 2026 15:05:25 +0200 Subject: [PATCH 19/35] Limit splat AST and FnDecl to 255 args And remove redundant const fn --- compiler/rustc_ast/src/ast.rs | 10 +++-- compiler/rustc_ast_lowering/src/delegation.rs | 2 +- .../rustc_ast_passes/src/ast_validation.rs | 6 +-- compiler/rustc_hir/src/hir.rs | 41 ++++++++++--------- compiler/rustc_type_ir/src/macros.rs | 1 + 5 files changed, 32 insertions(+), 28 deletions(-) diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 11a531353eba0..bd5ce5d18b839 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -3059,13 +3059,15 @@ impl FnDecl { } /// The marker index for "no splatted arguments". + /// Higher values are also not supported, for performance reasons. + /// /// Must have the same value as `FnSigKind::NO_SPLATTED_ARG_INDEX` and `FnDeclFlags::NO_SPLATTED_ARG_INDEX`. - pub const NO_SPLATTED_ARG_INDEX: u16 = u16::MAX; + pub const NO_SPLATTED_ARG_INDEX: u8 = u8::MAX; /// Returns a splatted argument index, if any are present. - pub fn splatted(&self) -> Option { + pub fn splatted(&self) -> Option { self.inputs.iter().enumerate().find_map(|(index, arg)| { - if index == Self::NO_SPLATTED_ARG_INDEX as usize { + if index >= usize::from(Self::NO_SPLATTED_ARG_INDEX) { // AST validation has already checked the splatted argument index is valid, so just // ignore invalid indexes here. None @@ -3073,7 +3075,7 @@ impl FnDecl { arg.attrs .iter() .any(|attr| attr.has_name(sym::splat)) - .then_some(u16::try_from(index).unwrap()) + .then_some(u8::try_from(index).unwrap()) } }) } diff --git a/compiler/rustc_ast_lowering/src/delegation.rs b/compiler/rustc_ast_lowering/src/delegation.rs index fc466246bf137..a631f8e046638 100644 --- a/compiler/rustc_ast_lowering/src/delegation.rs +++ b/compiler/rustc_ast_lowering/src/delegation.rs @@ -93,7 +93,7 @@ struct ParamInfo { pub c_variadic: bool, /// The index of the splatted parameter, if any. - pub splatted: Option, + pub splatted: Option, } const PARENT_ID: hir::ItemLocalId = hir::ItemLocalId::ZERO; diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index 186a43ca1693b..06925994b052c 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -412,11 +412,11 @@ impl<'a> AstValidator<'a> { }) .unzip(); - // A splatted argument at the "no splatted" marker index is not supported (this is an - // unlikely edge case). + // A splatted argument greater than or equal to the "no splatted" marker index is not + // supported. if let (Some(&splatted_arg_index), Some(&splatted_span)) = (splatted_arg_indexes.last(), splatted_spans.last()) - && splatted_arg_index == FnDecl::NO_SPLATTED_ARG_INDEX + && splatted_arg_index >= u16::from(FnDecl::NO_SPLATTED_ARG_INDEX) { self.dcx().emit_err(diagnostics::InvalidSplattedArg { splatted_arg_index, diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index f429d14e3a51c..6d2e68d20bf30 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -4045,12 +4045,12 @@ pub struct Param<'hir> { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SplattedArgIndexError { /// The splatted argument index is invalid. - /// Currently the only unsupported index is `u16::MAX`, which is used to indicate that no argument - /// is splatted. - InvalidIndex { splatted_arg_index: u16 }, + /// A `u8::MAX` argument index used to indicate that no argument is splatted. + /// Higher values are also not supported, for performance reasons. + InvalidIndex { splatted_arg_index: u8 }, /// The splatted argument index is outside the bounds of the function arguments. - OutOfBounds { splatted_arg_index: u16, args_len: u16 }, + OutOfBounds { splatted_arg_index: u8, args_len: u16 }, } /// Contains the packed non-type fields of a function declaration. @@ -4061,9 +4061,9 @@ pub struct FnDeclFlags { flags: u8, /// Which function argument is splatted into multiple arguments in callers, if any? - /// Splatting functions with `u16::MAX` arguments is not supported, see `FnSigKind` for + /// Splatting functions with `>= u8::MAX` arguments is not supported, see `FnSigKind` for /// details. - splatted: u16, + splatted: u8, } impl fmt::Debug for FnDeclFlags { @@ -4101,13 +4101,13 @@ impl FnDeclFlags { /// Marker index for "no splatted argument". /// Must have the same value as `FnSigKind::NO_SPLATTED_ARG_INDEX` and `rustc_ast::FnDecl::NO_SPLATTED_ARG_INDEX`. - const NO_SPLATTED_ARG_INDEX: u16 = u16::MAX; + const NO_SPLATTED_ARG_INDEX: u8 = u8::MAX; /// Create a new FnDeclKind with no implicit self, no lifetime elision, no C-style variadic /// argument, and no splatting. /// To modify these flags, use the `set_*` methods, for readability. // FIXME: use Default instead when that trait is const stable. - pub const fn default() -> Self { + pub fn default() -> Self { Self { flags: 0, splatted: 0 } .set_implicit_self(ImplicitSelfKind::None) .set_lifetime_elision_allowed(false) @@ -4117,7 +4117,7 @@ impl FnDeclFlags { /// Set the implicit self kind. #[must_use = "this method does not modify the receiver"] - pub const fn set_implicit_self(mut self, implicit_self: ImplicitSelfKind) -> Self { + pub fn set_implicit_self(mut self, implicit_self: ImplicitSelfKind) -> Self { self.flags &= !Self::IMPLICIT_SELF_MASK; match implicit_self { @@ -4133,7 +4133,7 @@ impl FnDeclFlags { /// Set the C-style variadic argument flag. #[must_use = "this method does not modify the receiver"] - pub const fn set_c_variadic(mut self, c_variadic: bool) -> Self { + pub fn set_c_variadic(mut self, c_variadic: bool) -> Self { if c_variadic { self.flags |= Self::C_VARIADIC_FLAG; } else { @@ -4145,7 +4145,7 @@ impl FnDeclFlags { /// Set the lifetime elision allowed flag. #[must_use = "this method does not modify the receiver"] - pub const fn set_lifetime_elision_allowed(mut self, allowed: bool) -> Self { + pub fn set_lifetime_elision_allowed(mut self, allowed: bool) -> Self { if allowed { self.flags |= Self::LIFETIME_ELISION_ALLOWED_FLAG; } else { @@ -4158,16 +4158,17 @@ impl FnDeclFlags { /// Set the splatted argument index. /// The number of function arguments is used for error checking. #[must_use = "this method does not modify the receiver"] - pub const fn set_splatted( + pub fn set_splatted( mut self, - splatted: Option, + splatted: Option, args_len: usize, ) -> Result { if let Some(splatted_arg_index) = splatted { if splatted_arg_index == Self::NO_SPLATTED_ARG_INDEX { // This index value is used as a marker for "no splatting", so it is unsupported. + // Higher values are also not supported, for performance reasons. return Err(SplattedArgIndexError::InvalidIndex { splatted_arg_index }); - } else if splatted_arg_index as usize >= args_len { + } else if usize::from(splatted_arg_index) >= args_len { return Err(SplattedArgIndexError::OutOfBounds { splatted_arg_index, args_len: args_len as u16, @@ -4184,14 +4185,14 @@ impl FnDeclFlags { /// Set "no splatted arguments" for the function declaration. #[must_use = "this method does not modify the receiver"] - pub const fn set_no_splatted_args(mut self) -> Self { + pub fn set_no_splatted_args(mut self) -> Self { self.splatted = Self::NO_SPLATTED_ARG_INDEX; self } /// Get the implicit self kind. - pub const fn implicit_self(self) -> ImplicitSelfKind { + pub fn implicit_self(self) -> ImplicitSelfKind { match self.flags & Self::IMPLICIT_SELF_MASK { 0 => ImplicitSelfKind::None, 1 => ImplicitSelfKind::Imm, @@ -4203,17 +4204,17 @@ impl FnDeclFlags { } /// Do the function arguments end with a C-style variadic argument? - pub const fn c_variadic(self) -> bool { + pub fn c_variadic(self) -> bool { self.flags & Self::C_VARIADIC_FLAG != 0 } /// Is lifetime elision allowed? - pub const fn lifetime_elision_allowed(self) -> bool { + pub fn lifetime_elision_allowed(self) -> bool { self.flags & Self::LIFETIME_ELISION_ALLOWED_FLAG != 0 } /// Get the splatted argument index, if any. - pub const fn splatted(self) -> Option { + pub fn splatted(self) -> Option { if self.splatted == Self::NO_SPLATTED_ARG_INDEX { None } else { Some(self.splatted) } } } @@ -4263,7 +4264,7 @@ impl<'hir> FnDecl<'hir> { self.fn_decl_kind.lifetime_elision_allowed() } - pub fn splatted(&self) -> Option { + pub fn splatted(&self) -> Option { self.fn_decl_kind.splatted() } diff --git a/compiler/rustc_type_ir/src/macros.rs b/compiler/rustc_type_ir/src/macros.rs index 857738d207b4f..ea92699958094 100644 --- a/compiler/rustc_type_ir/src/macros.rs +++ b/compiler/rustc_type_ir/src/macros.rs @@ -45,6 +45,7 @@ TrivialTypeTraversalImpls! { (), bool, usize, + u8, u16, u32, u64, From 5d0c6bad9418631109c36e656c6f32435136c89c Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 10 Jun 2026 12:05:50 +0200 Subject: [PATCH 20/35] Impl HIR FnSig for #[splat] --- compiler/rustc_ast_lowering/src/delegation.rs | 3 +- compiler/rustc_hir_analysis/src/check/mod.rs | 4 +- .../rustc_hir_analysis/src/check/wfcheck.rs | 11 +- .../src/hir_ty_lowering/mod.rs | 5 +- compiler/rustc_hir_typeck/src/closure.rs | 1 + compiler/rustc_hir_typeck/src/diagnostics.rs | 1 + compiler/rustc_lint/src/foreign_modules.rs | 8 + compiler/rustc_middle/src/ty/context.rs | 2 + compiler/rustc_middle/src/ty/error.rs | 14 ++ .../src/unstable/convert/internal.rs | 1 + .../src/error_reporting/infer/mod.rs | 10 ++ .../src/error_reporting/traits/suggestions.rs | 1 + compiler/rustc_type_ir/src/error.rs | 3 +- compiler/rustc_type_ir/src/relate.rs | 4 + compiler/rustc_type_ir/src/ty_kind.rs | 155 +++++++++++++++--- compiler/rustc_type_ir/src/ty_kind/closure.rs | 2 +- src/tools/miri/src/helpers.rs | 2 + src/tools/miri/src/shims/sig.rs | 2 +- .../crates/hir-ty/src/infer/callee.rs | 2 + .../rust-analyzer/crates/hir-ty/src/lib.rs | 1 + .../rust-analyzer/crates/hir-ty/src/lower.rs | 1 + .../crates/hir-ty/src/next_solver/interner.rs | 1 + .../crates/hir-ty/src/next_solver/ty.rs | 1 + tests/ui/symbol-names/basic.legacy.stderr | 4 +- .../ui/symbol-names/issue-60925.legacy.stderr | 4 +- 25 files changed, 210 insertions(+), 33 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/delegation.rs b/compiler/rustc_ast_lowering/src/delegation.rs index a631f8e046638..078ebd6987ae3 100644 --- a/compiler/rustc_ast_lowering/src/delegation.rs +++ b/compiler/rustc_ast_lowering/src/delegation.rs @@ -364,11 +364,10 @@ impl<'hir> LoweringContext<'_, 'hir> { fn param_info(&self, def_id: DefId) -> ParamInfo { let sig = self.tcx.fn_sig(def_id).skip_binder().skip_binder(); - // FIXME(splat): use `sig.splatted()` once FnSig has it ParamInfo { param_count: sig.inputs().len() + usize::from(sig.c_variadic()), c_variadic: sig.c_variadic(), - splatted: None, + splatted: sig.splatted(), } } diff --git a/compiler/rustc_hir_analysis/src/check/mod.rs b/compiler/rustc_hir_analysis/src/check/mod.rs index e1bbc8f0641de..9d8e0a512ab66 100644 --- a/compiler/rustc_hir_analysis/src/check/mod.rs +++ b/compiler/rustc_hir_analysis/src/check/mod.rs @@ -444,11 +444,13 @@ fn fn_sig_suggestion<'tcx>( predicates: impl IntoIterator, Span)>, assoc: ty::AssocItem, ) -> String { + let splatted_arg_index = sig.splatted().map(usize::from); let args = sig .inputs() .iter() .enumerate() .map(|(i, ty)| { + let splat = if splatted_arg_index == Some(i) { "#[splat] " } else { "" }; let arg_ty = match ty.kind() { ty::Param(_) if assoc.is_method() && i == 0 => "self".to_string(), ty::Ref(reg, ref_ty, mutability) if i == 0 => { @@ -477,7 +479,7 @@ fn fn_sig_suggestion<'tcx>( } } }; - Some(format!("{arg_ty}")) + Some(format!("{splat}{arg_ty}")) }) .chain(std::iter::once(if sig.c_variadic() { Some("...".to_string()) } else { None })) .flatten() diff --git a/compiler/rustc_hir_analysis/src/check/wfcheck.rs b/compiler/rustc_hir_analysis/src/check/wfcheck.rs index bf8593eb61882..b89fc81190d43 100644 --- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs +++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs @@ -1714,7 +1714,16 @@ fn check_fn_or_method<'tcx>( let span = tcx.def_span(def_id); let has_implicit_self = hir_decl.implicit_self().has_implicit_self(); let mut inputs = sig.inputs().iter().skip(if has_implicit_self { 1 } else { 0 }); - // FIXME(splat): use `sig.splatted()` once FnSig has it + // FIXME(splat): support the rest of closure splatting, or replace this code with an error + if let Some(mut splatted_arg_index) = sig.splatted() { + let mut inputs_count = sig.inputs().len(); + if has_implicit_self { + splatted_arg_index = splatted_arg_index.strict_sub(1); + inputs_count = inputs_count.strict_sub(1); + } + debug!(?splatted_arg_index, ?inputs_count, ?has_implicit_self, ?sig); + sig = sig.set_splatted(Some(splatted_arg_index), inputs_count).unwrap(); + } // Check that the argument is a tuple and is sized if let Some(ty) = inputs.next() { wfcx.register_bound( 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 baa9fddc2a651..95a91f1444404 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs @@ -3582,11 +3582,12 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { debug!(?output_ty); debug!(?abi, ?safety, ?decl.fn_decl_kind, input_tys_len = ?input_tys.len()); - // FIXME(splat): use `set_splatted()` once FnSig has it let fn_sig_kind = FnSigKind::default() .set_abi(abi) .set_safety(safety) - .set_c_variadic(decl.fn_decl_kind.c_variadic()); + .set_c_variadic(decl.fn_decl_kind.c_variadic()) + .set_splatted(decl.splatted(), input_tys.len()) + .unwrap(); let fn_ty = tcx.mk_fn_sig(input_tys, output_ty, fn_sig_kind); let fn_ptr_ty = ty::Binder::bind_with_vars(fn_ty, bound_vars); diff --git a/compiler/rustc_hir_typeck/src/closure.rs b/compiler/rustc_hir_typeck/src/closure.rs index 41aebea1c7ebe..95e67b135e411 100644 --- a/compiler/rustc_hir_typeck/src/closure.rs +++ b/compiler/rustc_hir_typeck/src/closure.rs @@ -720,6 +720,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // in this binder we are creating. assert!(!expected_sig.sig.skip_binder().has_vars_bound_above(ty::INNERMOST)); let bound_sig = expected_sig.sig.map_bound(|sig| { + // Ignore splatting, it is unsupported on closures. let fn_sig_kind = FnSigKind::default() .set_abi(ExternAbi::RustCall) .set_safety(hir::Safety::Safe) diff --git a/compiler/rustc_hir_typeck/src/diagnostics.rs b/compiler/rustc_hir_typeck/src/diagnostics.rs index a9a819935287c..a5c39bd1584a1 100644 --- a/compiler/rustc_hir_typeck/src/diagnostics.rs +++ b/compiler/rustc_hir_typeck/src/diagnostics.rs @@ -100,6 +100,7 @@ impl IntoDiagArg for ReturnLikeStatementKind { } } +// FIXME(splat): add "non-splatted" to all 4 instances of this error message #[derive(Diagnostic)] #[diag("functions with the \"rust-call\" ABI must take a single non-self tuple argument")] pub(crate) struct RustCallIncorrectArgs { diff --git a/compiler/rustc_lint/src/foreign_modules.rs b/compiler/rustc_lint/src/foreign_modules.rs index 27a21381ffce9..3010eadb61057 100644 --- a/compiler/rustc_lint/src/foreign_modules.rs +++ b/compiler/rustc_lint/src/foreign_modules.rs @@ -329,6 +329,14 @@ fn structurally_same_type_impl<'tcx>( let a_sig = tcx.instantiate_bound_regions_with_erased(a_poly_sig); let b_sig = tcx.instantiate_bound_regions_with_erased(b_poly_sig); + // FIXME(splat): Is splatting ever repr(C)? + // Can two splatted functions to have the same structure? + // Can a splatted and non-splatted function have the same structure? + // For now, we require splatting to match exactly. + if a_sig.splatted() != b_sig.splatted() { + return false; + } + (a_sig.abi(), a_sig.safety(), a_sig.c_variadic()) == (b_sig.abi(), b_sig.safety(), b_sig.c_variadic()) && a_sig.inputs().iter().eq_by(b_sig.inputs().iter(), |a, b| { diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index 5e87dd7667c14..569a1d5786095 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -2085,6 +2085,8 @@ impl<'tcx> TyCtxt<'tcx> { ty::Tuple(params) => *params, _ => bug!(), }; + // Ignore splatting, it is unsupported on closures. + assert!(s.splatted().is_none()); self.mk_fn_sig( params, s.output(), diff --git a/compiler/rustc_middle/src/ty/error.rs b/compiler/rustc_middle/src/ty/error.rs index 23b5899b0cc39..208e94270a45e 100644 --- a/compiler/rustc_middle/src/ty/error.rs +++ b/compiler/rustc_middle/src/ty/error.rs @@ -96,6 +96,20 @@ impl<'tcx> TypeError<'tcx> { if values.found { "variadic" } else { "non-variadic" } ) .into(), + TypeError::SplatMismatch(ref values) => format!( + "expected fn with {}, found fn with {}", + if let Some(index) = values.expected { + format!("arg {index} splatted") + } else { + "no splatted arg".to_string() + }, + if let Some(index) = values.found { + format!("arg {index} splatted") + } else { + "no splatted arg".to_string() + } + ) + .into(), TypeError::ProjectionMismatched(ref values) => format!( "expected `{}`, found `{}`", tcx.alias_term_kind_def_path_str(values.expected), diff --git a/compiler/rustc_public/src/unstable/convert/internal.rs b/compiler/rustc_public/src/unstable/convert/internal.rs index 81b32f4da10f6..181498443073d 100644 --- a/compiler/rustc_public/src/unstable/convert/internal.rs +++ b/compiler/rustc_public/src/unstable/convert/internal.rs @@ -312,6 +312,7 @@ impl RustcInternal for FnSig { tables: &mut Tables<'_, BridgeTys>, tcx: impl InternalCx<'tcx>, ) -> Self::T<'tcx> { + // FIXME(splat): When `#[splat]` is complete (or stable), add splatted to the public FnSig let fn_sig_kind = rustc_ty::FnSigKind::default() .set_abi(self.abi.internal(tables, tcx)) .set_safety(self.safety.internal(tables, tcx)) diff --git a/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs b/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs index 3aaf17095a6ce..9822e8cdef8b2 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs @@ -823,9 +823,19 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { // ^^^^^ let len1 = sig1.inputs().len(); let len2 = sig2.inputs().len(); + let splatted_arg_index1 = sig1.splatted().map(usize::from); + let splatted_arg_index2 = sig2.splatted().map(usize::from); if len1 == len2 { for (i, (l, r)) in iter::zip(sig1.inputs(), sig2.inputs()).enumerate() { self.push_comma(&mut values.0, &mut values.1, i); + if Some(i) == splatted_arg_index1 { + values.0.push("#[splat]", splatted_arg_index1 != splatted_arg_index2); + values.0.push_normal(" "); + } + if Some(i) == splatted_arg_index2 { + values.1.push("#[splat]", splatted_arg_index1 != splatted_arg_index2); + values.1.push_normal(" "); + } let (x1, x2) = self.cmp(*l, *r); (values.0).0.extend(x1.0); (values.1).0.extend(x2.0); 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 d7dfce166742b..4e3a31f0e84b5 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -4962,6 +4962,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { && let fn_sig @ ty::FnSig { .. } = fn_ty.fn_sig(tcx).skip_binder() + // FIXME(splat): this might need to change if the Fn* traits start using/supporting splat && fn_sig.abi() == ExternAbi::Rust && !fn_sig.c_variadic() && fn_sig.safety() == hir::Safety::Safe diff --git a/compiler/rustc_type_ir/src/error.rs b/compiler/rustc_type_ir/src/error.rs index c08cec21e5b1f..74abfad6e5abc 100644 --- a/compiler/rustc_type_ir/src/error.rs +++ b/compiler/rustc_type_ir/src/error.rs @@ -41,6 +41,7 @@ pub enum TypeError { ArgumentSorts(ExpectedFound, usize), Traits(ExpectedFound), VariadicMismatch(ExpectedFound), + SplatMismatch(ExpectedFound>), /// Instantiating a type variable with the given type would have /// created a cycle (because it appears somewhere within that @@ -76,7 +77,7 @@ impl TypeError { match self { CyclicTy(_) | CyclicConst(_) | SafetyMismatch(_) | PolarityMismatch(_) | Mismatch | AbiMismatch(_) | ArraySize(_) | ArgumentSorts(..) | Sorts(_) - | VariadicMismatch(_) | TargetFeatureCast(_) => false, + | VariadicMismatch(_) | SplatMismatch(_) | TargetFeatureCast(_) => false, Mutability | ArgumentMutability(_) diff --git a/compiler/rustc_type_ir/src/relate.rs b/compiler/rustc_type_ir/src/relate.rs index d64cc2450eb19..a683252f03716 100644 --- a/compiler/rustc_type_ir/src/relate.rs +++ b/compiler/rustc_type_ir/src/relate.rs @@ -169,6 +169,10 @@ impl Relate for ty::FnSig { return Err(TypeError::AbiMismatch(ExpectedFound::new(a.abi(), b.abi()))); }; + if a.splatted() != b.splatted() { + return Err(TypeError::SplatMismatch(ExpectedFound::new(a.splatted(), b.splatted()))); + } + let a_inputs = a.inputs(); let b_inputs = b.inputs(); if a_inputs.len() != b_inputs.len() { diff --git a/compiler/rustc_type_ir/src/ty_kind.rs b/compiler/rustc_type_ir/src/ty_kind.rs index 89fac9d200214..31824b61cfb27 100644 --- a/compiler/rustc_type_ir/src/ty_kind.rs +++ b/compiler/rustc_type_ir/src/ty_kind.rs @@ -888,8 +888,19 @@ pub struct TypeAndMut { impl Eq for TypeAndMut {} +/// Error type for splatted argument index errors. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SplattedArgIndexError { + /// The splatted argument index is invalid. + /// A `u8::MAX` argument index used to indicate that no argument is splatted. + /// Higher values are also not supported, for performance reasons. + InvalidIndex { splatted_arg_index: u8 }, + + /// The splatted argument index is outside the bounds of the function arguments. + OutOfBounds { splatted_arg_index: u8, args_len: u16 }, +} + /// Contains the packed non-type fields of a function signature. -// FIXME(splat): add the splatted argument index as a u16 #[derive_where(Copy, Clone, PartialEq, Eq, Hash; I: Interner)] #[derive(TypeVisitable_Generic, TypeFoldable_Generic, Lift_Generic)] #[cfg_attr( @@ -903,6 +914,15 @@ pub struct FnSigKind { #[type_visitable(ignore)] #[type_foldable(identity)] flags: u8, + + /// Which function argument is splatted into multiple arguments in callers, if any? + /// Splatting functions with `>= u8::MAX` arguments is not supported, for performance reasons. + /// (And spending an extra byte on an edge case is not worth the perf.) + #[lift(identity)] + #[type_visitable(ignore)] + #[type_foldable(identity)] + splatted: u8, + #[type_visitable(ignore)] #[type_foldable(identity)] _marker: PhantomData I>, @@ -922,12 +942,28 @@ impl fmt::Debug for FnSigKind { if self.c_variadic() { f.field(&"CVariadic"); - }; + } + + if let Some(index) = self.splatted() { + f.field(&format!("Splatted({})", index)); + } f.finish() } } +impl Default for FnSigKind { + /// Create a new FnSigKind with the "Rust" ABI, "Unsafe" safety, and no C-style variadic or splatted arguments. + /// To modify these flags, use the `set_*` methods, for readability. + fn default() -> Self { + Self { flags: 0, splatted: 0, _marker: PhantomData } + .set_abi(ExternAbi::Rust) + .set_safety(I::Safety::unsafe_mode()) + .set_c_variadic(false) + .set_no_splatted_args() + } +} + impl FnSigKind { /// Mask for the `ExternAbi` variant, including the unwind flag. const EXTERN_ABI_MASK: u8 = 0b111111; @@ -938,19 +974,34 @@ impl FnSigKind { /// Bitflag for a trailing C-style variadic argument. const C_VARIADIC_FLAG: u8 = 1 << 7; - /// Create a new FnSigKind with the "Rust" ABI, "Unsafe" safety, and no C-style variadic argument. - /// To modify these flags, use the `set_*` methods, for readability. - // FIXME: use Default instead when that trait is const stable. - pub fn default() -> Self { - Self { flags: 0, _marker: PhantomData } - .set_abi(ExternAbi::Rust) - .set_safety(I::Safety::unsafe_mode()) - .set_c_variadic(false) - } + /// The marker index for "no splatted arguments". Higher values are also not supported, for + /// performance reasons. + /// + /// Must have the same value as `FnDeclFlags::NO_SPLATTED_ARG_INDEX` and + /// `rustc_ast::FnDecl::NO_SPLATTED_ARG_INDEX`. + /// + /// This is an implementation detail, which should only be used in low-level encoding. + pub const NO_SPLATTED_ARG_INDEX: u8 = u8::MAX; - /// Create a new FnSigKind with the given ABI, safety, and C-style variadic flag. - pub fn new(abi: ExternAbi, safety: I::Safety, c_variadic: bool) -> Self { - Self::default().set_abi(abi).set_safety(safety).set_c_variadic(c_variadic) + /// Create a new FnSigKind with the given ABI, safety, C-style variadic, and splatted argument + /// index. + pub fn new( + abi: ExternAbi, + safety: I::Safety, + c_variadic: bool, + splatted: Option, + args_len: usize, + ) -> Result { + Self::default() + .set_abi(abi) + .set_safety(safety) + .set_c_variadic(c_variadic) + .set_splatted(splatted, args_len) + } + + /// Create a new safe FnSigKind with the `extern "Rust"` ABI, that isn't C-style variadic or splatted. + pub fn dummy() -> Self { + Self::default().set_safety(I::Safety::safe()) } /// Set the ABI, including the unwind flag. @@ -989,6 +1040,41 @@ impl FnSigKind { self } + /// Set the splatted argument index. + /// The number of function arguments is used for error checking. + #[must_use = "this method does not modify the receiver"] + pub fn set_splatted( + mut self, + splatted: Option, + args_len: usize, + ) -> Result { + if let Some(splatted_arg_index) = splatted { + if splatted_arg_index == Self::NO_SPLATTED_ARG_INDEX { + // This index value is used as a marker for "no splatting", so it is unsupported. + // Higher values are also not supported, for performance reasons. + return Err(SplattedArgIndexError::InvalidIndex { splatted_arg_index }); + } else if usize::from(splatted_arg_index) >= args_len { + return Err(SplattedArgIndexError::OutOfBounds { + splatted_arg_index, + args_len: args_len as u16, + }); + } + + self.splatted = splatted_arg_index; + } else { + self.splatted = Self::NO_SPLATTED_ARG_INDEX; + } + + Ok(self) + } + + /// Set the splatted argument index to "no splatted arguments". + #[must_use = "this method does not modify the receiver"] + pub fn set_no_splatted_args(mut self) -> Self { + self.splatted = Self::NO_SPLATTED_ARG_INDEX; + self + } + /// Get the ABI, including the unwind flag. pub fn abi(self) -> ExternAbi { let abi_index = self.flags & Self::EXTERN_ABI_MASK; @@ -1009,6 +1095,11 @@ impl FnSigKind { pub fn c_variadic(self) -> bool { self.flags & Self::C_VARIADIC_FLAG != 0 } + + /// Get the index of the splatted argument, if any. + pub fn splatted(self) -> Option { + if self.splatted == Self::NO_SPLATTED_ARG_INDEX { None } else { Some(self.splatted) } + } } #[derive_where(Clone, Copy, PartialEq, Hash; I: Interner)] @@ -1039,8 +1130,21 @@ impl FnSig { !self.c_variadic() && self.safety().is_safe() && self.abi() == ExternAbi::Rust } + /// Set the safety flag. + #[must_use = "this method does not modify the receiver"] pub fn set_safety(self, safety: I::Safety) -> Self { - Self { fn_sig_kind: FnSigKind::new(self.abi(), safety, self.c_variadic()), ..self } + Self { fn_sig_kind: self.fn_sig_kind.set_safety(safety), ..self } + } + + /// Set the splatted argument index. + /// The number of function arguments is used for error checking. + #[must_use = "this method does not modify the receiver"] + pub fn set_splatted( + self, + splatted: Option, + args_len: usize, + ) -> Result { + Ok(Self { fn_sig_kind: self.fn_sig_kind.set_splatted(splatted, args_len)?, ..self }) } pub fn safety(self) -> I::Safety { @@ -1055,11 +1159,14 @@ impl FnSig { self.fn_sig_kind.c_variadic() } + pub fn splatted(self) -> Option { + self.fn_sig_kind.splatted() + } + + /// Create a new safe FnSig with no arguments or return type, using the `extern "Rust"` ABI, + /// that isn't C-style variadic or splatted. pub fn dummy() -> Self { - Self { - inputs_and_output: Default::default(), - fn_sig_kind: FnSigKind::new(ExternAbi::Rust, I::Safety::safe(), false), - } + Self { inputs_and_output: Default::default(), fn_sig_kind: FnSigKind::dummy() } } } @@ -1092,6 +1199,10 @@ impl ty::Binder> { self.skip_binder().c_variadic() } + pub fn splatted(self) -> Option { + self.skip_binder().splatted() + } + pub fn safety(self) -> I::Safety { self.skip_binder().safety() } @@ -1127,6 +1238,9 @@ impl fmt::Debug for FnSig { if i > 0 { write!(f, ", ")?; } + if Some(i) == fn_sig_kind.splatted().map(usize::from) { + write!(f, "#[splat] ")?; + } write!(f, "{ty:?}")?; } if fn_sig_kind.c_variadic() { @@ -1288,8 +1402,9 @@ impl FnHeader { self.fn_sig_kind.abi() } + /// Create a new safe FnHeader with the `extern "Rust"` ABI, that isn't C-style variadic or splatted. pub fn dummy() -> Self { - Self { fn_sig_kind: FnSigKind::new(ExternAbi::Rust, I::Safety::safe(), false) } + Self { fn_sig_kind: FnSigKind::dummy() } } } diff --git a/compiler/rustc_type_ir/src/ty_kind/closure.rs b/compiler/rustc_type_ir/src/ty_kind/closure.rs index b688a001f73fa..9453d30ce91c9 100644 --- a/compiler/rustc_type_ir/src/ty_kind/closure.rs +++ b/compiler/rustc_type_ir/src/ty_kind/closure.rs @@ -364,7 +364,7 @@ pub struct CoroutineClosureSignature { // Like the `fn_sig_as_fn_ptr_ty` of a regular closure, these types // never actually differ. But we save them rather than recreating them // from scratch just for good measure. - /// Always safe, RustCall, non-c-variadic + /// Always safe, RustCall, non-c-variadic, non-splatted #[type_visitable(ignore)] #[type_foldable(identity)] pub fn_sig_kind: FnSigKind, diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index 730c7d9fac611..58fe7dc541fc3 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -406,6 +406,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let sig = this.tcx.mk_fn_sig( args.iter().map(|a| a.layout.ty), dest.layout.ty, + // FIXME(splat): Do we need to set splatted here? + // (Currently this also ignores c_variadic) FnSigKind::default().set_abi(caller_abi).set_safety(rustc_hir::Safety::Safe), ); let caller_fn_abi = this.fn_abi_of_fn_ptr(ty::Binder::dummy(sig), ty::List::empty())?; diff --git a/src/tools/miri/src/shims/sig.rs b/src/tools/miri/src/shims/sig.rs index 68b13a6ed58a0..99673319240fe 100644 --- a/src/tools/miri/src/shims/sig.rs +++ b/src/tools/miri/src/shims/sig.rs @@ -274,7 +274,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { inputs_and_output.push(shim_sig.ret); let fn_sig_binder = Binder::dummy(FnSig { inputs_and_output: this.machine.tcx.mk_type_list(&inputs_and_output), - // Safety does not matter for the ABI. + // Safety and splatted do not matter for the ABI. fn_sig_kind: FnSigKind::default() .set_abi(shim_sig.abi) .set_safety(rustc_hir::Safety::Safe), diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/callee.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/callee.rs index 057ba7fa868fe..a661731e60f46 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/callee.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/callee.rs @@ -173,6 +173,8 @@ impl<'db> InferenceContext<'_, 'db> { // impl forces the closure kind to `FnOnce` i.e. `u8`. let kind_ty = autoderef.ctx().table.next_ty_var(call_expr.into()); let interner = autoderef.ctx().interner(); + + // Ignore splatting, it is unsupported on closures. let call_sig = interner.mk_fn_sig( [coroutine_closure_sig.tupled_inputs_ty], coroutine_closure_sig.to_coroutine( diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs index cc48ba06dbfc5..1fddfc09c666f 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs @@ -464,6 +464,7 @@ pub fn callable_sig_from_fn_trait<'db>( args.tuple_fields(), ret, false, + // FIXME(splat): handle splatted arguments Safety::Safe, ExternAbi::Rust, )); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs index fae63ddc2deae..d63ac7f7a0ed4 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs @@ -637,6 +637,7 @@ impl<'db, 'a> TyLoweringContext<'db, 'a> { fn_.abi, if fn_.is_unsafe { Safety::Unsafe } else { Safety::Safe }, fn_.is_varargs, + // FIXME(splat): handle splatted arguments ), inputs_and_output: Tys::new_from_slice(&args), }), diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/interner.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/interner.rs index ef626fc0c8869..3c2976eccd980 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/interner.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/interner.rs @@ -2238,6 +2238,7 @@ impl<'db> DbInterner<'db> { self.replace_escaping_bound_vars_uncached(value.skip_binder(), delegate) } + // FIXME: add splat support when the experiment is complete pub fn mk_fn_sig( self, inputs: I, diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/ty.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/ty.rs index fe31d44207dff..d57d824e325fe 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/ty.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/ty.rs @@ -1514,6 +1514,7 @@ impl<'db> DbInterner<'db> { TyKind::Tuple(params) => params, _ => panic!(), }; + // Ignore splatting, it is unsupported on closures. self.mk_fn_sig(params, s.output(), s.c_variadic(), safety, ExternAbi::Rust) }) } diff --git a/tests/ui/symbol-names/basic.legacy.stderr b/tests/ui/symbol-names/basic.legacy.stderr index f88fdf7509c76..59085aeb3279b 100644 --- a/tests/ui/symbol-names/basic.legacy.stderr +++ b/tests/ui/symbol-names/basic.legacy.stderr @@ -1,10 +1,10 @@ -error: symbol-name(_ZN5basic4main17h27f62b2d0b2beac6E) +error: symbol-name(_ZN5basic4main17h33f35fba43592008E) --> $DIR/basic.rs:8:1 | LL | #[rustc_dump_symbol_name] | ^^^^^^^^^^^^^^^^^^^^^^^^^ -error: demangling(basic::main::h27f62b2d0b2beac6) +error: demangling(basic::main::h33f35fba43592008) --> $DIR/basic.rs:8:1 | LL | #[rustc_dump_symbol_name] diff --git a/tests/ui/symbol-names/issue-60925.legacy.stderr b/tests/ui/symbol-names/issue-60925.legacy.stderr index c1fc9f4b1cea1..367c2d14011ac 100644 --- a/tests/ui/symbol-names/issue-60925.legacy.stderr +++ b/tests/ui/symbol-names/issue-60925.legacy.stderr @@ -1,10 +1,10 @@ -error: symbol-name(_ZN11issue_609253foo37Foo$LT$issue_60925..llv$u6d$..Foo$GT$3foo17h1eb769490ff06e77E) +error: symbol-name(_ZN11issue_609253foo37Foo$LT$issue_60925..llv$u6d$..Foo$GT$3foo17hc741419c44ba4d79E) --> $DIR/issue-60925.rs:21:9 | LL | #[rustc_dump_symbol_name] | ^^^^^^^^^^^^^^^^^^^^^^^^^ -error: demangling(issue_60925::foo::Foo::foo::h1eb769490ff06e77) +error: demangling(issue_60925::foo::Foo::foo::hc741419c44ba4d79) --> $DIR/issue-60925.rs:21:9 | LL | #[rustc_dump_symbol_name] From e0a35a088f667e9e7257ede4dbe4f1acac8c3629 Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 10 Jun 2026 12:05:50 +0200 Subject: [PATCH 21/35] Impl HIR typeck for #[splat] --- compiler/rustc_hir_typeck/src/callee.rs | 45 +- compiler/rustc_hir_typeck/src/demand.rs | 17 +- compiler/rustc_hir_typeck/src/expr.rs | 14 +- .../rustc_hir_typeck/src/fn_ctxt/_impl.rs | 34 +- .../rustc_hir_typeck/src/fn_ctxt/checks.rs | 416 +++++++++++++++--- compiler/rustc_hir_typeck/src/lib.rs | 77 +++- compiler/rustc_hir_typeck/src/writeback.rs | 2 + tests/ui/splat/splat-cannot-resolve.rs | 49 +++ tests/ui/splat/splat-cannot-resolve.stderr | 94 ++++ tests/ui/splat/splat-invalid.rs | 14 + tests/ui/splat/splat-invalid.stderr | 31 +- tests/ui/splat/splat-maybe-tuple.rs | 22 + tests/ui/splat/splat-maybe-tuple.stderr | 12 + tests/ui/splat/splat-non-tuple.rs | 90 ++++ tests/ui/splat/splat-non-tuple.stderr | 54 +++ 15 files changed, 881 insertions(+), 90 deletions(-) create mode 100644 tests/ui/splat/splat-cannot-resolve.rs create mode 100644 tests/ui/splat/splat-cannot-resolve.stderr create mode 100644 tests/ui/splat/splat-maybe-tuple.rs create mode 100644 tests/ui/splat/splat-maybe-tuple.stderr create mode 100644 tests/ui/splat/splat-non-tuple.rs create mode 100644 tests/ui/splat/splat-non-tuple.stderr diff --git a/compiler/rustc_hir_typeck/src/callee.rs b/compiler/rustc_hir_typeck/src/callee.rs index 57ab29ac752ad..859e3463cb79f 100644 --- a/compiler/rustc_hir_typeck/src/callee.rs +++ b/compiler/rustc_hir_typeck/src/callee.rs @@ -58,9 +58,13 @@ pub(crate) fn check_legal_trait_for_method_call( tcx.ensure_result().coherent_trait(trait_id) } +/// State machine for typechecking a call, based on the callee type. #[derive(Debug)] enum CallStep<'tcx> { + /// Typecheck a call to a function definition or pointer. + /// Includes functions with splatted arguments. Builtin(Ty<'tcx>), + /// Deferred closure Fn* trait typechecking, when the callee is a closure. DeferredClosure(LocalDefId, ty::FnSig<'tcx>), /// Call overloading when callee implements one of the Fn* traits. Overloaded(MethodCallee<'tcx>), @@ -544,7 +548,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { arg_exprs: &'tcx [hir::Expr<'tcx>], expected: Expectation<'tcx>, ) -> Ty<'tcx> { - let (fn_sig, def_id) = match *callee_ty.kind() { + let (fn_sig, def_id, callee_generic_args) = match *callee_ty.kind() { ty::FnDef(def_id, args) => { self.enforce_context_effects(Some(call_expr.hir_id), call_expr.span, def_id, args); let fn_sig = self.tcx.fn_sig(def_id).instantiate(self.tcx, args).skip_norm_wip(); @@ -573,11 +577,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .emit(); } } - (fn_sig, Some(def_id)) + (fn_sig, Some(def_id), Some(args)) } // FIXME(const_trait_impl): these arms should error because we can't enforce them - ty::FnPtr(sig_tys, hdr) => (sig_tys.with(hdr), None), + ty::FnPtr(sig_tys, hdr) => (sig_tys.with(hdr), None, None), _ => unreachable!(), }; @@ -595,12 +599,21 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let fn_sig = self.normalize(call_expr.span, Unnormalized::new_wip(fn_sig)); self.check_argument_types_maybe_method_like( - &fn_sig, call_expr, arg_exprs, expected, def_id, + &fn_sig, + call_expr, + arg_exprs, + expected, + TupleArgumentsFlag::with_fn_sig_kind(fn_sig.fn_sig_kind, false), + def_id, + callee_generic_args, ); + // Splatting is currently incompatible with RustCall. if fn_sig.abi() == rustc_abi::ExternAbi::RustCall { let sp = arg_exprs.last().map_or(call_expr.span, |expr| expr.span); - if let Some(ty) = fn_sig.inputs().last().copied() { + if let Some(ty) = fn_sig.inputs().last().copied() + && fn_sig.splatted().is_none() + { self.register_bound( ty, self.tcx.require_lang_item(hir::LangItem::Tuple, sp), @@ -627,7 +640,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { call_expr: &'tcx hir::Expr<'tcx>, arg_exprs: &'tcx [hir::Expr<'tcx>], expected: Expectation<'tcx>, + tuple_arguments_flag: TupleArgumentsFlag, def_id: Option, + callee_generic_args: Option>, ) { let do_check = || { self.check_argument_types( @@ -638,8 +653,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { expected, arg_exprs, fn_sig.c_variadic(), - TupleArgumentsFlag::DontTupleArguments, + tuple_arguments_flag, def_id, + callee_generic_args, ); }; @@ -1009,9 +1025,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fn_sig.output(), expected, arg_exprs, - fn_sig.c_variadic(), - TupleArgumentsFlag::TupleArguments, + fn_sig.fn_sig_kind.c_variadic(), + TupleArgumentsFlag::rust_fn_trait_call(), Some(closure_def_id.to_def_id()), + None, ); fn_sig.output() @@ -1092,6 +1109,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { expected: Expectation<'tcx>, method: MethodCallee<'tcx>, ) -> Ty<'tcx> { + // FIXME(splat): if we ever support splatting here, decrement the splatted index, because + // the receiver argument is removed below. + assert_eq!( + method.sig.fn_sig_kind.splatted(), + None, + "splatting is not supported on RustCall tuples", + ); self.check_argument_types( call_expr.span, call_expr, @@ -1099,9 +1123,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { method.sig.output(), expected, arg_exprs, - method.sig.c_variadic(), - TupleArgumentsFlag::TupleArguments, + method.sig.fn_sig_kind.c_variadic(), + TupleArgumentsFlag::rust_fn_trait_call(), Some(method.def_id), + None, ); self.write_method_call_and_enforce_effects(call_expr.hir_id, call_expr.span, method); diff --git a/compiler/rustc_hir_typeck/src/demand.rs b/compiler/rustc_hir_typeck/src/demand.rs index 6e4d6aa80a63a..bd8295ba3a328 100644 --- a/compiler/rustc_hir_typeck/src/demand.rs +++ b/compiler/rustc_hir_typeck/src/demand.rs @@ -3,11 +3,11 @@ use rustc_hir::def::Res; use rustc_hir::intravisit::Visitor; use rustc_hir::{self as hir, find_attr}; use rustc_infer::infer::DefineOpaqueTypes; -use rustc_middle::bug; use rustc_middle::ty::adjustment::AllowTwoPhase; use rustc_middle::ty::error::{ExpectedFound, TypeError}; use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::{self, AssocItem, BottomUpFolder, Ty, TypeFoldable, TypeVisitableExt}; +use rustc_middle::{bug, span_bug}; use rustc_span::{DUMMY_SP, Ident, Span, sym}; use rustc_trait_selection::infer::InferCtxtExt; use rustc_trait_selection::traits::ObligationCause; @@ -402,9 +402,22 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Unify the method signature with our incompatible arg, to // do inference in the *opposite* direction and to find out // what our ideal rcvr ty would look like. + let Some(input_arg) = method.sig.inputs().get(idx + 1) else { + if method.sig.splatted().is_some() { + // FIXME(splat): when the arg is splatted, adjust its index, to handle the type mismatch properly + return None; + } else { + span_bug!( + self.tcx.def_span(method.def_id), + "arg index {} out of bounds for method with {} inputs", + idx + 1, + method.sig.inputs().len(), + ); + } + }; let _ = self .at(&ObligationCause::dummy(), self.param_env) - .eq(DefineOpaqueTypes::Yes, method.sig.inputs()[idx + 1], arg_ty) + .eq(DefineOpaqueTypes::Yes, *input_arg, arg_ty) .ok()?; self.select_obligations_where_possible(|errs| { // Yeet the errors, we're already reporting errors. diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 0e13f7a4bfe1c..22c211b199474 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -1463,16 +1463,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { Ok(method) => { self.write_method_call_and_enforce_effects(expr.hir_id, expr.span, method); + // Handle splatted method arguments + // self is already handled as `rcvr`, so it's never splatted here + let method_inputs = &method.sig.inputs()[1..]; + let method_tuple_args_flag = + TupleArgumentsFlag::with_fn_sig_kind(method.sig.fn_sig_kind, true); + self.check_argument_types( segment.ident.span, expr, - &method.sig.inputs()[1..], + method_inputs, method.sig.output(), expected, args, - method.sig.c_variadic(), - TupleArgumentsFlag::DontTupleArguments, + method.sig.fn_sig_kind.c_variadic(), + method_tuple_args_flag, Some(method.def_id), + Some(method.args), ); self.check_call_abi(method.sig.abi(), expr.span); @@ -1495,6 +1502,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { false, TupleArgumentsFlag::DontTupleArguments, None, + Some(GenericArgsRef::default()), ); err_output diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs index c8c6de4f99c03..bc954f5518619 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs @@ -143,7 +143,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { /// also select obligations if it seems useful, in an effort /// to get more type information. // FIXME(-Znext-solver): A lot of the calls to this method should - // probably be `try_structurally_resolve_type` or `structurally_resolve_type` instead. + // probably be `resolve_vars_with_obligations` or `structurally_resolve_type` instead. #[instrument(skip(self), level = "debug", ret)] pub(crate) fn resolve_vars_with_obligations>>( &self, @@ -236,6 +236,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.typeck_results.borrow_mut().type_dependent_defs_mut().insert(hir_id, r); } + #[instrument(level = "debug", skip(self))] + pub(crate) fn write_splatted_resolution( + &self, + _hir_id: HirId, + _r: Result<() /* SplattedDef */, ErrorGuaranteed>, + ) { + // FIXME(splat): add side table and write to it here + } + #[instrument(level = "debug", skip(self))] pub(crate) fn write_method_call_and_enforce_effects( &self, @@ -248,6 +257,29 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.write_args(hir_id, method.args); } + #[instrument(level = "debug", skip(self))] + pub(crate) fn write_splatted_call( + &self, + hir_id: HirId, + span: Span, + callee_def_id: Option, + callee_generic_args: Option>, + first_tupled_arg_index: u16, + tupled_args_count: u16, + ) { + // FIXME(const_trait_impl): enforce constness using enforce_context_effects() and add + // _and_enforce_effects to this method's name + + self.write_splatted_resolution( + hir_id, + // FIXME(splat): add side table and write to it here + Ok(()), + ); + if let Some(callee_generic_args) = callee_generic_args { + self.write_args(hir_id, callee_generic_args); + } + } + fn write_args(&self, node_id: HirId, args: GenericArgsRef<'tcx>) { if !args.is_empty() { debug!("write_args({:?}, {:?}) in fcx {}", node_id, args, self.tag()); diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index 890110e431fa2..55c6aa2c3ad25 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -50,6 +50,19 @@ rustc_index::newtype_index! { pub(crate) struct GenericIdx {} } +/// Outcome of checking arguments that are tupled by "rust-call" or `#[splat]`. +#[derive(Debug, Clone, Eq, PartialEq)] +struct TupledArgCheckOutcome<'tcx> { + /// The error code to emit if the arguments are not compatible. + new_err_code: Option, + + /// The formal input types after checking. + untupled_formal_input_tys: Vec>, + + /// The expected input types after checking. + untupled_expected_input_tys: Option>>, +} + impl<'a, 'tcx> FnCtxt<'a, 'tcx> { pub(in super::super) fn check_casts(&mut self) { let mut deferred_cast_checks = self.root_ctxt.deferred_cast_checks.borrow_mut(); @@ -185,13 +198,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { expectation: Expectation<'tcx>, // The expressions for each provided argument provided_args: &'tcx [hir::Expr<'tcx>], - // Whether the function is variadic, for example when imported from C - // FIXME(splat): maybe change this to FnSigKind? + // Whether the function is variadic (e.g. from C) c_variadic: bool, - // Whether the arguments have been bundled in a tuple (ex: closures) + // Whether all the arguments have been bundled in a tuple (ex: closures), or one has been splatted tuple_arguments: TupleArgumentsFlag, // The DefId for the function being called, for better error messages fn_def_id: Option, + // The generics of the function being called. Only used for splatting + callee_generic_args: Option>, ) { let tcx = self.tcx; @@ -220,11 +234,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } // First, let's unify the formal method signature with the expectation eagerly. - // We use this to guide coercion inference; it's output is "fudged" which means + // We use this to guide coercion inference; its output is "fudged" which means // any remaining type variables are assigned to new, unrelated variables. This // is because the inference guidance here is only speculative. + // FIXME(splat): do we need to splat arguments before this type inference? let formal_output = self.resolve_vars_with_obligations(formal_output); - let expected_input_tys: Option> = expectation + let mut expected_input_tys: Option> = expectation .only_has_type(self) .and_then(|expected_output| { // FIXME(#149379): This operation results in expected input @@ -272,45 +287,34 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let mut err_code = E0061; - // If the arguments should be wrapped in a tuple (ex: closures), unwrap them here - let (formal_input_tys, expected_input_tys) = if tuple_arguments == TupleArguments { - let tuple_type = self.structurally_resolve_type(call_span, formal_input_tys[0]); - match tuple_type.kind() { - // We expected a tuple and got a tuple - ty::Tuple(arg_types) => { - // Argument length differs - if arg_types.len() != provided_args.len() { - err_code = E0057; - } - let expected_input_tys = match expected_input_tys { - Some(expected_input_tys) => match expected_input_tys.get(0) { - Some(ty) => match ty.kind() { - ty::Tuple(tys) => Some(tys.iter().collect()), - _ => None, - }, - None => None, - }, - None => None, - }; - (arg_types.iter().collect(), expected_input_tys) - } - _ => { - // Otherwise, there's a mismatch, so clear out what we're expecting, and set - // our input types to err_args so we don't blow up the error messages - let guar = struct_span_code_err!( - self.dcx(), - call_span, - E0059, - "cannot use call notation; the first type parameter \ - for the function trait is neither a tuple nor unit" - ) - .emit(); - (self.err_args(provided_args.len(), guar), None) - } + let mut formal_input_tys = formal_input_tys.to_vec(); + + // If the arguments should be wrapped in a tuple (ex: closures, splats), unwrap them here + if tuple_arguments.is_tupled() { + // Caller arguments are tupled before typechecking, starting at the given index. + // Tupling makes the callee and caller argument counts match. + let outcome = self.check_tupled_arguments( + call_span, + call_expr, + formal_input_tys, + provided_args, + expected_input_tys, + c_variadic, + tuple_arguments, + fn_def_id, + callee_generic_args, + ); + let TupledArgCheckOutcome { + new_err_code, + untupled_formal_input_tys, + untupled_expected_input_tys, + } = outcome; + if let Some(new_err_code) = new_err_code { + err_code = new_err_code; } - } else { - (formal_input_tys.to_vec(), expected_input_tys) - }; + formal_input_tys = untupled_formal_input_tys; + expected_input_tys = untupled_expected_input_tys; + } // If there are no external expectations at the call site, just use the types from the function defn let expected_input_tys = if let Some(expected_input_tys) = expected_input_tys { @@ -556,6 +560,257 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } + /// Check arguments that are tupled by "rust-call" or `#[splat]`. + fn check_tupled_arguments( + &self, + // Span enclosing the call site + call_span: Span, + // Expression of the call site + call_expr: &'tcx hir::Expr<'tcx>, + // Types (as defined in the *signature* of the target function) + mut formal_input_tys: Vec>, + // The expressions for each provided argument + provided_args: &'tcx [hir::Expr<'tcx>], + // The expected input types from the context of the call site + mut expected_input_tys: Option>>, + // Whether the function is variadic (e.g. from C) + c_variadic: bool, + // Whether all the arguments have been bundled in a tuple (ex: closures). + // Splatting is handled separately. + tuple_arguments: TupleArgumentsFlag, + // The DefId for the function being called, for better error messages + fn_def_id: Option, + // The generics of the function being called. Only used for splatting + callee_generic_args: Option>, + ) -> TupledArgCheckOutcome<'tcx> { + let (first_tupled_arg_index, is_self_splatted) = tuple_arguments.tupled_arg_index(); + let Some(first_tupled_arg_index) = first_tupled_arg_index else { + // If we're not tupling any of the current arguments, we're done. + return TupledArgCheckOutcome { + new_err_code: None, + untupled_formal_input_tys: formal_input_tys, + untupled_expected_input_tys: expected_input_tys, + }; + }; + + // The argument difference can range from -1 to u16::MAX - 1, so we count the number + // of tupled arguments instead. + // (An empty argument list becomes a unit tuple in the callee.) + // 0: f() -> f(#[splat] _: ()) + // 1: f(a) -> f(#[splat] _: (A,)) + // 2: f(a, b) -> f(#[splat] _: (A, B)) + // The Fn* traits ensure this by construction, and `#[splat]` can only be applied to + // an actual argument. + let tupled_args_count = (1 + provided_args.len()).checked_sub(formal_input_tys.len()); + debug!( + ?first_tupled_arg_index, ?is_self_splatted, + ?tupled_args_count, ?tuple_arguments, ?c_variadic, + provided_args_len = ?provided_args.len(), formal_input_tys_len = ?formal_input_tys.len() + ); + + // If earlier code has modified the FnSig argument list without adjusting the splatted + // argument, indexing into the formal input types will panic. + if first_tupled_arg_index >= formal_input_tys.len() { + span_bug!( + call_span, + "splatted argument index is out of bounds: {first_tupled_arg_index:?} >= {}, \ + is_self_splatted = {is_self_splatted:?}, \ + tupled_args_count = {tupled_args_count:?}, {tuple_arguments:?}, \ + c_variadic = {c_variadic:?}, provided_args: {}", + formal_input_tys.len(), + provided_args.len(), + ); + } + + // Keep the type variable if the argument is splatted, so we can force it to be a tuple later. + let tuple_type = if tuple_arguments.is_splatted() { + let callee_tuple_type = + self.resolve_vars_with_obligations(formal_input_tys[first_tupled_arg_index]); + if callee_tuple_type.is_ty_var() + && let Some(tupled_args_count) = tupled_args_count + { + // Make the original type variable resolve to a tuple containing new type variables + let ocx = ObligationCtxt::new(self); + let origin = self.misc(call_span); + + let new_tupled_type = Ty::new_tup_from_iter( + self.tcx, + iter::repeat_with(|| self.next_ty_var(call_span)).take(tupled_args_count), + ); + + // FIXME(splat): should this be a sub/super type relationship? + let ocx_error = ocx.eq(&origin, self.param_env, callee_tuple_type, new_tupled_type); + if let Err(ocx_error) = ocx_error { + // FIXME(splat): add a test for this error and the one below, if they are reachable + struct_span_code_err!( + self.dcx(), + call_span, + // FIXME(splat): add a new error code before stabilization (and below as well) + E0277, + "cannot resolve splatted arguments; splatted type parameters \ + must be a tuple or unit type: {:?}", + ocx_error, + ) + .emit(); + } + + let type_errors = ocx.try_evaluate_obligations(); + if type_errors.is_empty() { + new_tupled_type + } else { + let guar = struct_span_code_err!( + self.dcx(), + call_span, + E0277, + "cannot resolve splatted arguments; splatted type parameters \ + must be a tuple or unit type: {:?}", + type_errors, + ) + .emit(); + Ty::new_error(self.tcx, guar) + } + } else { + // Otherwise, just let the argument type checker make a suggestion + callee_tuple_type + } + } else { + self.structurally_resolve_type(call_span, formal_input_tys[first_tupled_arg_index]) + }; + + // We expected a tuple and got a tuple (or made one ourselves). + // If it's not a tuple, we error out in the next block. + let mut err_code = None; + if let ty::Tuple(detup_formal_arg_tys) = tuple_type.kind() { + // Argument length differs + // FIXME(splat): update the error code E0057 docs when splat is stabilized + if Some(detup_formal_arg_tys.len()) != tupled_args_count { + err_code = Some(E0057); + } + if let Some(ref mut expected_input_tys) = expected_input_tys + && let Some(ty) = expected_input_tys.get(first_tupled_arg_index) + && let ty::Tuple(detup_expected_arg_tys) = ty.kind() + { + let substitute_tys = if Some(detup_expected_arg_tys.len()) == tupled_args_count { + detup_expected_arg_tys.iter() + } else { + // Just fall back to the formal argument types + detup_formal_arg_tys.iter() + }; + + expected_input_tys + .splice(first_tupled_arg_index..=first_tupled_arg_index, substitute_tys); + } else { + expected_input_tys = None; + } + // If splatting, record this call in a side-table, so MIR lowering can tuple the caller's arguments + if tuple_arguments.is_splatted() { + // FIXME(const_trait_impl): does not enforce constness yet + self.write_splatted_call( + call_expr.hir_id, + call_span, + fn_def_id, + callee_generic_args, + first_tupled_arg_index.try_into().unwrap(), + tupled_args_count.unwrap().try_into().unwrap(), + ); + } + + formal_input_tys.splice( + first_tupled_arg_index..=first_tupled_arg_index, + detup_formal_arg_tys.iter(), + ); + if let Some(ref expected_input_tys) = expected_input_tys { + assert_eq!( + formal_input_tys.len(), + expected_input_tys.len(), + "incorrectly constructed input type tuples, argument counts must match: \ + tuple_arguments: {tuple_arguments:?}", + ) + } + } + + // Otherwise, there's a mismatch during splatting or a rust-call. + // So clear out what we're expecting, and set our input types to err_args so we don't + // blow up the error messages. + let guar = + if tuple_arguments == TupleAllCallArgs && !matches!(tuple_type.kind(), ty::Tuple(_)) { + let guar = struct_span_code_err!( + self.dcx(), + call_span, + E0059, + "cannot use call notation; the first type parameter \ + for the function trait is neither a tuple nor unit" + ) + .emit(); + + Some(guar) + } else if tuple_arguments.is_splatted() { + // If we don't check argument counts here, and there's a subtle bug in the code above, + // later compilation stages can fail in unrelated places with confusing errors. + if !matches!(tuple_type.kind(), ty::Tuple(_)) { + let spans = if let Some(def_id) = fn_def_id + && let Some(hir_node) = self.tcx.hir_get_if_local(def_id) + && let Some(fn_decl) = hir_node.fn_decl() + && let Some(arg_ty) = fn_decl.inputs.get(first_tupled_arg_index) + { + let arg_def_span = arg_ty.span; + vec![call_span, arg_def_span] + } else { + vec![call_span] + }; + let guar = struct_span_code_err!( + self.dcx(), + spans, + // FIXME(splat): add a new error code before stabilization + E0277, + "cannot use splat attribute; the splatted argument type \ + must be a tuple or unit, not a {:?} ({:?})", + tuple_type.kind(), + self.structurally_resolve_type( + call_span, + formal_input_tys[first_tupled_arg_index] + ) + .kind(), + ) + .emit(); + + Some(guar) + } else if formal_input_tys.len() != provided_args.len() { + // FIXME(splat): suggest alternative argument counts, if there are any + let guar = struct_span_code_err!( + self.dcx(), + call_span, + E0057, + "this splatted function takes {} arguments, but {} {} provided", + formal_input_tys.len(), + provided_args.len(), + if provided_args.len() == 1 { "was" } else { "were" }, + ) + .emit(); + + Some(guar) + } else { + None + } + } else { + None + }; + + if let Some(guar) = guar { + TupledArgCheckOutcome { + new_err_code: err_code, + untupled_formal_input_tys: self.err_args(provided_args.len(), guar), + untupled_expected_input_tys: None, + } + } else { + TupledArgCheckOutcome { + new_err_code: err_code, + untupled_formal_input_tys: formal_input_tys, + untupled_expected_input_tys: expected_input_tys, + } + } + } + /// If `unsized_fn_params` is active, check that unsized values are place expressions. Since /// the removal of `unsized_locals` in we can't /// store them in MIR locals as temporaries. @@ -581,6 +836,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fn_def_id: Option, call_span: Span, call_expr: &'tcx hir::Expr<'tcx>, + // FIXME(splat): when the feature design is settled, improve the errors here tuple_arguments: TupleArgumentsFlag, ) -> ErrorGuaranteed { // Next, let's construct the error @@ -1354,8 +1610,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // If we're calling a method of a Fn/FnMut/FnOnce trait object implicitly // (eg invoking a closure) we want to point at the underlying callable, // not the method implicitly invoked (eg call_once). - // TupleArguments is set only when this is an implicit call (my_closure(...)) rather than explicit (my_closure.call(...)) - if tuple_arguments == TupleArguments + // TupleAllCallArgs is set only when this is an implicit call `my_closure(...)` rather + // than explicit `my_closure.call(...)`. + if tuple_arguments == TupleAllCallArgs && let Some(assoc_item) = self.tcx.opt_associated_item(def_id) // Since this is an associated item, it might point at either an impl or a trait item. // We want it to always point to the trait item. @@ -1442,25 +1699,44 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { deps: SmallVec<[ExpectedIdx; 4]>, } - debug_assert_eq!(params_with_generics.len(), matched_inputs.len()); + // FIXME(splat): fix the generic mismatch earlier, so it doesn't reach here + if !tuple_arguments.is_splatted() { + debug_assert_eq!(params_with_generics.len(), matched_inputs.len()); + } // Gather all mismatched parameters with generics. let mut mismatched_params = Vec::>::new(); + let mut use_splat_fallback = false; if let Some(expected_idx) = expected_idx { let expected_idx = ExpectedIdx::from_usize(expected_idx); - let &(expected_generic, ref expected_param) = - ¶ms_with_generics[expected_idx]; - if let Some(expected_generic) = expected_generic { - mismatched_params.push(MismatchedParam { - idx: expected_idx, - generic: expected_generic, - param: expected_param, - deps: SmallVec::new(), - }); - } else { - // Still mark the mismatched parameter - spans.push_span_label(expected_param.span(), ""); - } - } else { + match params_with_generics.get(expected_idx) { + Some(&(Some(expected_generic), ref expected_param)) => mismatched_params + .push(MismatchedParam { + idx: expected_idx, + generic: expected_generic, + param: expected_param, + deps: SmallVec::new(), + }), + Some((None, expected_param)) => { + // Still mark the mismatched parameter + spans.push_span_label(expected_param.span(), ""); + } + None => { + if tuple_arguments.is_splatted() { + // FIXME(splat): when the arg is splatted, adjust its index, to handle the type mismatch properly + use_splat_fallback = true; + } else { + span_bug!( + self.tcx.def_span(def_id), + "arg index {} out of bounds for method with {} inputs", + expected_idx.as_usize(), + params_with_generics.len(), + ); + } + } + }; + } + + if expected_idx.is_none() || use_splat_fallback { mismatched_params.extend( params_with_generics.iter_enumerated().zip(matched_inputs).filter_map( |((idx, &(generic, ref param)), matched_idx)| { @@ -1671,15 +1947,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { provided_arg_tys: &IndexVec, Span)>, formal_and_expected_inputs: &IndexVec, Ty<'tcx>)>, is_method: bool, + is_splat: bool, ) { let Some(def_id) = callable_def_id else { return; }; if let Some((params_with_generics, _)) = self.get_hir_param_info(def_id, is_method) { - debug_assert_eq!(params_with_generics.len(), matched_inputs.len()); + // FIXME(splat): fix the generic mismatch earlier, so it doesn't reach here + if !is_splat { + debug_assert_eq!(params_with_generics.len(), matched_inputs.len()); + } for (idx, (generic_param, _)) in params_with_generics.iter_enumerated() { - if matched_inputs[idx].is_none() { + if matched_inputs.get(idx).flatten_ref().is_none() { continue; } @@ -1701,7 +1981,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let Some(other_generic_param) = other_generic_param else { return false; }; - if matched_inputs[other_idx].is_some() { + if matched_inputs.get(other_idx).flatten_ref().is_some() { return false; } other_generic_param == generic_param @@ -2004,7 +2284,11 @@ impl<'a, 'tcx> FnCallDiagCtxt<'a, 'tcx> { self.arg_matching_ctxt.args_ctxt.call_metadata.full_call_span, format!( "{call_name} takes {}{} but {} {} supplied", - if self.c_variadic { "at least " } else { "" }, + if self.arg_matching_ctxt.args_ctxt.c_variadic { + "at least " + } else { + "" + }, potentially_plural_count( self.formal_and_expected_inputs.len(), "argument" @@ -2154,6 +2438,7 @@ impl<'a, 'tcx> FnCallDiagCtxt<'a, 'tcx> { &self.provided_arg_tys, &self.formal_and_expected_inputs, self.call_metadata.is_method, + self.tuple_arguments.is_splatted(), ); if let hir::ExprKind::MethodCall(_, rcvr, _, _) = @@ -2237,7 +2522,7 @@ impl<'a, 'tcx> FnCallDiagCtxt<'a, 'tcx> { format!( "this {} takes {}{} but {} {} supplied", self.call_metadata.call_name, - if self.c_variadic { "at least " } else { "" }, + if self.arg_matching_ctxt.args_ctxt.c_variadic { "at least " } else { "" }, potentially_plural_count(self.formal_and_expected_inputs.len(), "argument"), potentially_plural_count(self.provided_args.len(), "argument"), pluralize!("was", self.provided_args.len()) @@ -2614,6 +2899,7 @@ impl<'a, 'tcx> FnCallDiagCtxt<'a, 'tcx> { &self.provided_arg_tys, &self.formal_and_expected_inputs, self.call_metadata.is_method, + self.arg_matching_ctxt.tuple_arguments.is_splatted(), ); } diff --git a/compiler/rustc_hir_typeck/src/lib.rs b/compiler/rustc_hir_typeck/src/lib.rs index 3941f06c3e7d3..3b3f5caa85c09 100644 --- a/compiler/rustc_hir_typeck/src/lib.rs +++ b/compiler/rustc_hir_typeck/src/lib.rs @@ -3,6 +3,7 @@ #![feature(iter_intersperse)] #![feature(iter_order_by)] #![feature(never_type)] +#![feature(option_reference_flattening)] #![feature(trim_prefix_suffix)] // tidy-alphabetical-end @@ -50,7 +51,7 @@ use rustc_hir_analysis::hir_ty_lowering::HirTyLowerer; use rustc_infer::traits::{ObligationCauseCode, ObligationInspector, WellFormedLoc}; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; use rustc_middle::query::Providers; -use rustc_middle::ty::{self, Ty, TyCtxt, Unnormalized}; +use rustc_middle::ty::{self, FnSigKind, Ty, TyCtxt, Unnormalized}; use rustc_middle::{bug, span_bug}; use rustc_session::config; use rustc_span::Span; @@ -589,12 +590,10 @@ fn report_unexpected_variant_res( .emit() } -/// Controls whether the arguments are tupled. This is used for the call -/// operator. +/// Controls whether all arguments are tupled. This is used for the call operator only. /// -/// Tupling means that all call-side arguments are packed into a tuple and -/// passed as a single parameter. For example, if tupling is enabled, this -/// function: +/// Tupling means that all call-side arguments are packed into a tuple and passed as a single +/// parameter. For example, if tupling is enabled, this function: /// ``` /// fn f(x: (isize, isize)) {} /// ``` @@ -608,10 +607,72 @@ fn report_unexpected_variant_res( /// # fn f(x: (isize, isize)) {} /// f((1, 2)); /// ``` -#[derive(Copy, Clone, Eq, PartialEq)] +/// +/// Note: splatted arguments are handled separately. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] enum TupleArgumentsFlag { + /// Arguments are typechecked unchanged. DontTupleArguments, - TupleArguments, + /// This is a call operator: all caller arguments are tupled before typechecking. + /// Set based on the "rust-call" ABI and Fn* traits. + TupleAllCallArgs, + /// The `self` method argument is splatted, so `Self` should be tupled before typechecking. + TupleSplattedSelfArg, + /// A non-self argument is splatted, so that argument should be tupled before typechecking. + TupleSplattedArg(u8), +} + +impl TupleArgumentsFlag { + /// Returns the TupleArgumentsFlag for a known RustCall function. + fn rust_fn_trait_call() -> Self { + Self::TupleAllCallArgs + } + + /// Returns the appropriate TupleArgumentsFlag for the given FnSigKind and method flag. + fn with_fn_sig_kind<'tcx>(fn_sig_kind: FnSigKind<'tcx>, is_method: bool) -> Self { + if let Some(splatted_arg_index) = fn_sig_kind.splatted() { + if is_method { + if let Some(splatted_arg_index) = splatted_arg_index.checked_sub(1) { + return Self::TupleSplattedArg(splatted_arg_index); + } else { + // In `check_argument_types`, this is effectively `TupleSplattedArg(-1)` + return Self::TupleSplattedSelfArg; + } + } + + return Self::TupleSplattedArg(splatted_arg_index); + } + + Self::DontTupleArguments + } + + /// Returns true if the arguments are tupled through "rust-call" or splatting. + fn is_tupled(self) -> bool { + match self { + Self::DontTupleArguments => false, + Self::TupleAllCallArgs | Self::TupleSplattedSelfArg | Self::TupleSplattedArg(_) => true, + } + } + + /// Returns true if the arguments are tupled through splatting. + /// (But false if they are "rust-call" or not tupled.) + fn is_splatted(self) -> bool { + match self { + Self::TupleSplattedSelfArg | Self::TupleSplattedArg(_) => true, + Self::DontTupleArguments | Self::TupleAllCallArgs => false, + } + } + + /// Returns the tupled argument index, and whether the `self` argument is splatted. + /// Returns `None` if the arguments are not tupled, or if the `self` argument is splatted. + fn tupled_arg_index(self) -> (Option, bool /* is_self_splatted */) { + match self { + Self::TupleSplattedArg(index) => (Some(usize::from(index)), false), + Self::TupleAllCallArgs => (Some(0), false), + Self::TupleSplattedSelfArg => (None, true), + Self::DontTupleArguments => (None, false), + } + } } fn fatally_break_rust(tcx: TyCtxt<'_>, span: Span) -> ! { diff --git a/compiler/rustc_hir_typeck/src/writeback.rs b/compiler/rustc_hir_typeck/src/writeback.rs index 298fee525fc55..ad08daa7a4141 100644 --- a/compiler/rustc_hir_typeck/src/writeback.rs +++ b/compiler/rustc_hir_typeck/src/writeback.rs @@ -663,6 +663,8 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> { self.typeck_results.type_dependent_defs_mut().insert(hir_id, def); } + // FIXME(splat): add side table and write to it here + // Resolve any borrowings for the node with id `node_id` self.visit_adjustments(span, hir_id); diff --git a/tests/ui/splat/splat-cannot-resolve.rs b/tests/ui/splat/splat-cannot-resolve.rs new file mode 100644 index 0000000000000..1c22a53f82916 --- /dev/null +++ b/tests/ui/splat/splat-cannot-resolve.rs @@ -0,0 +1,49 @@ +//! Test that using `#[splat]` on un-resolvable types is an error. + +#![allow(incomplete_features)] +#![allow(unconditional_recursion)] +#![feature(splat)] +#![feature(tuple_trait)] + +fn tuple(#[splat] t: impl Sized) -> impl Sized { + //~^ ERROR cannot resolve opaque type + tuple(tuple((t, ()))) +} + +fn tuple_trait(#[splat] t: impl std::marker::Tuple) -> impl std::marker::Tuple { + //~^ ERROR cannot resolve opaque type + tuple_trait(tuple_trait((t, ()))) +} + +trait Trait { + type MaybeTup; + type Tup: std::marker::Tuple; +} + +fn ambig(#[splat] t: Trait::MaybeTup) {} +//~^ ERROR ambiguous associated type +//~| ERROR cannot use splat attribute; the splatted argument type must be a tuple or unit, not a +//~| ERROR cannot use splat attribute; the splatted argument type must be a tuple or unit, not a +//~| ERROR cannot use splat attribute; the splatted argument type must be a tuple or unit, not a +fn ambig_tup(#[splat] t: Trait::Tup) {} +//~^ ERROR ambiguous associated type +//~| ERROR cannot use splat attribute; the splatted argument type must be a tuple or unit, not a +//~| ERROR cannot use splat attribute; the splatted argument type must be a tuple or unit, not a +//~| ERROR cannot use splat attribute; the splatted argument type must be a tuple or unit, not a + +fn main() { + tuple(); + tuple_trait(); + ambig(); + ambig_tup(); + + tuple(1); + tuple_trait(1); + ambig(1); + ambig_tup(1); + + tuple(1, 2.0); + tuple_trait(1, 2.0); + ambig(1, 2.0); + ambig_tup(1, 2.0); +} diff --git a/tests/ui/splat/splat-cannot-resolve.stderr b/tests/ui/splat/splat-cannot-resolve.stderr new file mode 100644 index 0000000000000..f91267d37dbc8 --- /dev/null +++ b/tests/ui/splat/splat-cannot-resolve.stderr @@ -0,0 +1,94 @@ +error[E0223]: ambiguous associated type + --> $DIR/splat-cannot-resolve.rs:23:22 + | +LL | fn ambig(#[splat] t: Trait::MaybeTup) {} + | ^^^^^^^^^^^^^^^ + | +help: if there were a type named `Example` that implemented `Trait`, you could use the fully-qualified path + | +LL - fn ambig(#[splat] t: Trait::MaybeTup) {} +LL + fn ambig(#[splat] t: ::MaybeTup) {} + | + +error[E0223]: ambiguous associated type + --> $DIR/splat-cannot-resolve.rs:28:26 + | +LL | fn ambig_tup(#[splat] t: Trait::Tup) {} + | ^^^^^^^^^^ + | +help: if there were a type named `Example` that implemented `Trait`, you could use the fully-qualified path + | +LL - fn ambig_tup(#[splat] t: Trait::Tup) {} +LL + fn ambig_tup(#[splat] t: ::Tup) {} + | + +error[E0720]: cannot resolve opaque type + --> $DIR/splat-cannot-resolve.rs:8:37 + | +LL | fn tuple(#[splat] t: impl Sized) -> impl Sized { + | ^^^^^^^^^^ + +error[E0720]: cannot resolve opaque type + --> $DIR/splat-cannot-resolve.rs:13:56 + | +LL | fn tuple_trait(#[splat] t: impl std::marker::Tuple) -> impl std::marker::Tuple { + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0277]: cannot use splat attribute; the splatted argument type must be a tuple or unit, not a {type error} ({type error}) + --> $DIR/splat-cannot-resolve.rs:23:22 + | +LL | fn ambig(#[splat] t: Trait::MaybeTup) {} + | ^^^^^^^^^^^^^^^ +... +LL | ambig(); + | ^^^^^^^ + +error[E0277]: cannot use splat attribute; the splatted argument type must be a tuple or unit, not a {type error} ({type error}) + --> $DIR/splat-cannot-resolve.rs:28:26 + | +LL | fn ambig_tup(#[splat] t: Trait::Tup) {} + | ^^^^^^^^^^ +... +LL | ambig_tup(); + | ^^^^^^^^^^^ + +error[E0277]: cannot use splat attribute; the splatted argument type must be a tuple or unit, not a {type error} ({type error}) + --> $DIR/splat-cannot-resolve.rs:23:22 + | +LL | fn ambig(#[splat] t: Trait::MaybeTup) {} + | ^^^^^^^^^^^^^^^ +... +LL | ambig(1); + | ^^^^^^^^ + +error[E0277]: cannot use splat attribute; the splatted argument type must be a tuple or unit, not a {type error} ({type error}) + --> $DIR/splat-cannot-resolve.rs:28:26 + | +LL | fn ambig_tup(#[splat] t: Trait::Tup) {} + | ^^^^^^^^^^ +... +LL | ambig_tup(1); + | ^^^^^^^^^^^^ + +error[E0277]: cannot use splat attribute; the splatted argument type must be a tuple or unit, not a {type error} ({type error}) + --> $DIR/splat-cannot-resolve.rs:23:22 + | +LL | fn ambig(#[splat] t: Trait::MaybeTup) {} + | ^^^^^^^^^^^^^^^ +... +LL | ambig(1, 2.0); + | ^^^^^^^^^^^^^ + +error[E0277]: cannot use splat attribute; the splatted argument type must be a tuple or unit, not a {type error} ({type error}) + --> $DIR/splat-cannot-resolve.rs:28:26 + | +LL | fn ambig_tup(#[splat] t: Trait::Tup) {} + | ^^^^^^^^^^ +... +LL | ambig_tup(1, 2.0); + | ^^^^^^^^^^^^^^^^^ + +error: aborting due to 10 previous errors + +Some errors have detailed explanations: E0223, E0277, E0720. +For more information about an error, try `rustc --explain E0223`. diff --git a/tests/ui/splat/splat-invalid.rs b/tests/ui/splat/splat-invalid.rs index 314003459ef72..2c4bf57ef972a 100644 --- a/tests/ui/splat/splat-invalid.rs +++ b/tests/ui/splat/splat-invalid.rs @@ -30,4 +30,18 @@ extern "C" { fn bar_2(#[splat] _: (u32, i8)); } +trait FooTrait { + fn has_splat(#[splat] _: ()); + + fn no_splat(_: (u32, f64)); +} + +struct Foo; + +impl FooTrait for Foo { + fn has_splat(_: ()) {} //~ ERROR method `has_splat` has an incompatible type for trait + + fn no_splat(#[splat] _: (u32, f64)) {} //~ ERROR method `no_splat` has an incompatible type for trait +} + fn main() {} diff --git a/tests/ui/splat/splat-invalid.stderr b/tests/ui/splat/splat-invalid.stderr index d88cbf4ff0b6d..74c54c00cd285 100644 --- a/tests/ui/splat/splat-invalid.stderr +++ b/tests/ui/splat/splat-invalid.stderr @@ -73,5 +73,34 @@ LL | fn splat_variadic4(..., #[splat] (_a, _b): (u32, i8)) {} | = help: remove `#[splat]` or remove `...` -error: aborting due to 9 previous errors +error[E0053]: method `has_splat` has an incompatible type for trait + --> $DIR/splat-invalid.rs:42:5 + | +LL | fn has_splat(_: ()) {} + | ^^^^^^^^^^^^^^^^^^^ expected fn with arg 0 splatted, found fn with no splatted arg + | +note: type in trait + --> $DIR/splat-invalid.rs:34:5 + | +LL | fn has_splat(#[splat] _: ()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: expected signature `fn(#[splat] ())` + found signature `fn(())` + +error[E0053]: method `no_splat` has an incompatible type for trait + --> $DIR/splat-invalid.rs:44:5 + | +LL | fn no_splat(#[splat] _: (u32, f64)) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected fn with no splatted arg, found fn with arg 0 splatted + | +note: type in trait + --> $DIR/splat-invalid.rs:36:5 + | +LL | fn no_splat(_: (u32, f64)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: expected signature `fn((_, _))` + found signature `fn(#[splat] (_, _))` + +error: aborting due to 11 previous errors +For more information about this error, try `rustc --explain E0053`. diff --git a/tests/ui/splat/splat-maybe-tuple.rs b/tests/ui/splat/splat-maybe-tuple.rs new file mode 100644 index 0000000000000..a74af66e9a874 --- /dev/null +++ b/tests/ui/splat/splat-maybe-tuple.rs @@ -0,0 +1,22 @@ +//! Test that using `#[splat]` on maybe-tuple generic function arguments is an error, +//! but only when the generics aren't tuples. + +#![allow(incomplete_features)] +#![feature(splat)] +#![expect(unused)] + +fn unbound_generic_arg(#[splat] t: T) {} //~ ERROR cannot use splat attribute; the splatted argument type must be a tuple or unit, not a u32 + +fn main() { + unbound_generic_arg(); + unbound_generic_arg::<()>(); + + unbound_generic_arg(1); + unbound_generic_arg::<(u32,)>(1); + + unbound_generic_arg(1, 2.0); + unbound_generic_arg::<(u32, f32)>(1, 2.0); + + // The error comes from this call + unbound_generic_arg::(1); +} diff --git a/tests/ui/splat/splat-maybe-tuple.stderr b/tests/ui/splat/splat-maybe-tuple.stderr new file mode 100644 index 0000000000000..9abacdd657b93 --- /dev/null +++ b/tests/ui/splat/splat-maybe-tuple.stderr @@ -0,0 +1,12 @@ +error[E0277]: cannot use splat attribute; the splatted argument type must be a tuple or unit, not a u32 (u32) + --> $DIR/splat-maybe-tuple.rs:8:39 + | +LL | fn unbound_generic_arg(#[splat] t: T) {} + | ^ +... +LL | unbound_generic_arg::(1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/splat/splat-non-tuple.rs b/tests/ui/splat/splat-non-tuple.rs new file mode 100644 index 0000000000000..35841a770b226 --- /dev/null +++ b/tests/ui/splat/splat-non-tuple.rs @@ -0,0 +1,90 @@ +//! Test that using `#[splat]` on non-tuple function arguments is an error. + +#![allow(incomplete_features)] +#![feature(splat)] +#![expect(unused)] + +fn primitive_arg(#[splat] x: u32) {} //~ ERROR cannot use splat attribute; the splatted argument type must be a tuple or unit, not a u32 + +enum NotATuple { + A(u32), + B(i8), +} + +fn enum_arg(#[splat] y: NotATuple) {} //~ ERROR cannot use splat attribute; the splatted argument type must be a tuple or unit, not a NotATuple + +trait FooTrait { + fn tuple_1(#[splat] _: (u32,)); //~ NOTE type in trait + + // Ambiguous case, self could be a tuple or a non-tuple + fn tuple_4(#[splat] self, _: (u32, i8, (), f32)); +} + +struct Foo; + +fn struct_arg(#[splat] z: Foo) {} //~ ERROR cannot use splat attribute; the splatted argument type must be a tuple or unit, not a Foo + +impl Foo { + fn tuple_2_self( + // FIXME(splat): ERROR cannot use splat attribute; the splatted argument type must be a... + #[splat] self, + (a, b): (u32, i8), + ) -> u32 { + a + } +} + +impl FooTrait for Foo { + fn tuple_1(_: (u32,)) {} + //~^ ERROR method `tuple_1` has an incompatible type for trait + //~| NOTE expected fn with arg 0 splatted, found fn with no splatted arg + //~| NOTE expected signature `fn(#[splat] (_,))` + //~| NOTE found signature `fn((_,))` + + fn tuple_4( + // FIXME(splat): ERROR cannot use splat attribute; the splatted argument type must be a... + #[splat] self, + _: (u32, i8, (), f32), + ) { + } +} + +struct TupleStruct(u32, i8); + +fn tuple_struct_arg(#[splat] z: TupleStruct) {} //~ ERROR cannot use splat attribute; the splatted argument type must be a tuple or unit, not a TupleStruct + +impl TupleStruct { + fn tuple_2( + #[splat] self, // FIXME(splat): ERROR `#[splat]` attribute must be used on a tuple + (a, b): (f32, f64), + ) -> f32 { + a + } +} + +impl FooTrait for TupleStruct { + fn tuple_1(#[splat] _: (u32,)) {} + + fn tuple_4( + #[splat] self, // FIXME(splat): ERROR `#[splat]` attribute must be used on a tuple + _: (u32, i8, (), f32), + ) { + } +} + +fn main() { + // FIXME(splat): is it enough for just the definitions/callees to error, + // or should the callers also error? + primitive_arg(1u32); + enum_arg(NotATuple::A(1u32)); + + let foo = Foo; + struct_arg(foo); + foo.tuple_2_self((1u32, 2i8)); + + let tuple_struct = TupleStruct(1u32, 2i8); + tuple_struct_arg(tuple_struct); + tuple_struct.tuple_2((1f32, 2f64)); + TupleStruct::tuple_1(1u32); + tuple_struct.tuple_4((1u32, 2i8, (), 3f32)); +} diff --git a/tests/ui/splat/splat-non-tuple.stderr b/tests/ui/splat/splat-non-tuple.stderr new file mode 100644 index 0000000000000..64f7148d8dbd2 --- /dev/null +++ b/tests/ui/splat/splat-non-tuple.stderr @@ -0,0 +1,54 @@ +error[E0053]: method `tuple_1` has an incompatible type for trait + --> $DIR/splat-non-tuple.rs:38:5 + | +LL | fn tuple_1(_: (u32,)) {} + | ^^^^^^^^^^^^^^^^^^^^^ expected fn with arg 0 splatted, found fn with no splatted arg + | +note: type in trait + --> $DIR/splat-non-tuple.rs:17:5 + | +LL | fn tuple_1(#[splat] _: (u32,)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: expected signature `fn(#[splat] (_,))` + found signature `fn((_,))` + +error[E0277]: cannot use splat attribute; the splatted argument type must be a tuple or unit, not a u32 (u32) + --> $DIR/splat-non-tuple.rs:7:30 + | +LL | fn primitive_arg(#[splat] x: u32) {} + | ^^^ +... +LL | primitive_arg(1u32); + | ^^^^^^^^^^^^^^^^^^^ + +error[E0277]: cannot use splat attribute; the splatted argument type must be a tuple or unit, not a NotATuple (NotATuple) + --> $DIR/splat-non-tuple.rs:14:25 + | +LL | fn enum_arg(#[splat] y: NotATuple) {} + | ^^^^^^^^^ +... +LL | enum_arg(NotATuple::A(1u32)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0277]: cannot use splat attribute; the splatted argument type must be a tuple or unit, not a Foo (Foo) + --> $DIR/splat-non-tuple.rs:25:27 + | +LL | fn struct_arg(#[splat] z: Foo) {} + | ^^^ +... +LL | struct_arg(foo); + | ^^^^^^^^^^^^^^^ + +error[E0277]: cannot use splat attribute; the splatted argument type must be a tuple or unit, not a TupleStruct (TupleStruct) + --> $DIR/splat-non-tuple.rs:54:33 + | +LL | fn tuple_struct_arg(#[splat] z: TupleStruct) {} + | ^^^^^^^^^^^ +... +LL | tuple_struct_arg(tuple_struct); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 5 previous errors + +Some errors have detailed explanations: E0053, E0277. +For more information about an error, try `rustc --explain E0053`. From 472c197cef731dc9632fc1a56e9b6753d7f2dd9d Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 5 Jun 2026 19:09:38 +0200 Subject: [PATCH 22/35] Remove a leftover direct call to fn_ctxt.label_generic_mismatches --- compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index 55c6aa2c3ad25..9a773cc7808d4 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -2431,15 +2431,7 @@ impl<'a, 'tcx> FnCallDiagCtxt<'a, 'tcx> { format!("arguments to this {call_name} are incorrect"), ); - self.fn_ctxt.label_generic_mismatches( - &mut err, - self.fn_def_id, - &self.matched_inputs, - &self.provided_arg_tys, - &self.formal_and_expected_inputs, - self.call_metadata.is_method, - self.tuple_arguments.is_splatted(), - ); + self.label_generic_mismatches(&mut err); if let hir::ExprKind::MethodCall(_, rcvr, _, _) = self.arg_matching_ctxt.args_ctxt.call_ctxt.call_expr.kind From 50c5d853a0d8594d995d2d537765599fe64b7122 Mon Sep 17 00:00:00 2001 From: Ajay Singh Date: Wed, 3 Jun 2026 15:44:02 +0530 Subject: [PATCH 23/35] Fix ICE in `label_fn_like` when splatted arg index is out of bounds --- tests/ui/splat/splat-overload-at-home-fail.rs | 45 +++++++++++++++++++ .../splat/splat-overload-at-home-fail.stderr | 40 +++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 tests/ui/splat/splat-overload-at-home-fail.rs create mode 100644 tests/ui/splat/splat-overload-at-home-fail.stderr diff --git a/tests/ui/splat/splat-overload-at-home-fail.rs b/tests/ui/splat/splat-overload-at-home-fail.rs new file mode 100644 index 0000000000000..730b46ac0f9ee --- /dev/null +++ b/tests/ui/splat/splat-overload-at-home-fail.rs @@ -0,0 +1,45 @@ +// tests/ui/splat/splat-overload-at-home-fail.rs + +// ignore-tidy-linelength +//! Test error cases for `#[splat]` "overloading at home" example code. +//! Splatted calls that don't match any registered MethodArgs impl should fail. +#![allow(incomplete_features)] +#![feature(splat)] +#![feature(tuple_trait)] + +struct Foo; + +trait MethodArgs: std::marker::Tuple { + fn call_method(self, _this: &Foo); +} + +impl MethodArgs for () { + fn call_method(self, _this: &Foo) {} +} + +impl MethodArgs for (i32,) { + fn call_method(self, _this: &Foo) {} +} + +impl MethodArgs for (i32, String) { + fn call_method(self, _this: &Foo) {} +} + +impl Foo { + fn method(&self, #[splat] args: T) { + args.call_method(self) + } +} + +fn main() { + let foo = Foo; + + // No impl for (f32,) — wrong type + foo.method(42f32); + //~^ ERROR mismatched types + + // No impl for (i32,i32) - wrong type + foo.method(42i32, 42i32); + //~^ ERROR mismatched types + + } diff --git a/tests/ui/splat/splat-overload-at-home-fail.stderr b/tests/ui/splat/splat-overload-at-home-fail.stderr new file mode 100644 index 0000000000000..8a0ba449af213 --- /dev/null +++ b/tests/ui/splat/splat-overload-at-home-fail.stderr @@ -0,0 +1,40 @@ +error[E0308]: mismatched types + --> $DIR/splat-overload-at-home-fail.rs:38:16 + | +LL | foo.method(42f32); + | ------ ^^^^^ expected `i32`, found `f32` + | | + | arguments to this method are incorrect + | +note: method defined here + --> $DIR/splat-overload-at-home-fail.rs:29:8 + | +LL | fn method(&self, #[splat] args: T) { + | ^^^^^^ ---------------- +help: change the type of the numeric literal from `f32` to `i32` + | +LL - foo.method(42f32); +LL + foo.method(42i32); + | + +error[E0308]: mismatched types + --> $DIR/splat-overload-at-home-fail.rs:42:23 + | +LL | foo.method(42i32, 42i32); + | ------ ^^^^^ expected `String`, found `i32` + | | + | arguments to this method are incorrect + | +note: method defined here + --> $DIR/splat-overload-at-home-fail.rs:29:8 + | +LL | fn method(&self, #[splat] args: T) { + | ^^^^^^ +help: try using a conversion method + | +LL | foo.method(42i32, 42i32.to_string()); + | ++++++++++++ + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0308`. From 9e48cd9e1f45447f6560777e460aac71503b1561 Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 12 Jun 2026 14:04:35 +0200 Subject: [PATCH 24/35] Impl MIR lowering side-tables for #[splat] --- .../rustc_codegen_cranelift/src/abi/mod.rs | 1 + .../src/debuginfo/type_names.rs | 1 + compiler/rustc_codegen_ssa/src/mir/block.rs | 1 + compiler/rustc_codegen_ssa/src/mir/mod.rs | 1 + .../rustc_hir_typeck/src/fn_ctxt/_impl.rs | 17 ++++---- compiler/rustc_hir_typeck/src/writeback.rs | 5 ++- compiler/rustc_middle/src/ty/mod.rs | 3 +- compiler/rustc_middle/src/ty/print/pretty.rs | 15 ++++++- .../rustc_middle/src/ty/typeck_results.rs | 35 +++++++++++++++++ compiler/rustc_mir_build/src/builder/mod.rs | 1 + tests/ui/splat/splat-fn-tuple-generic-fail.rs | 27 +++++++++++++ .../splat/splat-fn-tuple-generic-fail.stderr | 39 +++++++++++++++++++ 12 files changed, 135 insertions(+), 11 deletions(-) create mode 100644 tests/ui/splat/splat-fn-tuple-generic-fail.rs create mode 100644 tests/ui/splat/splat-fn-tuple-generic-fail.stderr diff --git a/compiler/rustc_codegen_cranelift/src/abi/mod.rs b/compiler/rustc_codegen_cranelift/src/abi/mod.rs index 8f965a5ef7b12..012951098fa15 100644 --- a/compiler/rustc_codegen_cranelift/src/abi/mod.rs +++ b/compiler/rustc_codegen_cranelift/src/abi/mod.rs @@ -271,6 +271,7 @@ pub(crate) fn codegen_fn_prelude<'tcx>(fx: &mut FunctionCx<'_, '_, 'tcx>, start_ .map(|local| { let arg_ty = fx.monomorphize(fx.mir.local_decls[local].ty); + // FIXME(splat): un-tuple splatted arguments in codegen, for performance // Adapted from https://github.com/rust-lang/rust/blob/145155dc96757002c7b2e9de8489416e2fdbbd57/src/librustc_codegen_llvm/mir/mod.rs#L442-L482 if Some(local) == fx.mir.spread_arg { // This argument (e.g. the last argument in the "rust-call" ABI) diff --git a/compiler/rustc_codegen_ssa/src/debuginfo/type_names.rs b/compiler/rustc_codegen_ssa/src/debuginfo/type_names.rs index 9b8d05c9f72c0..5ece363fcd8d9 100644 --- a/compiler/rustc_codegen_ssa/src/debuginfo/type_names.rs +++ b/compiler/rustc_codegen_ssa/src/debuginfo/type_names.rs @@ -375,6 +375,7 @@ fn push_debuginfo_type_name<'tcx>( output.push_str("fn("); } + // FIXME(splat): should debuginfo be de-tupled in the callee (and caller)? if !sig.inputs().is_empty() { for ¶meter_type in sig.inputs() { push_debuginfo_type_name(tcx, parameter_type, true, output, visited); diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs index 115c50edf4e9f..00cfe1c845d7a 100644 --- a/compiler/rustc_codegen_ssa/src/mir/block.rs +++ b/compiler/rustc_codegen_ssa/src/mir/block.rs @@ -1202,6 +1202,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { }; // Split the rust-call tupled arguments off. + // FIXME(splat): un-tuple splatted arguments in codegen, for performance let (first_args, untuple) = if sig.abi() == ExternAbi::RustCall && let Some((tup, args)) = args.split_last() { diff --git a/compiler/rustc_codegen_ssa/src/mir/mod.rs b/compiler/rustc_codegen_ssa/src/mir/mod.rs index 67c9a85ca408f..68fed6c867fa5 100644 --- a/compiler/rustc_codegen_ssa/src/mir/mod.rs +++ b/compiler/rustc_codegen_ssa/src/mir/mod.rs @@ -433,6 +433,7 @@ fn arg_local_refs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( let arg_decl = &mir.local_decls[local]; let arg_ty = fx.monomorphize(arg_decl.ty); + // FIXME(splat): re-tuple splatted arguments that were un-tupled in the ABI if Some(local) == mir.spread_arg { // This argument (e.g., the last argument in the "rust-call" ABI) // is a tuple that was spread at the ABI level and now we have diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs index bc954f5518619..db2fb43522e14 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs @@ -27,8 +27,8 @@ use rustc_middle::ty::adjustment::{ }; use rustc_middle::ty::{ self, AdtKind, CanonicalUserType, GenericArgsRef, GenericParamDefKind, IsIdentity, - SizedTraitKind, Ty, TyCtxt, TypeFoldable, TypeVisitable, TypeVisitableExt, Unnormalized, - UserArgs, UserSelfTy, + SizedTraitKind, SplattedDef, Ty, TyCtxt, TypeFoldable, TypeVisitable, TypeVisitableExt, + Unnormalized, UserArgs, UserSelfTy, }; use rustc_middle::{bug, span_bug}; use rustc_session::lint; @@ -239,10 +239,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { #[instrument(level = "debug", skip(self))] pub(crate) fn write_splatted_resolution( &self, - _hir_id: HirId, - _r: Result<() /* SplattedDef */, ErrorGuaranteed>, + hir_id: HirId, + r: Result, ) { - // FIXME(splat): add side table and write to it here + self.typeck_results.borrow_mut().splatted_defs_mut().insert(hir_id, r); } #[instrument(level = "debug", skip(self))] @@ -272,8 +272,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.write_splatted_resolution( hir_id, - // FIXME(splat): add side table and write to it here - Ok(()), + Ok(SplattedDef { + def_id: callee_def_id, + arg_index: first_tupled_arg_index, + arg_count: tupled_args_count, + }), ); if let Some(callee_generic_args) = callee_generic_args { self.write_args(hir_id, callee_generic_args); diff --git a/compiler/rustc_hir_typeck/src/writeback.rs b/compiler/rustc_hir_typeck/src/writeback.rs index ad08daa7a4141..3d50befe042f1 100644 --- a/compiler/rustc_hir_typeck/src/writeback.rs +++ b/compiler/rustc_hir_typeck/src/writeback.rs @@ -663,7 +663,10 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> { self.typeck_results.type_dependent_defs_mut().insert(hir_id, def); } - // FIXME(splat): add side table and write to it here + // Export splatted function call resolutions. + if let Some(def) = self.fcx.typeck_results.borrow_mut().splatted_defs_mut().remove(hir_id) { + self.typeck_results.splatted_defs_mut().insert(hir_id, def); + } // Resolve any borrowings for the node with id `node_id` self.visit_adjustments(span, hir_id); diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index 3ea798ee45fb2..f081e98127cf4 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -113,7 +113,8 @@ pub use self::sty::{ pub use self::trait_def::TraitDef; pub use self::typeck_results::{ CanonicalUserType, CanonicalUserTypeAnnotation, CanonicalUserTypeAnnotations, IsIdentity, - Rust2024IncompatiblePatInfo, TypeckResults, UserType, UserTypeAnnotationIndex, UserTypeKind, + Rust2024IncompatiblePatInfo, SplattedDef, TypeckResults, UserType, UserTypeAnnotationIndex, + UserTypeKind, }; use crate::error::{OpaqueHiddenTypeMismatch, TypeMismatchReason}; use crate::metadata::{AmbigModChild, ModChild}; diff --git a/compiler/rustc_middle/src/ty/print/pretty.rs b/compiler/rustc_middle/src/ty/print/pretty.rs index c1f96c3a94fdd..89b43480b3fc3 100644 --- a/compiler/rustc_middle/src/ty/print/pretty.rs +++ b/compiler/rustc_middle/src/ty/print/pretty.rs @@ -1435,6 +1435,8 @@ pub trait PrettyPrinter<'tcx>: Printer<'tcx> + fmt::Write { p.pretty_print_fn_sig( tys, false, + // FIXME(splat): support splatted arguments here? + None, proj.skip_binder().term.as_type().expect("Return type was a const"), )?; resugared = true; @@ -1538,10 +1540,19 @@ pub trait PrettyPrinter<'tcx>: Printer<'tcx> + fmt::Write { &mut self, inputs: &[Ty<'tcx>], c_variadic: bool, + splatted: Option, output: Ty<'tcx>, ) -> Result<(), PrintError> { write!(self, "(")?; - self.comma_sep(inputs.iter().copied())?; + let splatted_arg_index = splatted.map(usize::from); + let mut input_iter = inputs.iter().copied(); + if let Some(index) = splatted_arg_index { + self.comma_sep((&mut input_iter).take(usize::from(index)))?; + write!(self, ", #[splat]")?; + self.comma_sep(input_iter)?; + } else { + self.comma_sep(input_iter)?; + } if c_variadic { if !inputs.is_empty() { write!(self, ", ")?; @@ -3150,7 +3161,7 @@ define_print! { } write!(p, "fn")?; - p.pretty_print_fn_sig(self.inputs(), self.c_variadic(), self.output())?; + p.pretty_print_fn_sig(self.inputs(), self.c_variadic(), self.splatted(), self.output())?; } ty::TraitRef<'tcx> { diff --git a/compiler/rustc_middle/src/ty/typeck_results.rs b/compiler/rustc_middle/src/ty/typeck_results.rs index 1287047581196..77820beef0228 100644 --- a/compiler/rustc_middle/src/ty/typeck_results.rs +++ b/compiler/rustc_middle/src/ty/typeck_results.rs @@ -36,6 +36,9 @@ pub struct TypeckResults<'tcx> { /// method calls, including those of overloaded operators. type_dependent_defs: ItemLocalMap>, + /// Resolved definitions for splatted function calls. + splatted_defs: ItemLocalMap>, + /// Resolved field indices for field accesses in expressions (`S { field }`, `obj.field`) /// or patterns (`S { field }`). The index is often useful by itself, but to learn more /// about the field you also need definition of the variant to which the field @@ -229,6 +232,7 @@ impl<'tcx> TypeckResults<'tcx> { TypeckResults { hir_owner, type_dependent_defs: Default::default(), + splatted_defs: Default::default(), field_indices: Default::default(), user_provided_types: Default::default(), user_provided_sigs: Default::default(), @@ -287,6 +291,21 @@ impl<'tcx> TypeckResults<'tcx> { LocalTableInContextMut { hir_owner: self.hir_owner, data: &mut self.type_dependent_defs } } + pub fn splatted_defs(&self) -> LocalTableInContext<'_, Result> { + LocalTableInContext { hir_owner: self.hir_owner, data: &self.splatted_defs } + } + + pub fn splatted_def(&self, id: HirId) -> Option { + validate_hir_id_for_typeck_results(self.hir_owner, id); + self.splatted_defs.get(&id.local_id).cloned().and_then(|r| r.ok()) + } + + pub fn splatted_defs_mut( + &mut self, + ) -> LocalTableInContextMut<'_, Result> { + LocalTableInContextMut { hir_owner: self.hir_owner, data: &mut self.splatted_defs } + } + pub fn field_indices(&self) -> LocalTableInContext<'_, FieldIdx> { LocalTableInContext { hir_owner: self.hir_owner, data: &self.field_indices } } @@ -407,6 +426,10 @@ impl<'tcx> TypeckResults<'tcx> { matches!(self.type_dependent_defs().get(expr.hir_id), Some(Ok((DefKind::AssocFn, _)))) } + pub fn is_splatted_call(&self, expr: &hir::Expr<'_>) -> bool { + matches!(self.splatted_defs().get(expr.hir_id), Some(Ok(SplattedDef { .. }))) + } + /// Returns the computed binding mode for a `PatKind::Binding` pattern /// (after match ergonomics adjustments). pub fn extract_binding_mode(&self, s: &Session, id: HirId, sp: Span) -> BindingMode { @@ -569,6 +592,18 @@ impl<'tcx> TypeckResults<'tcx> { } } +/// A resolved splatted function call. +#[derive(Debug, Copy, Clone, PartialEq, Eq, StableHash, TyEncodable, TyDecodable)] +pub struct SplattedDef { + /// The function DefId, if available (FnPtrs don't have DefIds) + pub def_id: Option, + /// The index of the first argument in the callee's splatted tuple, and the index of the + /// splatted tuple argument in the caller. + pub arg_index: u16, + /// The number of arguments in the splatted tuple. + pub arg_count: u16, +} + /// Validate that the given HirId (respectively its `local_id` part) can be /// safely used as a key in the maps of a TypeckResults. For that to be /// the case, the HirId must have the same `owner` as all the other IDs in diff --git a/compiler/rustc_mir_build/src/builder/mod.rs b/compiler/rustc_mir_build/src/builder/mod.rs index 854a378bb993d..378e2e618fb93 100644 --- a/compiler/rustc_mir_build/src/builder/mod.rs +++ b/compiler/rustc_mir_build/src/builder/mod.rs @@ -547,6 +547,7 @@ fn construct_fn<'tcx>( body.spread_arg = if abi == ExternAbi::RustCall { // RustCall pseudo-ABI untuples the last argument. + // FIXME(splat): splat can untuple any argument, set spread_arg here Some(Local::new(arguments.len())) } else { None diff --git a/tests/ui/splat/splat-fn-tuple-generic-fail.rs b/tests/ui/splat/splat-fn-tuple-generic-fail.rs new file mode 100644 index 0000000000000..8a8651e75c9ff --- /dev/null +++ b/tests/ui/splat/splat-fn-tuple-generic-fail.rs @@ -0,0 +1,27 @@ +//! Test failing use of `#[splat]` on tuple trait arguments of generic functions. + +#![allow(incomplete_features)] +#![feature(splat)] +#![feature(tuple_trait)] + +fn splat_generic_tuple(#[splat] _t: T) {} + +fn main() { + // FIXME(splat): should splatted functions be callable with tupled and un-tupled arguments? + + // Calling with un-splatted arguments might look like it works, but the actual generic type is + // a tuple inside another tuple. Aren't generics great? + splat_generic_tuple((1, 2)); + splat_generic_tuple((1u32, 2i8)); + + // FIXME(splat): Make the splat generic handling code handle tuples inside tuples + // (if we want to support tupled calls) + splat_generic_tuple::<(((u32, i8)))>((1, 2)); //~ ERROR this splatted function takes 2 arguments, but 1 was provided + splat_generic_tuple::<(((u32, i8)))>((1u32, 2i8)); //~ ERROR this splatted function takes 2 arguments, but 1 was provided + + splat_generic_tuple::<((u32, i8))>((1, 2)); //~ ERROR this splatted function takes 2 arguments, but 1 was provided + splat_generic_tuple::<((u32, i8))>((1u32, 2i8)); //~ ERROR this splatted function takes 2 arguments, but 1 was provided + + splat_generic_tuple::<(u32, i8)>((1, 2)); //~ ERROR this splatted function takes 2 arguments, but 1 was provided + splat_generic_tuple::<(u32, i8)>((1u32, 2i8)); //~ ERROR this splatted function takes 2 arguments, but 1 was provided +} diff --git a/tests/ui/splat/splat-fn-tuple-generic-fail.stderr b/tests/ui/splat/splat-fn-tuple-generic-fail.stderr new file mode 100644 index 0000000000000..7fd4a0719b493 --- /dev/null +++ b/tests/ui/splat/splat-fn-tuple-generic-fail.stderr @@ -0,0 +1,39 @@ +error[E0057]: this splatted function takes 2 arguments, but 1 was provided + --> $DIR/splat-fn-tuple-generic-fail.rs:19:5 + | +LL | splat_generic_tuple::<(((u32, i8)))>((1, 2)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0057]: this splatted function takes 2 arguments, but 1 was provided + --> $DIR/splat-fn-tuple-generic-fail.rs:20:5 + | +LL | splat_generic_tuple::<(((u32, i8)))>((1u32, 2i8)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0057]: this splatted function takes 2 arguments, but 1 was provided + --> $DIR/splat-fn-tuple-generic-fail.rs:22:5 + | +LL | splat_generic_tuple::<((u32, i8))>((1, 2)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0057]: this splatted function takes 2 arguments, but 1 was provided + --> $DIR/splat-fn-tuple-generic-fail.rs:23:5 + | +LL | splat_generic_tuple::<((u32, i8))>((1u32, 2i8)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0057]: this splatted function takes 2 arguments, but 1 was provided + --> $DIR/splat-fn-tuple-generic-fail.rs:25:5 + | +LL | splat_generic_tuple::<(u32, i8)>((1, 2)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0057]: this splatted function takes 2 arguments, but 1 was provided + --> $DIR/splat-fn-tuple-generic-fail.rs:26:5 + | +LL | splat_generic_tuple::<(u32, i8)>((1u32, 2i8)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 6 previous errors + +For more information about this error, try `rustc --explain E0057`. From 2bb9dabcc247dc1c47eb14c655c0958cbb5d3671 Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 12 Jun 2026 14:27:09 +0200 Subject: [PATCH 25/35] Impl actual MIR lowering for #[splat] --- compiler/rustc_mir_build/src/thir/cx/expr.rs | 151 ++++++++++++++++-- tests/ui/splat/splat-assoc-fn-tuple-simple.rs | 22 +++ tests/ui/splat/splat-fn-ptr-tuple.rs | 52 ++++++ tests/ui/splat/splat-fn-ptr-tuple.stderr | 24 +++ tests/ui/splat/splat-fn-tuple-generic.rs | 23 +++ tests/ui/splat/splat-fn-tuple-simple.rs | 32 ++++ tests/ui/splat/splat-generics-everywhere.rs | 68 ++++++++ tests/ui/splat/splat-method-tuple-simple.rs | 40 +++++ tests/ui/splat/splat-overload-at-home.rs | 52 ++++++ tests/ui/splat/splat-trait-tuple.rs | 45 ++++++ 10 files changed, 494 insertions(+), 15 deletions(-) create mode 100644 tests/ui/splat/splat-assoc-fn-tuple-simple.rs create mode 100644 tests/ui/splat/splat-fn-ptr-tuple.rs create mode 100644 tests/ui/splat/splat-fn-ptr-tuple.stderr create mode 100644 tests/ui/splat/splat-fn-tuple-generic.rs create mode 100644 tests/ui/splat/splat-fn-tuple-simple.rs create mode 100644 tests/ui/splat/splat-generics-everywhere.rs create mode 100644 tests/ui/splat/splat-method-tuple-simple.rs create mode 100644 tests/ui/splat/splat-overload-at-home.rs create mode 100644 tests/ui/splat/splat-trait-tuple.rs diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index b71793c157621..c286c0819aea2 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -18,8 +18,8 @@ use rustc_middle::ty::adjustment::{ Adjust, Adjustment, AutoBorrow, AutoBorrowMutability, DerefAdjustKind, PointerCoercion, }; use rustc_middle::ty::{ - self, AdtKind, GenericArgs, InlineConstArgs, InlineConstArgsParts, ScalarInt, Ty, TyCtxt, - UpvarArgs, + self, AdtKind, GenericArgs, InlineConstArgs, InlineConstArgsParts, ScalarInt, SplattedDef, Ty, + TyCtxt, UpvarArgs, }; use rustc_middle::{bug, span_bug}; use rustc_span::{DesugaringKind, Span}; @@ -369,19 +369,26 @@ impl<'tcx> ThirBuildCx<'tcx> { let kind = match expr.kind { // Here comes the interesting stuff: hir::ExprKind::MethodCall(segment, receiver, args, fn_span) => { - // Rewrite a.b(c) into UFCS form like Trait::b(a, c) - let expr = self.method_callee(expr, segment.ident.span, None); - info!("Using method span: {:?}", expr.span); - let args = std::iter::once(receiver) - .chain(args.iter()) - .map(|expr| self.mirror_expr(expr)) - .collect(); - ExprKind::Call { - ty: expr.ty, - fun: self.thir.exprs.push(expr), - args, - from_hir_call: true, - fn_span, + if self.typeck_results.is_splatted_call(expr) { + // The callee has a splatted tuple argument. + // rewrite `receiver.f(a, u, v)` into `receiver.f(a, #[splat] (u, v))` + self.convert_splatted_callee(expr, fn_span, args, Some(receiver)) + } else { + // Rewrite a.b(c) into UFCS form like Trait::b(a, c) + let expr = self.method_callee(expr, segment.ident.span, None); + info!("Using method span: {:?}", expr.span); + + let args = std::iter::once(receiver) + .chain(args.iter()) + .map(|expr| self.mirror_expr(expr)) + .collect(); + ExprKind::Call { + ty: expr.ty, + fun: self.thir.exprs.push(expr), + args, + from_hir_call: true, + fn_span, + } } } @@ -412,6 +419,10 @@ impl<'tcx> ThirBuildCx<'tcx> { from_hir_call: true, fn_span: expr.span, } + } else if self.typeck_results.is_splatted_call(expr) { + // The callee has a splatted tuple argument. + // rewrite `f(a, u, v)` into `f(a, #[splat] (u, v))` + self.convert_splatted_callee(expr, fun.span, args, None) } else { // Tuple-like ADTs are represented as ExprKind::Call. We convert them here. let adt_data = if let hir::ExprKind::Path(ref qpath) = fun.kind @@ -1205,6 +1216,116 @@ impl<'tcx> ThirBuildCx<'tcx> { } } + fn splatted_callee( + &mut self, + expr: &hir::Expr<'_>, + span: Span, + ) -> (Expr<'tcx>, u16 /* arg_index */, u16 /* arg_count */) { + let SplattedDef { def_id, arg_index, arg_count } = + self.typeck_results.splatted_def(expr.hir_id).unwrap_or_else(|| { + span_bug!(expr.span, "no splatted def for function or method callee") + }); + let def_id = def_id.unwrap_or_else(|| { + span_bug!(expr.span, "no splatted def for function or method callee") + }); + let def_kind = self.tcx.def_kind(def_id); + let user_ty = self.user_args_applied_to_res(expr.hir_id, Res::Def(def_kind, def_id)); + debug!( + "splatted_callee: user_ty={:?} def_kind={:?} def_id={:?} arg_index={:?} arg_count={:?}", + user_ty, def_kind, def_id, arg_index, arg_count + ); + + ( + Expr { + temp_scope_id: expr.hir_id.local_id, + ty: Ty::new_fn_def(self.tcx, def_id, self.typeck_results.node_args(expr.hir_id)), + span, + kind: ExprKind::ZstLiteral { user_ty }, + }, + arg_index, + arg_count, + ) + } + + /// The callee has a splatted tuple argument. + /// Rewrite a splatted call `receiver.f(a, u, v)` into `receiver.f(a, #[splat] (u, v))`. + /// The receiver is optional. + fn convert_splatted_callee( + &mut self, + expr: &hir::Expr<'_>, + fn_span: Span, + args: &'tcx [hir::Expr<'tcx>], + receiver: Option<&'tcx hir::Expr<'tcx>>, + ) -> ExprKind<'tcx> { + let tcx = self.tcx; + + // The callee has a splatted tuple argument. + let (func, tupled_arg_index, tupled_args_count) = self.splatted_callee(expr, fn_span); + let tupled_arg_index = usize::from(tupled_arg_index); + let tupled_args_count = usize::from(tupled_args_count); + + // Splatting an empty tuple is permitted: `a.f() -> Trait::f(a, #[splat] ())`. + // In that case, the tupled arg index is one past the end of the args. + if tupled_arg_index + tupled_args_count > args.len() { + span_bug!( + expr.span, + "splatted arg index out of bounds of function args: {:?} + {:?} > {:?} for function call: receiver {:?}, args {:?}", + tupled_arg_index, + tupled_args_count, + args.len(), + receiver, + args, + ); + } + + info!("Using splatted function span: {:?}", func.span); + + // Split into non-tupled and tupled arguments + let initial_non_tupled_args = + args.iter().take(tupled_arg_index).map(|e| self.mirror_expr(e)).collect_vec(); + let tupled_args = if tupled_arg_index == args.len() || tupled_args_count == 0 { + // Splatting an empty tuple, in the ABI this gets ignored + Default::default() + } else { + &args[tupled_arg_index..(tupled_arg_index + tupled_args_count)] + }; + let final_non_tupled_args = args + .iter() + .skip(tupled_arg_index + tupled_args_count) + .map(|e| self.mirror_expr(e)) + .collect_vec(); + + let tupled_arg_tys = tupled_args.iter().map(|e| self.typeck_results.expr_ty_adjusted(e)); + + let temp_scope_id = + if receiver.is_some() { func.temp_scope_id } else { expr.hir_id.local_id }; + let tupled_args = Expr { + ty: Ty::new_tup_from_iter(tcx, tupled_arg_tys), + temp_scope_id, + span: expr.span, + kind: ExprKind::Tuple { fields: self.mirror_exprs(tupled_args) }, + }; + + let tupled_args = self.thir.exprs.push(tupled_args); + + let mut args = + if let Some(receiver) = receiver { vec![self.mirror_expr(receiver)] } else { vec![] }; + args.extend(initial_non_tupled_args); + args.push(tupled_args); + args.extend(final_non_tupled_args); + + // We need the tupled arguments in HIR/MIR for type checking, but codegen can + // de-tuple them for performance + let fn_span = if receiver.is_some() { func.span } else { expr.span }; + ExprKind::Call { + ty: func.ty, + fun: self.thir.exprs.push(func), + args: args.into_boxed_slice(), + from_hir_call: true, + fn_span, + } + } + fn convert_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) -> ArmId { let arm = Arm { pattern: self.pattern_from_hir(&arm.pat), diff --git a/tests/ui/splat/splat-assoc-fn-tuple-simple.rs b/tests/ui/splat/splat-assoc-fn-tuple-simple.rs new file mode 100644 index 0000000000000..d2681c0d2574c --- /dev/null +++ b/tests/ui/splat/splat-assoc-fn-tuple-simple.rs @@ -0,0 +1,22 @@ +//@ run-pass +//! Test using `#[splat]` on associated function tuple arguments (no receivers). + +#![allow(incomplete_features)] +#![feature(splat)] + +struct Foo; + +impl Foo { + fn tuple_1(#[splat] (_a,): (u32,)) {} + + fn tuple_3(#[splat] (_a, _b, _c): (u32, i32, i8)) {} +} + +fn main() { + // FIXME(splat): should splatted functions be callable with tupled and un-tupled arguments? + // Add a tupled test for each call if they are. + //Foo::tuple_1((1u32,)); + + Foo::tuple_1(1u32); + Foo::tuple_3(1u32, 2i32, 3i8); +} diff --git a/tests/ui/splat/splat-fn-ptr-tuple.rs b/tests/ui/splat/splat-fn-ptr-tuple.rs new file mode 100644 index 0000000000000..b4a6a3beae58f --- /dev/null +++ b/tests/ui/splat/splat-fn-ptr-tuple.rs @@ -0,0 +1,52 @@ +//@ failure-status: 101 + +//@ normalize-stderr: "(.*)internal compiler error:([^:]+):\d{1,}:\d{1,}:(.*)" -> "$1internal compiler error:$2:LL:CC:$3" +//@ normalize-stderr: "thread.*panicked at compiler.*" -> "" +//@ normalize-stderr: "note: rustc.*running on.*" -> "note: rustc {version} running on {platform}" +//@ normalize-stderr: "note: compiler flags.*\n\n" -> "" +//@ normalize-stderr: " +\d{1,}: .*\n" -> "" +//@ normalize-stderr: " + at .*\n" -> "" +//@ normalize-stderr: ".*omitted \d{1,} frames?.*\n" -> "" +//@ normalize-stderr: ".*note: Some details are omitted.*\n" -> "" + +//! Test using `#[splat]` on tuple arguments of simple functions. +//! Currently ICEs, but if we fix it, we'll want to know and update this test to pass. + +#![allow(incomplete_features)] +#![feature(splat)] + +fn tuple_args(#[splat] (_a, _b): (u32, i8)) {} + +fn splat_non_terminal_arg(#[splat] (_a, _b): (u32, i8), _c: f64) {} + +fn main() { + // FIXME(splat): not currently supported, can be supported when we no longer require a DefId in + // MIR lowering + // FIXME(rustfmt): the attribute gets deleted by rustfmt + // Functions + #[rustfmt::skip] + let fn_ptr: fn(#[splat] (u32, i8)) = tuple_args; + fn_ptr(1, 2); //~ ERROR no splatted def for function or method callee + fn_ptr(1u32, 2i8); + + // FIXME(splat): should splatted functions be callable with tupled and un-tupled arguments? + // Add a tupled test for each call if they are. + //fn_ptr((1, 2)); // ERROR this splatted function takes 2 arguments, but 1 was provided + + #[rustfmt::skip] + let fn_ptr: fn(#[splat] (u32, i8), f64) = splat_non_terminal_arg; + fn_ptr(1, 2, 3.5); + fn_ptr(1u32, 2i8, 3.5f64); + + // Function pointers + #[rustfmt::skip] + let fn_ptr: *const fn(#[splat] (u32, i8)) = tuple_args as *const fn(#[splat] (u32, i8)); + (*fn_ptr)(1, 2); + (*fn_ptr)(1u32, 2i8); + + #[rustfmt::skip] + let fn_ptr: *const fn(#[splat] (u32, i8), f64) = + splat_non_terminal_arg as *const fn(#[splat] (u32, i8), f64); + (*fn_ptr)(1, 2, 3.5); + (*fn_ptr)(1u32, 2i8, 3.5f64); +} diff --git a/tests/ui/splat/splat-fn-ptr-tuple.stderr b/tests/ui/splat/splat-fn-ptr-tuple.stderr new file mode 100644 index 0000000000000..700d7639241a3 --- /dev/null +++ b/tests/ui/splat/splat-fn-ptr-tuple.stderr @@ -0,0 +1,24 @@ + compiler error: compiler/rustc_mir_build/src/thir/cx/expr.rs:LL:CC: no splatted def for function or method callee + --> $DIR/splat-fn-ptr-tuple.rs:29:5 + | +LL | fn_ptr(1, 2); + | ^^^^^^^^^^^^ + + + +Box +stack backtrace: + +note: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-compiler&template=ice.md + +note: please make sure that you have updated to the latest nightly + +note: rustc {version} running on {platform} + +query stack during panic: +#0 [thir_body] building THIR for `main` +#1 [check_unsafety] unsafety-checking `main` +#2 [analysis] running analysis passes on crate `splat_fn_ptr_tuple` +end of query stack +error: aborting due to 1 previous error + diff --git a/tests/ui/splat/splat-fn-tuple-generic.rs b/tests/ui/splat/splat-fn-tuple-generic.rs new file mode 100644 index 0000000000000..b7e3615f62c45 --- /dev/null +++ b/tests/ui/splat/splat-fn-tuple-generic.rs @@ -0,0 +1,23 @@ +//@ run-pass +//! Test using `#[splat]` on tuple trait arguments of generic functions. + +#![allow(incomplete_features)] +#![feature(splat)] +#![feature(tuple_trait)] + +fn splat_generic_tuple(#[splat] _t: T) {} + +fn main() { + // Calling with un-splatted arguments might look like it works, but the actual generic type is + // a tuple inside another tuple. Aren't generics great? + splat_generic_tuple((1, 2)); + splat_generic_tuple((1u32, 2i8)); + + // Generic tuple trait implementers are resolved during caller typeck. + splat_generic_tuple::<(u32, i8)>(1u32, 2i8); + splat_generic_tuple(1u32, 2i8); + splat_generic_tuple(1, 2); + + splat_generic_tuple::<()>(); + splat_generic_tuple(); +} diff --git a/tests/ui/splat/splat-fn-tuple-simple.rs b/tests/ui/splat/splat-fn-tuple-simple.rs new file mode 100644 index 0000000000000..c7234a15b9d55 --- /dev/null +++ b/tests/ui/splat/splat-fn-tuple-simple.rs @@ -0,0 +1,32 @@ +//@ run-pass +//! Test using `#[splat]` on tuple arguments of simple functions. + +#![allow(incomplete_features)] +#![feature(splat)] + +fn tuple_args(#[splat] (_a, _b): (u32, i8)) {} + +fn splat_non_terminal_arg(#[splat] (_a, _b): (u32, i8), _c: f64) {} + +fn main() { + tuple_args(1, 2); + // FIXME(splat): should splatted functions be callable with tupled and un-tupled arguments? + // Add a tupled test for each call if they are. + //tuple_args((1, 2)); + + tuple_args(1, 2); + tuple_args(1u32, 2i8); + + splat_non_terminal_arg(1, 2, 3.5); + splat_non_terminal_arg(1u32, 2i8, 3.5f64); + + #[expect(unused_variables, reason = "FIXME(splat or lint): this is obviously used")] + let fn_ptr = tuple_args; + fn_ptr(1, 2); + fn_ptr(1u32, 2i8); + + #[expect(unused_variables, reason = "FIXME(splat or lint): this is obviously used")] + let fn_ptr = splat_non_terminal_arg; + fn_ptr(1, 2, 3.5); + fn_ptr(1u32, 2i8, 3.5f64); +} diff --git a/tests/ui/splat/splat-generics-everywhere.rs b/tests/ui/splat/splat-generics-everywhere.rs new file mode 100644 index 0000000000000..16093fe338357 --- /dev/null +++ b/tests/ui/splat/splat-generics-everywhere.rs @@ -0,0 +1,68 @@ +//@ run-pass +//! Test using `#[splat]` on tuples with generics in various positions. + +#![allow(incomplete_features)] +#![feature(splat)] + +struct Foo(T); + +// FIXME(splat): also add assoc/method with splatted generic tuple traits +// also add generics inside the splatted tuple +impl Foo { + fn new(t: T) -> Self { + Self(t) + } + + fn assoc(_u: U, #[splat] _s: ()) {} + + fn method(&self, _v: V, #[splat] _s: (u32, f64)) {} + + fn lifetime<'a>(&self, #[splat] _s: (u32, f64, &'a str)) {} + + fn const_generic(&self, #[splat] _s: (u32, f64, [u8; N])) {} +} + +// FIXME(splat): also add generics to the trait +// also add assoc/method with splatted generic tuple traits +// also add generics inside the splatted tuple +trait BarTrait { + fn trait_assoc(w: W, #[splat] _s: ()); + + fn trait_method(&self, x: X, #[splat] _s: (u32, f64)); + + fn trait_lifetime<'a>(&self, #[splat] _s: (u32, f64, &'a str)) {} + + fn trait_const_generic(&self, #[splat] _s: (u32, f64, [u8; N])) {} +} + +impl BarTrait for Foo { + fn trait_assoc(_w: W, #[splat] _s: ()) {} + + fn trait_method(&self, _x: X, #[splat] _s: (u32, f64)) {} + + fn trait_lifetime<'a>(&self, #[splat] _s: (u32, f64, &'a str)) {} + + fn trait_const_generic(&self, #[splat] _s: (u32, f64, [u8; N])) {} +} + +// FIXME(splat): +// - add `T: Tuple` generics tests +// - add const fn generics tests + +fn main() { + // FIXME(splat): should splatted functions be callable with tupled and un-tupled arguments? + // Add a tupled test for each call if they are. + //Foo::::assoc(("u",)); + + Foo::::assoc("u"); + Foo::::trait_assoc("w"); + + let foo = Foo::new("t"); + foo.method("v", 1u32, 2.3); + foo.lifetime(1u32, 2.3, "asdf"); + foo.const_generic(1u32, 2.3, [1, 2, 3]); + + foo.trait_method("x", 42u32, 9.8); + foo.trait_lifetime(1u32, 2.3, "asdf"); + foo.trait_const_generic(1u32, 2.3, [1, 2, 3]); +} diff --git a/tests/ui/splat/splat-method-tuple-simple.rs b/tests/ui/splat/splat-method-tuple-simple.rs new file mode 100644 index 0000000000000..887c9515bcdae --- /dev/null +++ b/tests/ui/splat/splat-method-tuple-simple.rs @@ -0,0 +1,40 @@ +//@ run-pass +//! Test using `#[splat]` on method tuple arguments (with receivers). + +#![allow(incomplete_features)] +#![feature(splat)] + +struct Foo; + +impl Foo { + fn tuple_2(&self, #[splat] (a, _b): (u32, i8)) -> u32 { + a + } + + fn tuple_4(&self, #[splat] (a, _b, _c, _d): (u32, i8, (), f32)) -> u32 { + a + } +} + +#[expect(dead_code)] +struct TupleStruct(u32, i8); + +impl TupleStruct { + fn tuple_2(&self, #[splat] (a, _b): (u32, i8)) -> u32 { + a + } +} + +fn main() { + let foo = Foo; + + // FIXME(splat): should splatted functions be callable with tupled and un-tupled arguments? + // Add a tupled test for each call if they are. + //foo.tuple_2((1, 2)); + + foo.tuple_2(1u32, 2i8); + foo.tuple_4(1u32, 2i8, (), 3f32); + + let tuple_struct = TupleStruct(1u32, 2i8); + tuple_struct.tuple_2(1u32, 2i8); +} diff --git a/tests/ui/splat/splat-overload-at-home.rs b/tests/ui/splat/splat-overload-at-home.rs new file mode 100644 index 0000000000000..621f0e04f67e1 --- /dev/null +++ b/tests/ui/splat/splat-overload-at-home.rs @@ -0,0 +1,52 @@ +//@ run-pass +// ignore-tidy-linelength +//! Test using `#[splat]` on some "overloading at home" example code. +//! + +#![allow(incomplete_features)] +#![feature(splat)] +#![feature(tuple_trait)] + +struct Foo; + +trait MethodArgs: std::marker::Tuple { + fn call_method(self, _this: &Foo); +} +impl MethodArgs for () { + fn call_method(self, _this: &Foo) {} +} +impl MethodArgs for (i32,) { + fn call_method(self, _this: &Foo) {} +} +impl MethodArgs for (i32, String) { + fn call_method(self, _this: &Foo) {} +} + +impl Foo { + fn method(&self, #[splat] args: T) { + args.call_method(self) + } +} + +fn main() { + let foo = Foo; + + // FIXME(splat): should splatted functions be callable with tupled and un-tupled arguments? + // Add a tupled test for each call if they are. + //foo.method(()); + //foo.method((42i32,)); + + // Generic tuple trait implementers work without explicit tuple type parameters. + foo.method::<()>(); + foo.method(); + + foo.method::<(i32,)>(42i32); + foo.method::<(i32,)>(42); + foo.method(42i32); + foo.method(42); + + foo.method::<(i32, String)>(42i32, "asdf".to_owned()); + foo.method::<(i32, String)>(42, "asdf".to_owned()); + foo.method(42i32, "asdf".to_owned()); + foo.method(42, "asdf".to_owned()); +} diff --git a/tests/ui/splat/splat-trait-tuple.rs b/tests/ui/splat/splat-trait-tuple.rs new file mode 100644 index 0000000000000..a5b74a40cffd9 --- /dev/null +++ b/tests/ui/splat/splat-trait-tuple.rs @@ -0,0 +1,45 @@ +//@ run-pass +//! Test using `#[splat]` on trait assoc function/method tuple arguments. + +#![allow(incomplete_features)] +#![feature(splat)] + +trait FooTrait { + fn tuple_1_trait(#[splat] _: (u32,)); + + fn tuple_2_trait(&self, #[splat] _: (u32, f32)); +} + +struct Foo; + +impl FooTrait for Foo { + // Currently, splat attributes on impls must match traits. This provides better UX. + fn tuple_1_trait(#[splat] _: (u32,)) {} + + fn tuple_2_trait(&self, #[splat] _: (u32, f32)) {} +} + +#[expect(dead_code)] +struct TupleStruct(u32, i8); + +impl FooTrait for TupleStruct { + fn tuple_1_trait(#[splat] _: (u32,)) {} + + fn tuple_2_trait(&self, #[splat] _: (u32, f32)) {} +} + +fn main() { + let foo = Foo; + + // FIXME(splat): should splatted functions be callable with tupled and un-tupled arguments? + // Add a tupled test for each call if they are. + //Foo::tuple_1_trait((1u32,)); + //foo.tuple_2_trait((1, 3.5)); + + Foo::tuple_1_trait(1u32); + foo.tuple_2_trait(1, 3.5); + + let tuple_struct = TupleStruct(1u32, 2i8); + TupleStruct::tuple_1_trait(1u32); + tuple_struct.tuple_2_trait(1, 3.5) +} From 50c658c0cbd9c79f4e519aeae4d4b90136dbf04f Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 11 Mar 2026 17:34:32 +1000 Subject: [PATCH 26/35] Impl TypeInfo for splat --- .../src/const_eval/type_info.rs | 18 ++++- .../rustc_const_eval/src/interpret/call.rs | 1 + compiler/rustc_span/src/symbol.rs | 2 + library/core/src/mem/type_info.rs | 18 ++++- library/coretests/tests/mem/fn_ptr.rs | 66 +++++++++++++++++++ 5 files changed, 103 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_const_eval/src/const_eval/type_info.rs b/compiler/rustc_const_eval/src/const_eval/type_info.rs index 3d8d9592459c4..8f58e39d419ce 100644 --- a/compiler/rustc_const_eval/src/const_eval/type_info.rs +++ b/compiler/rustc_const_eval/src/const_eval/type_info.rs @@ -7,7 +7,7 @@ use rustc_ast::Mutability; use rustc_hir::LangItem; use rustc_middle::span_bug; use rustc_middle::ty::layout::TyAndLayout; -use rustc_middle::ty::{self, Const, FnHeader, FnSigTys, ScalarInt, Ty, TyCtxt}; +use rustc_middle::ty::{self, Const, FnHeader, FnSigKind, FnSigTys, ScalarInt, Ty, TyCtxt}; use rustc_span::{Symbol, sym}; use crate::const_eval::CompileTimeMachine; @@ -436,6 +436,22 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> { sym::variadic => { self.write_scalar(Scalar::from_bool(fn_sig_kind.c_variadic()), &field_place)?; } + sym::is_splatted => { + self.write_scalar( + Scalar::from_bool(fn_sig_kind.splatted().is_some()), + &field_place, + )?; + } + sym::splatted_index => { + self.write_scalar( + Scalar::from_u8( + // Currently the same encoding as FnSigKind.splatted + // FIXME(splat): make these two fields into a single Option, or choose a stable encoding + fn_sig_kind.splatted().unwrap_or(FnSigKind::NO_SPLATTED_ARG_INDEX), + ), + &field_place, + )?; + } other => span_bug!(self.tcx.def_span(field.did), "unimplemented field {other}"), } } diff --git a/compiler/rustc_const_eval/src/interpret/call.rs b/compiler/rustc_const_eval/src/interpret/call.rs index 44420b7148478..e2fe8c3a79690 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -681,6 +681,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { }; // Special handling for the closure ABI: untuple the last argument. + // FIXME(splat): un-tuple splatted arguments that were tupled in typecheck let args: Cow<'_, [FnArg<'tcx, M::Provenance>]> = if caller_abi == ExternAbi::RustCall && !args.is_empty() { // Untuple diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 827e33dcd6632..b3344d150d999 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1141,6 +1141,7 @@ symbols! { irrefutable_let_patterns, is, is_auto, + is_splatted, is_val_statically_known, isa_attribute, isize, @@ -1996,6 +1997,7 @@ symbols! { speed, spirv, splat, + splatted_index, spotlight, sqrtf16, sqrtf32, diff --git a/library/core/src/mem/type_info.rs b/library/core/src/mem/type_info.rs index 9ec36561839b5..7614adbcc532b 100644 --- a/library/core/src/mem/type_info.rs +++ b/library/core/src/mem/type_info.rs @@ -215,7 +215,7 @@ pub struct Variant { pub name: &'static str, /// All fields of the variant. pub fields: &'static [Field], - /// Whether the enum variant fields is non-exhaustive. + /// Whether the enum variant fields are non-exhaustive. pub non_exhaustive: bool, } @@ -342,6 +342,22 @@ pub struct FnPtr { /// Vardiadic function, e.g. extern "C" fn add(n: usize, mut args: ...); pub variadic: bool, + + // FIXME(splat): should these fields be private, or merged into an Option? + /// Is any function argument splatted? + pub is_splatted: bool, + + /// The index of the splatted function argument in `inputs`, only valid if `is_splatted` is true. + /// e.g. in `fn overload(a: u8, #[splat] b: (f32, usize))` the index is 1, and it can be called + /// as `overload(a, 1.0, 2)`. + pub splatted_index: u8, +} + +impl FnPtr { + /// Returns the splatted function argument index, or `None` if no argument is splatted. + pub const fn splatted(&self) -> Option { + if self.is_splatted { Some(self.splatted_index) } else { None } + } } #[derive(Debug, Default)] diff --git a/library/coretests/tests/mem/fn_ptr.rs b/library/coretests/tests/mem/fn_ptr.rs index 1d50a2552a193..6e7e170917f55 100644 --- a/library/coretests/tests/mem/fn_ptr.rs +++ b/library/coretests/tests/mem/fn_ptr.rs @@ -5,6 +5,7 @@ const STRING_TY: TypeId = const { TypeId::of::() }; const U8_TY: TypeId = const { TypeId::of::() }; const _U8_REF_TY: TypeId = const { TypeId::of::<&u8>() }; const UNIT_TY: TypeId = const { TypeId::of::<()>() }; +const TUPLE_STRING_U8_TY: TypeId = const { TypeId::of::<(String, u8)>() }; #[test] fn test_fn_ptrs() { @@ -14,6 +15,8 @@ fn test_fn_ptrs() { inputs: &[], output, variadic: false, + is_splatted: false, + splatted_index: _, }) = (const { Type::of::().kind }) else { panic!(); @@ -31,6 +34,8 @@ fn test_ref() { inputs: &[ty1, ty2], output, variadic: false, + is_splatted: false, + splatted_index: _, }) = (const { Type::of::().kind }) else { panic!(); @@ -61,6 +66,8 @@ fn test_unsafe() { inputs: &[], output, variadic: false, + is_splatted: false, + splatted_index: _, }) = (const { Type::of::().kind }) else { panic!(); @@ -75,6 +82,8 @@ fn test_abi() { inputs: &[], output, variadic: false, + is_splatted: false, + splatted_index: _, }) = (const { Type::of::().kind }) else { panic!(); @@ -87,6 +96,8 @@ fn test_abi() { inputs: &[], output, variadic: false, + is_splatted: false, + splatted_index: _, }) = (const { Type::of::().kind }) else { panic!(); @@ -99,6 +110,8 @@ fn test_abi() { inputs: &[], output, variadic: false, + is_splatted: false, + splatted_index: _, }) = (const { Type::of::().kind }) else { panic!(); @@ -114,6 +127,8 @@ fn test_inputs() { inputs: &[ty1, ty2], output, variadic: false, + is_splatted: false, + splatted_index: _, }) = (const { Type::of::().kind }) else { panic!(); @@ -128,6 +143,8 @@ fn test_inputs() { inputs: &[ty1, ty2], output, variadic: false, + is_splatted: false, + splatted_index: _, }) = (const { Type::of::().kind }) else { panic!(); @@ -145,6 +162,8 @@ fn test_output() { inputs: &[], output, variadic: false, + is_splatted: false, + splatted_index: _, }) = (const { Type::of:: u8>().kind }) else { panic!(); @@ -160,6 +179,8 @@ fn test_variadic() { inputs: [ty1], output, variadic: true, + is_splatted: false, + splatted_index: _, }) = &(const { Type::of::().kind }) else { panic!(); @@ -167,3 +188,48 @@ fn test_variadic() { assert_eq!(output, &UNIT_TY); assert_eq!(*ty1, U8_TY); } + +#[test] +fn test_splat() { + #[rustfmt::skip] + let TypeKind::FnPtr(fn_ptr_ty) = &(const { Type::of::().kind }) else { + panic!(); + }; + let FnPtr { + unsafety: false, + abi: Abi::ExternRust, + inputs: [ty1], + output, + variadic: false, + is_splatted: true, + splatted_index: 0, + } = fn_ptr_ty + else { + panic!(); + }; + assert_eq!(output, &UNIT_TY); + assert_eq!(*ty1, TUPLE_STRING_U8_TY); + assert_eq!(fn_ptr_ty.splatted(), Some(0)); +} + +#[test] +fn test_not_splat() { + let TypeKind::FnPtr(fn_ptr_ty) = &(const { Type::of::().kind }) else { + panic!(); + }; + let FnPtr { + unsafety: false, + abi: Abi::ExternRust, + inputs: [ty1], + output, + variadic: false, + is_splatted: false, + splatted_index: _, + } = fn_ptr_ty + else { + panic!(); + }; + assert_eq!(output, &UNIT_TY); + assert_eq!(*ty1, TUPLE_STRING_U8_TY); + assert_eq!(fn_ptr_ty.splatted(), None); +} From dc3889a9f6d163a49cf87c05a50d90d408a4eb32 Mon Sep 17 00:00:00 2001 From: Ajay Singh Date: Wed, 3 Jun 2026 15:44:38 +0530 Subject: [PATCH 27/35] Add UI tests for `#[splat]` unhappy path, generics, fn colors - const, async, and unsafe functions - const generics, complex types, and where clauses (including impl Tuple) --- tests/ui/splat/splat-async-fn-tuple-fail.rs | 17 ++++++ .../ui/splat/splat-async-fn-tuple-fail.stderr | 27 +++++++++ tests/ui/splat/splat-async-fn-tuple.rs | 18 ++++++ .../ui/splat/splat-const-fn-tuple-generic.rs | 34 +++++++++++ tests/ui/splat/splat-const-fn-tuple.rs | 17 ++++++ .../ui/splat/splat-generics-complex-types.rs | 26 ++++++++ tests/ui/splat/splat-generics-everywhere.rs | 4 -- tests/ui/splat/splat-generics-inside-tuple.rs | 29 +++++++++ tests/ui/splat/splat-invalid-trait-impl.rs | 27 +++++++++ .../ui/splat/splat-invalid-trait-impl.stderr | 60 +++++++++++++++++++ tests/ui/splat/splat-overload-at-home-fail.rs | 6 +- .../splat/splat-overload-at-home-fail.stderr | 8 +-- tests/ui/splat/splat-unsafe-fn-tuple-fail.rs | 16 +++++ .../splat/splat-unsafe-fn-tuple-fail.stderr | 20 +++++++ tests/ui/splat/splat-unsafe-fn-tuple.rs | 19 ++++++ tests/ui/splat/splat-where-clause.rs | 44 ++++++++++++++ 16 files changed, 359 insertions(+), 13 deletions(-) create mode 100644 tests/ui/splat/splat-async-fn-tuple-fail.rs create mode 100644 tests/ui/splat/splat-async-fn-tuple-fail.stderr create mode 100644 tests/ui/splat/splat-async-fn-tuple.rs create mode 100644 tests/ui/splat/splat-const-fn-tuple-generic.rs create mode 100644 tests/ui/splat/splat-const-fn-tuple.rs create mode 100644 tests/ui/splat/splat-generics-complex-types.rs create mode 100644 tests/ui/splat/splat-generics-inside-tuple.rs create mode 100644 tests/ui/splat/splat-invalid-trait-impl.rs create mode 100644 tests/ui/splat/splat-invalid-trait-impl.stderr create mode 100644 tests/ui/splat/splat-unsafe-fn-tuple-fail.rs create mode 100644 tests/ui/splat/splat-unsafe-fn-tuple-fail.stderr create mode 100644 tests/ui/splat/splat-unsafe-fn-tuple.rs create mode 100644 tests/ui/splat/splat-where-clause.rs diff --git a/tests/ui/splat/splat-async-fn-tuple-fail.rs b/tests/ui/splat/splat-async-fn-tuple-fail.rs new file mode 100644 index 0000000000000..a1f67dfe606d6 --- /dev/null +++ b/tests/ui/splat/splat-async-fn-tuple-fail.rs @@ -0,0 +1,17 @@ +//@ edition:2024 +//! Test that using `#[splat]` incorrectly on async functions gives errors. + +#![allow(incomplete_features)] +#![feature(splat)] + +async fn async_wrong_type(#[splat] _x: u32) {} +//~^ ERROR cannot use splat attribute; the splatted argument type must be a tuple or unit, not a u32 + +async fn async_multi_splat(#[splat] (_a, _b): (u32, i8), #[splat] (_c, _d): (u32, i8)) {} +//~^ ERROR multiple `#[splat]`s are not allowed in the same function + +fn main() { + async_wrong_type(1u32); + async_multi_splat(1u32, 2i8, 3u32, 4i8); + //~^ ERROR this splatted function takes 3 arguments, but 4 were provided +} diff --git a/tests/ui/splat/splat-async-fn-tuple-fail.stderr b/tests/ui/splat/splat-async-fn-tuple-fail.stderr new file mode 100644 index 0000000000000..e643b29c88c8a --- /dev/null +++ b/tests/ui/splat/splat-async-fn-tuple-fail.stderr @@ -0,0 +1,27 @@ +error: multiple `#[splat]`s are not allowed in the same function + --> $DIR/splat-async-fn-tuple-fail.rs:10:28 + | +LL | async fn async_multi_splat(#[splat] (_a, _b): (u32, i8), #[splat] (_c, _d): (u32, i8)) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: remove `#[splat]` from all but one argument + +error[E0277]: cannot use splat attribute; the splatted argument type must be a tuple or unit, not a u32 (u32) + --> $DIR/splat-async-fn-tuple-fail.rs:7:40 + | +LL | async fn async_wrong_type(#[splat] _x: u32) {} + | ^^^ +... +LL | async_wrong_type(1u32); + | ^^^^^^^^^^^^^^^^^^^^^^ + +error[E0057]: this splatted function takes 3 arguments, but 4 were provided + --> $DIR/splat-async-fn-tuple-fail.rs:15:5 + | +LL | async_multi_splat(1u32, 2i8, 3u32, 4i8); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 3 previous errors + +Some errors have detailed explanations: E0057, E0277. +For more information about an error, try `rustc --explain E0057`. diff --git a/tests/ui/splat/splat-async-fn-tuple.rs b/tests/ui/splat/splat-async-fn-tuple.rs new file mode 100644 index 0000000000000..81e56193bd4ab --- /dev/null +++ b/tests/ui/splat/splat-async-fn-tuple.rs @@ -0,0 +1,18 @@ +//@ run-pass +//@ edition:2024 +//! Test using `#[splat]` on tuple arguments of async functions. + +#![allow(incomplete_features)] +#![feature(splat)] + +async fn async_tuple_args(#[splat] (_a, _b): (u32, i8)) {} + +async fn async_splat_non_terminal_arg(#[splat] (_a, _b): (u32, i8), _c: f64) {} + +fn main() { + let _ = async_tuple_args(1u32, 2i8); + let _ = async_tuple_args(1, 2); + + let _ = async_splat_non_terminal_arg(1u32, 2i8, 3.5f64); + let _ = async_splat_non_terminal_arg(1, 2, 3.5); +} diff --git a/tests/ui/splat/splat-const-fn-tuple-generic.rs b/tests/ui/splat/splat-const-fn-tuple-generic.rs new file mode 100644 index 0000000000000..646bf7e9b9f19 --- /dev/null +++ b/tests/ui/splat/splat-const-fn-tuple-generic.rs @@ -0,0 +1,34 @@ +//@ run-pass +//! Test using `#[splat]` on tuple arguments of const functions with generics. + +#![allow(incomplete_features)] +#![feature(splat)] + +// Generic type in first position +const fn const_generic_first(#[splat] _: (T, u32)) {} + +// Generic type in second position +const fn const_generic_second(#[splat] _: (u32, T)) {} + +// Multiple generic types +const fn const_generic_both(#[splat] _: (T, U)) {} + +// Generic with extra non-splatted arg +const fn const_generic_extra(#[splat] _: (T, u32), _extra: i32) {} + +fn main() { + const_generic_first(1i8, 2u32); + const_generic_first(true, 2u32); + const_generic_first(1u64, 2u32); + + const_generic_second(1u32, 2i8); + const_generic_second(1u32, true); + const_generic_second(1u32, 2u64); + + const_generic_both(1u32, 2i8); + const_generic_both(true, 2u64); + const_generic_both(1i8, false); + + const_generic_extra(1i8, 2u32, 42i32); + const_generic_extra(true, 2u32, 42i32); +} diff --git a/tests/ui/splat/splat-const-fn-tuple.rs b/tests/ui/splat/splat-const-fn-tuple.rs new file mode 100644 index 0000000000000..f4ae2e124a72d --- /dev/null +++ b/tests/ui/splat/splat-const-fn-tuple.rs @@ -0,0 +1,17 @@ +//@ run-pass +//! Test using `#[splat]` on tuple arguments of const functions. + +#![allow(incomplete_features)] +#![feature(splat)] + +const fn sum(#[splat] (a, b): (u32, u32)) -> u32 { + a + b +} + +const RESULT: u32 = sum(1, 2); + +fn main() { + assert_eq!(RESULT, 3); + assert_eq!(sum(12, 18) , 30); + assert_eq!(sum(1, 2), 3); +} diff --git a/tests/ui/splat/splat-generics-complex-types.rs b/tests/ui/splat/splat-generics-complex-types.rs new file mode 100644 index 0000000000000..7d4490c18da75 --- /dev/null +++ b/tests/ui/splat/splat-generics-complex-types.rs @@ -0,0 +1,26 @@ +//@ run-pass +//! Test using `#[splat]` on tuples with complex generic types inside the splatted tuple. + +#![allow(incomplete_features)] +#![feature(splat)] + +// Vec and Option inside splatted tuple +fn nested_generic(#[splat] _: (Vec, Option)) {} + +// Box inside splatted tuple +fn box_generic(#[splat] _: (Box, u32)) {} + +// Multiple complex generics +fn multi_generic(#[splat] _: (Vec, Option, Box)) {} + +fn main() { + nested_generic(vec![1u32, 2u32], Some(2i8)); + nested_generic(vec![1, 2, 3], None::); + nested_generic::(vec![], Some("hello")); + + box_generic(Box::new(1u32), 42u32); + box_generic(Box::new("hello"), 1u32); + + multi_generic(vec![1u32], Some(2i8), Box::new(3.0f64)); + multi_generic::(vec![], None::, Box::new("hello")); +} diff --git a/tests/ui/splat/splat-generics-everywhere.rs b/tests/ui/splat/splat-generics-everywhere.rs index 16093fe338357..be4f917ce21df 100644 --- a/tests/ui/splat/splat-generics-everywhere.rs +++ b/tests/ui/splat/splat-generics-everywhere.rs @@ -45,10 +45,6 @@ impl BarTrait for Foo { fn trait_const_generic(&self, #[splat] _s: (u32, f64, [u8; N])) {} } -// FIXME(splat): -// - add `T: Tuple` generics tests -// - add const fn generics tests - fn main() { // FIXME(splat): should splatted functions be callable with tupled and un-tupled arguments? // Add a tupled test for each call if they are. diff --git a/tests/ui/splat/splat-generics-inside-tuple.rs b/tests/ui/splat/splat-generics-inside-tuple.rs new file mode 100644 index 0000000000000..dce26e55fa2d9 --- /dev/null +++ b/tests/ui/splat/splat-generics-inside-tuple.rs @@ -0,0 +1,29 @@ +//@ run-pass +//! Test using `#[splat]` on tuples with generics inside the splatted tuple. +#![allow(incomplete_features)] +#![feature(splat)] + +fn generic_second(#[splat] _s: (u32, T)) {} + +fn generic_first(#[splat] _s: (T, u32)) {} + +fn generic_both(#[splat] _s: (T, U)) {} + +fn generic_triple(#[splat] _s: (T, U, V)) {} + +fn main() { + generic_second(1u32, 2i8); + generic_second(1u32, 2.0f64); + generic_second(1u32, "hello"); + + generic_first(1i8, 2u32); + generic_first(2.0f64, 2u32); + generic_first("hello", 2u32); + + generic_both(1u32, 2i8); + generic_both("hello", 2.0f64); + generic_both(true, "world"); + + generic_triple(1u32, 2.0f64, "hello"); + generic_triple(true, 42i32, 3.14f32); +} diff --git a/tests/ui/splat/splat-invalid-trait-impl.rs b/tests/ui/splat/splat-invalid-trait-impl.rs new file mode 100644 index 0000000000000..fe0d5ddc5a1c8 --- /dev/null +++ b/tests/ui/splat/splat-invalid-trait-impl.rs @@ -0,0 +1,27 @@ +//! Test that `#[splat]` trait impls with mismatched tuple element types are rejected. +#![allow(incomplete_features)] +#![feature(splat)] + +trait FooTrait { + fn method(#[splat] _: (u32, i8)); +} + +struct Foo; +struct Foo1; +struct Foo2; + +impl FooTrait for Foo { + fn method(#[splat] _: (u32, f32)) {} + //~^ ERROR method `method` has an incompatible type for trait +} + +impl FooTrait for Foo1 { + fn method(#[splat] _: (f32, i8)) {} + //~^ ERROR method `method` has an incompatible type for trait +} + +impl FooTrait for Foo2 { + fn method(#[splat] _: (f32, f64)) {} + //~^ ERROR method `method` has an incompatible type for trait +} +fn main() {} diff --git a/tests/ui/splat/splat-invalid-trait-impl.stderr b/tests/ui/splat/splat-invalid-trait-impl.stderr new file mode 100644 index 0000000000000..684cc65d7a326 --- /dev/null +++ b/tests/ui/splat/splat-invalid-trait-impl.stderr @@ -0,0 +1,60 @@ +error[E0053]: method `method` has an incompatible type for trait + --> $DIR/splat-invalid-trait-impl.rs:14:27 + | +LL | fn method(#[splat] _: (u32, f32)) {} + | ^^^^^^^^^^ expected `i8`, found `f32` + | +note: type in trait + --> $DIR/splat-invalid-trait-impl.rs:6:27 + | +LL | fn method(#[splat] _: (u32, i8)); + | ^^^^^^^^^ + = note: expected signature `fn(#[splat] (_, i8))` + found signature `fn(#[splat] (_, f32))` +help: change the parameter type to match the trait + | +LL - fn method(#[splat] _: (u32, f32)) {} +LL + fn method(#[splat] _: (u32, i8)) {} + | + +error[E0053]: method `method` has an incompatible type for trait + --> $DIR/splat-invalid-trait-impl.rs:19:27 + | +LL | fn method(#[splat] _: (f32, i8)) {} + | ^^^^^^^^^ expected `u32`, found `f32` + | +note: type in trait + --> $DIR/splat-invalid-trait-impl.rs:6:27 + | +LL | fn method(#[splat] _: (u32, i8)); + | ^^^^^^^^^ + = note: expected signature `fn(#[splat] (u32, _))` + found signature `fn(#[splat] (f32, _))` +help: change the parameter type to match the trait + | +LL - fn method(#[splat] _: (f32, i8)) {} +LL + fn method(#[splat] _: (u32, i8)) {} + | + +error[E0053]: method `method` has an incompatible type for trait + --> $DIR/splat-invalid-trait-impl.rs:24:27 + | +LL | fn method(#[splat] _: (f32, f64)) {} + | ^^^^^^^^^^ expected `u32`, found `f32` + | +note: type in trait + --> $DIR/splat-invalid-trait-impl.rs:6:27 + | +LL | fn method(#[splat] _: (u32, i8)); + | ^^^^^^^^^ + = note: expected signature `fn(#[splat] (u32, i8))` + found signature `fn(#[splat] (f32, f64))` +help: change the parameter type to match the trait + | +LL - fn method(#[splat] _: (f32, f64)) {} +LL + fn method(#[splat] _: (u32, i8)) {} + | + +error: aborting due to 3 previous errors + +For more information about this error, try `rustc --explain E0053`. diff --git a/tests/ui/splat/splat-overload-at-home-fail.rs b/tests/ui/splat/splat-overload-at-home-fail.rs index 730b46ac0f9ee..8e6a375e0eb96 100644 --- a/tests/ui/splat/splat-overload-at-home-fail.rs +++ b/tests/ui/splat/splat-overload-at-home-fail.rs @@ -1,6 +1,3 @@ -// tests/ui/splat/splat-overload-at-home-fail.rs - -// ignore-tidy-linelength //! Test error cases for `#[splat]` "overloading at home" example code. //! Splatted calls that don't match any registered MethodArgs impl should fail. #![allow(incomplete_features)] @@ -41,5 +38,4 @@ fn main() { // No impl for (i32,i32) - wrong type foo.method(42i32, 42i32); //~^ ERROR mismatched types - - } +} diff --git a/tests/ui/splat/splat-overload-at-home-fail.stderr b/tests/ui/splat/splat-overload-at-home-fail.stderr index 8a0ba449af213..fcedccf28c359 100644 --- a/tests/ui/splat/splat-overload-at-home-fail.stderr +++ b/tests/ui/splat/splat-overload-at-home-fail.stderr @@ -1,5 +1,5 @@ error[E0308]: mismatched types - --> $DIR/splat-overload-at-home-fail.rs:38:16 + --> $DIR/splat-overload-at-home-fail.rs:35:16 | LL | foo.method(42f32); | ------ ^^^^^ expected `i32`, found `f32` @@ -7,7 +7,7 @@ LL | foo.method(42f32); | arguments to this method are incorrect | note: method defined here - --> $DIR/splat-overload-at-home-fail.rs:29:8 + --> $DIR/splat-overload-at-home-fail.rs:26:8 | LL | fn method(&self, #[splat] args: T) { | ^^^^^^ ---------------- @@ -18,7 +18,7 @@ LL + foo.method(42i32); | error[E0308]: mismatched types - --> $DIR/splat-overload-at-home-fail.rs:42:23 + --> $DIR/splat-overload-at-home-fail.rs:39:23 | LL | foo.method(42i32, 42i32); | ------ ^^^^^ expected `String`, found `i32` @@ -26,7 +26,7 @@ LL | foo.method(42i32, 42i32); | arguments to this method are incorrect | note: method defined here - --> $DIR/splat-overload-at-home-fail.rs:29:8 + --> $DIR/splat-overload-at-home-fail.rs:26:8 | LL | fn method(&self, #[splat] args: T) { | ^^^^^^ diff --git a/tests/ui/splat/splat-unsafe-fn-tuple-fail.rs b/tests/ui/splat/splat-unsafe-fn-tuple-fail.rs new file mode 100644 index 0000000000000..3f29b9d292d22 --- /dev/null +++ b/tests/ui/splat/splat-unsafe-fn-tuple-fail.rs @@ -0,0 +1,16 @@ +//! Test that using `#[splat]` incorrectly on unsafe functions gives errors. + +#![allow(incomplete_features)] +#![feature(splat)] + +unsafe fn unsafe_wrong_type(#[splat] _x: u32) {} +//~^ ERROR cannot use splat attribute; the splatted argument type must be a tuple or unit, not a u32 + +unsafe fn unsafe_multi_splat(#[splat] (_a, _b): (u32, i8), #[splat] (_c, _d): (u32, i8)) {} +//~^ ERROR multiple `#[splat]`s are not allowed in the same function + +fn main() { + unsafe { + unsafe_wrong_type(1u32); + } +} diff --git a/tests/ui/splat/splat-unsafe-fn-tuple-fail.stderr b/tests/ui/splat/splat-unsafe-fn-tuple-fail.stderr new file mode 100644 index 0000000000000..67935e170671a --- /dev/null +++ b/tests/ui/splat/splat-unsafe-fn-tuple-fail.stderr @@ -0,0 +1,20 @@ +error: multiple `#[splat]`s are not allowed in the same function + --> $DIR/splat-unsafe-fn-tuple-fail.rs:9:30 + | +LL | unsafe fn unsafe_multi_splat(#[splat] (_a, _b): (u32, i8), #[splat] (_c, _d): (u32, i8)) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: remove `#[splat]` from all but one argument + +error[E0277]: cannot use splat attribute; the splatted argument type must be a tuple or unit, not a u32 (u32) + --> $DIR/splat-unsafe-fn-tuple-fail.rs:6:42 + | +LL | unsafe fn unsafe_wrong_type(#[splat] _x: u32) {} + | ^^^ +... +LL | unsafe_wrong_type(1u32); + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/splat/splat-unsafe-fn-tuple.rs b/tests/ui/splat/splat-unsafe-fn-tuple.rs new file mode 100644 index 0000000000000..0b9b3510ad50b --- /dev/null +++ b/tests/ui/splat/splat-unsafe-fn-tuple.rs @@ -0,0 +1,19 @@ +//@ run-pass +//! Test using `#[splat]` on tuple arguments of unsafe functions. + +#![allow(incomplete_features)] +#![feature(splat)] + +unsafe fn unsafe_tuple_args(#[splat] (_a, _b): (u32, i8)) {} + +unsafe fn unsafe_splat_non_terminal_arg(#[splat] (_a, _b): (u32, i8), _c: f64) {} + +fn main() { + unsafe { + unsafe_tuple_args(1u32, 2i8); + unsafe_tuple_args(1, 2); + + unsafe_splat_non_terminal_arg(1u32, 2i8, 3.5f64); + unsafe_splat_non_terminal_arg(1, 2, 3.5); + } +} diff --git a/tests/ui/splat/splat-where-clause.rs b/tests/ui/splat/splat-where-clause.rs new file mode 100644 index 0000000000000..42a23fe67b216 --- /dev/null +++ b/tests/ui/splat/splat-where-clause.rs @@ -0,0 +1,44 @@ +//@ run-pass +//! Test using `#[splat]` on tuple arguments with where clause bounds. + +#![allow(incomplete_features)] +#![feature(splat)] +#![feature(tuple_trait)] + +fn where_splat(#[splat] _t: T) where T: std::marker::Tuple {} + +fn where_splat_with_extra(#[splat] _t: T, _extra: u32) where T: std::marker::Tuple {} + +fn impl_tuple_splat(#[splat] _t: impl std::marker::Tuple) {} + +fn impl_tuple_splat_with_extra(#[splat] _t: impl std::marker::Tuple, _extra: u32) {} + +fn main() { + // empty tuple + where_splat(); + + // single element + where_splat(1u32); + where_splat(1); + + // two elements + where_splat(1u32, 2i8); + where_splat(1, 2); + + // three elements + where_splat(1u32, 2i8, 3.0f64); + where_splat(1, 2, 3.0); + + // with extra non-splatted arg + where_splat_with_extra(1u32, 2i8, 42u32); + where_splat_with_extra(1, 2, 42); + + // impl Trait syntax variants + impl_tuple_splat(); + impl_tuple_splat(1u32); + impl_tuple_splat(1u32, 2i8); + impl_tuple_splat(1, 2, 3.0); + + impl_tuple_splat_with_extra(1u32, 2i8, 42u32); + impl_tuple_splat_with_extra(1, 2, 42); +} From 633b89bd53b73dee02050af2e0b23b8fdef858c1 Mon Sep 17 00:00:00 2001 From: Ajay Singh Date: Mon, 22 Jun 2026 11:50:13 +0530 Subject: [PATCH 28/35] Add failing test for #[splat] with dyn AsRef where T: Tuple --- tests/ui/splat/splat-dyn-asref-tuple-fail.rs | 14 +++++++++++ .../splat/splat-dyn-asref-tuple-fail.stderr | 24 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 tests/ui/splat/splat-dyn-asref-tuple-fail.rs create mode 100644 tests/ui/splat/splat-dyn-asref-tuple-fail.stderr diff --git a/tests/ui/splat/splat-dyn-asref-tuple-fail.rs b/tests/ui/splat/splat-dyn-asref-tuple-fail.rs new file mode 100644 index 0000000000000..40d702346b7a3 --- /dev/null +++ b/tests/ui/splat/splat-dyn-asref-tuple-fail.rs @@ -0,0 +1,14 @@ +//! Test that `#[splat]` on `&dyn AsRef` where `T: Tuple` is an error. + +#![allow(incomplete_features)] +#![feature(splat)] +#![feature(tuple_trait)] + +fn dyn_asref_splat(#[splat] _t: &dyn AsRef) where T: std::marker::Tuple {} +//~^ ERROR cannot use splat attribute + +fn main() { + let s: String = "hello".to_owned(); + dyn_asref_splat::(&s); + //~^ ERROR `String` is not a tuple +} diff --git a/tests/ui/splat/splat-dyn-asref-tuple-fail.stderr b/tests/ui/splat/splat-dyn-asref-tuple-fail.stderr new file mode 100644 index 0000000000000..cba1ab28181a4 --- /dev/null +++ b/tests/ui/splat/splat-dyn-asref-tuple-fail.stderr @@ -0,0 +1,24 @@ +error[E0277]: cannot use splat attribute; the splatted argument type must be a tuple or unit, not a &'?3 dyn [Binder { value: Trait(std::convert::AsRef), bound_vars: [] }] + '?3 (&'?3 dyn [Binder { value: Trait(std::convert::AsRef), bound_vars: [] }] + '?3) + --> $DIR/splat-dyn-asref-tuple-fail.rs:7:36 + | +LL | fn dyn_asref_splat(#[splat] _t: &dyn AsRef) where T: std::marker::Tuple {} + | ^^^^^^^^^^^^^ +... +LL | dyn_asref_splat::(&s); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0277]: `String` is not a tuple + --> $DIR/splat-dyn-asref-tuple-fail.rs:12:23 + | +LL | dyn_asref_splat::(&s); + | ^^^^^^ the nightly-only, unstable trait `std::marker::Tuple` is not implemented for `String` + | +note: required by a bound in `dyn_asref_splat` + --> $DIR/splat-dyn-asref-tuple-fail.rs:7:60 + | +LL | fn dyn_asref_splat(#[splat] _t: &dyn AsRef) where T: std::marker::Tuple {} + | ^^^^^^^^^^^^^^^^^^ required by this bound in `dyn_asref_splat` + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0277`. From 3173c8b900d8007bdb546ee0ac762aef78da2b88 Mon Sep 17 00:00:00 2001 From: teor Date: Mon, 22 Jun 2026 16:13:45 +0200 Subject: [PATCH 29/35] Add splat UI tests for 255 splatted arg limit --- tests/ui/splat/splat-255-limit-fail.rs | 97 +++++++++++++ tests/ui/splat/splat-255-limit-fail.stderr | 34 +++++ tests/ui/splat/splat-255-limit-pass.rs | 156 +++++++++++++++++++++ 3 files changed, 287 insertions(+) create mode 100644 tests/ui/splat/splat-255-limit-fail.rs create mode 100644 tests/ui/splat/splat-255-limit-fail.stderr create mode 100644 tests/ui/splat/splat-255-limit-pass.rs diff --git a/tests/ui/splat/splat-255-limit-fail.rs b/tests/ui/splat/splat-255-limit-fail.rs new file mode 100644 index 0000000000000..422988ffc3f01 --- /dev/null +++ b/tests/ui/splat/splat-255-limit-fail.rs @@ -0,0 +1,97 @@ +// ignore-tidy-linelength +//! Test `#[splat]` fails over the 255th argument index (or higher). +//! FIXME(splat): The 255 argument limit is a temporary performance hack. + +#![allow(incomplete_features)] +#![feature(splat)] +#![expect(dead_code)] + +type A = (); + +// These functions are deliberately formatted with 17 arguments in 15 lines, to show they have 255 +// arguments. +#[rustfmt::skip] +fn s_255_terminal( + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + #[splat] (_a, _b): (u32, i8), //~ ERROR `#[splat]` is not supported on argument index 255 +) {} + +#[rustfmt::skip] +fn s_256_terminal( + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, + #[splat] (_a, _b): (u32, i8), //~ ERROR `#[splat]` is not supported on argument index 256 +) {} + +#[rustfmt::skip] +fn s_255_non_terminal( + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + #[splat] (_a, _b): (u32, i8), //~ ERROR `#[splat]` is not supported on argument index 255 + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, +) {} + +#[rustfmt::skip] +fn s_256_non_terminal( + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, + #[splat] (_a, _b): (u32, i8), //~ ERROR `#[splat]` is not supported on argument index 256 + _: A, +) {} + +fn main() {} diff --git a/tests/ui/splat/splat-255-limit-fail.stderr b/tests/ui/splat/splat-255-limit-fail.stderr new file mode 100644 index 0000000000000..51cad1b3cf588 --- /dev/null +++ b/tests/ui/splat/splat-255-limit-fail.stderr @@ -0,0 +1,34 @@ +error: `#[splat]` is not supported on argument index 255 + --> $DIR/splat-255-limit-fail.rs:30:5 + | +LL | #[splat] (_a, _b): (u32, i8), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `#[splat]` is not supported here + | + = help: remove `#[splat]`, or use it on an argument closer to the start of the argument list + +error: `#[splat]` is not supported on argument index 256 + --> $DIR/splat-255-limit-fail.rs:51:5 + | +LL | #[splat] (_a, _b): (u32, i8), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `#[splat]` is not supported here + | + = help: remove `#[splat]`, or use it on an argument closer to the start of the argument list + +error: `#[splat]` is not supported on argument index 255 + --> $DIR/splat-255-limit-fail.rs:71:5 + | +LL | #[splat] (_a, _b): (u32, i8), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `#[splat]` is not supported here + | + = help: remove `#[splat]`, or use it on an argument closer to the start of the argument list + +error: `#[splat]` is not supported on argument index 256 + --> $DIR/splat-255-limit-fail.rs:93:5 + | +LL | #[splat] (_a, _b): (u32, i8), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `#[splat]` is not supported here + | + = help: remove `#[splat]`, or use it on an argument closer to the start of the argument list + +error: aborting due to 4 previous errors + diff --git a/tests/ui/splat/splat-255-limit-pass.rs b/tests/ui/splat/splat-255-limit-pass.rs new file mode 100644 index 0000000000000..37301afcec561 --- /dev/null +++ b/tests/ui/splat/splat-255-limit-pass.rs @@ -0,0 +1,156 @@ +//@ run-pass +// ignore-tidy-linelength +//! Test `#[splat]` on the 255th argument index (or lower). +//! FIXME(splat): The 255 argument limit is a temporary performance hack. + +#![allow(incomplete_features)] +#![feature(splat)] +#![expect(dead_code)] + +type A = (); + +// These functions are deliberately formatted with 17 arguments in 15 lines, to show they have 255 +// arguments. +#[rustfmt::skip] +fn s_253_terminal( + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + #[splat] (_a, _b): (u32, i8), +) {} + +#[rustfmt::skip] +fn s_254_terminal( + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + #[splat] (_a, _b): (u32, i8), +) {} + +#[rustfmt::skip] +fn s_254_non_terminal_272_args( + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + #[splat] (_a, _b): (u32, i8), + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, +) {} + +#[rustfmt::skip] +fn s_0_initial_253_args( + #[splat] (_a, _b): (u32, i8), + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, +) {} + +#[rustfmt::skip] +fn s_0_initial_254_args( + #[splat] (_a, _b): (u32, i8), + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, +) {} + +#[rustfmt::skip] +fn s_0_initial_255_args( + #[splat] (_a, _b): (u32, i8), + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, +) {} + +#[rustfmt::skip] +fn s_0_initial_256_args( + #[splat] (_a, _b): (u32, i8), + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, + _: A, +) {} + +fn main() {} From 18185d0a6dfdfbd6e89e8c62a4e40dfa32bc6773 Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 23 Jun 2026 14:55:04 +0200 Subject: [PATCH 30/35] UI test arg diffs greater than u8::MAX --- tests/ui/splat/splat-255-limit-fail.rs | 108 ++++++++++++++++++++- tests/ui/splat/splat-255-limit-fail.stderr | 47 ++++++++- tests/ui/splat/splat-255-limit-pass.rs | 51 +++++++++- 3 files changed, 195 insertions(+), 11 deletions(-) diff --git a/tests/ui/splat/splat-255-limit-fail.rs b/tests/ui/splat/splat-255-limit-fail.rs index 422988ffc3f01..31767f11d999b 100644 --- a/tests/ui/splat/splat-255-limit-fail.rs +++ b/tests/ui/splat/splat-255-limit-fail.rs @@ -8,8 +8,28 @@ type A = (); -// These functions are deliberately formatted with 17 arguments in 15 lines, to show they have 255 -// arguments. +// These types and functions are deliberately formatted with 17 arguments in 15 lines, to show they +// have ~255 arguments. +#[rustfmt::skip] +type Tuple256 = ( + A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, + A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, + A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, + A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, + A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, + A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, + A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, + A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, + A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, + A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, + A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, + A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, + A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, + A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, + A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, + A, +); + #[rustfmt::skip] fn s_255_terminal( _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, @@ -94,4 +114,86 @@ fn s_256_non_terminal( _: A, ) {} -fn main() {} +// It's only the splatted index that's constrained to 255, not the argument count of the caller or callee. +fn more_than_255_splatted_args(#[splat] _t: Tuple256) {} + +fn main() { + let a = (); + + #[rustfmt::skip] + more_than_255_splatted_args( //~ ERROR this splatted function takes 256 arguments, but 255 were provided [E0057] + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + /* missing: a, */ + ); + + #[rustfmt::skip] + more_than_255_splatted_args( //~ ERROR this splatted function takes 256 arguments, but 257 were provided [E0057] + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, /* unexpected: */ a, + ); + + #[rustfmt::skip] + more_than_255_splatted_args( //~ ERROR this splatted function takes 256 arguments, but 512 were provided [E0057] + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, + /* unexpected: */ + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, + ); +} diff --git a/tests/ui/splat/splat-255-limit-fail.stderr b/tests/ui/splat/splat-255-limit-fail.stderr index 51cad1b3cf588..da62da8aa605b 100644 --- a/tests/ui/splat/splat-255-limit-fail.stderr +++ b/tests/ui/splat/splat-255-limit-fail.stderr @@ -1,5 +1,5 @@ error: `#[splat]` is not supported on argument index 255 - --> $DIR/splat-255-limit-fail.rs:30:5 + --> $DIR/splat-255-limit-fail.rs:50:5 | LL | #[splat] (_a, _b): (u32, i8), | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `#[splat]` is not supported here @@ -7,7 +7,7 @@ LL | #[splat] (_a, _b): (u32, i8), = help: remove `#[splat]`, or use it on an argument closer to the start of the argument list error: `#[splat]` is not supported on argument index 256 - --> $DIR/splat-255-limit-fail.rs:51:5 + --> $DIR/splat-255-limit-fail.rs:71:5 | LL | #[splat] (_a, _b): (u32, i8), | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `#[splat]` is not supported here @@ -15,7 +15,7 @@ LL | #[splat] (_a, _b): (u32, i8), = help: remove `#[splat]`, or use it on an argument closer to the start of the argument list error: `#[splat]` is not supported on argument index 255 - --> $DIR/splat-255-limit-fail.rs:71:5 + --> $DIR/splat-255-limit-fail.rs:91:5 | LL | #[splat] (_a, _b): (u32, i8), | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `#[splat]` is not supported here @@ -23,12 +23,49 @@ LL | #[splat] (_a, _b): (u32, i8), = help: remove `#[splat]`, or use it on an argument closer to the start of the argument list error: `#[splat]` is not supported on argument index 256 - --> $DIR/splat-255-limit-fail.rs:93:5 + --> $DIR/splat-255-limit-fail.rs:113:5 | LL | #[splat] (_a, _b): (u32, i8), | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `#[splat]` is not supported here | = help: remove `#[splat]`, or use it on an argument closer to the start of the argument list -error: aborting due to 4 previous errors +error[E0057]: this splatted function takes 256 arguments, but 255 were provided + --> $DIR/splat-255-limit-fail.rs:124:5 + | +LL | / more_than_255_splatted_args( +LL | | a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, +LL | | a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, +LL | | a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, +... | +LL | | /* missing: a, */ +LL | | ); + | |_____^ + +error[E0057]: this splatted function takes 256 arguments, but 257 were provided + --> $DIR/splat-255-limit-fail.rs:144:5 + | +LL | / more_than_255_splatted_args( +LL | | a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, +LL | | a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, +LL | | a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, +... | +LL | | a, /* unexpected: */ a, +LL | | ); + | |_____^ + +error[E0057]: this splatted function takes 256 arguments, but 512 were provided + --> $DIR/splat-255-limit-fail.rs:164:5 + | +LL | / more_than_255_splatted_args( +LL | | a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, +LL | | a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, +LL | | a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, +... | +LL | | a, +LL | | ); + | |_____^ + +error: aborting due to 7 previous errors +For more information about this error, try `rustc --explain E0057`. diff --git a/tests/ui/splat/splat-255-limit-pass.rs b/tests/ui/splat/splat-255-limit-pass.rs index 37301afcec561..9713616830f9b 100644 --- a/tests/ui/splat/splat-255-limit-pass.rs +++ b/tests/ui/splat/splat-255-limit-pass.rs @@ -9,8 +9,28 @@ type A = (); -// These functions are deliberately formatted with 17 arguments in 15 lines, to show they have 255 -// arguments. +// These types and functions are deliberately formatted with 17 arguments in 15 lines, to show they +// have ~255 arguments. +#[rustfmt::skip] +type Tuple256 = ( + A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, + A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, + A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, + A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, + A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, + A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, + A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, + A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, + A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, + A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, + A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, + A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, + A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, + A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, + A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, + A, +); + #[rustfmt::skip] fn s_253_terminal( _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, @@ -132,6 +152,7 @@ fn s_0_initial_255_args( _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, _: A, ) {} +// It's only the splatted index that's constrained to 255, not the argument count of the caller or callee. #[rustfmt::skip] fn s_0_initial_256_args( #[splat] (_a, _b): (u32, i8), @@ -153,4 +174,28 @@ fn s_0_initial_256_args( _: A, ) {} -fn main() {} +fn more_than_255_splatted_args(#[splat] _t: Tuple256) {} + +fn main() { + let a = (); + + #[rustfmt::skip] + more_than_255_splatted_args( + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, + a, + ); +} From 4d9ae094fb1f588b75b297c735bc0f2c2c070fc5 Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 26 Jun 2026 09:54:10 +0200 Subject: [PATCH 31/35] Fix output normtn in splat-fn-ptr-tuple.rs --- tests/ui/splat/splat-fn-ptr-tuple.rs | 5 +++-- tests/ui/splat/splat-fn-ptr-tuple.stderr | 3 +-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/ui/splat/splat-fn-ptr-tuple.rs b/tests/ui/splat/splat-fn-ptr-tuple.rs index b4a6a3beae58f..297dbc0457794 100644 --- a/tests/ui/splat/splat-fn-ptr-tuple.rs +++ b/tests/ui/splat/splat-fn-ptr-tuple.rs @@ -1,13 +1,14 @@ //@ failure-status: 101 -//@ normalize-stderr: "(.*)internal compiler error:([^:]+):\d{1,}:\d{1,}:(.*)" -> "$1internal compiler error:$2:LL:CC:$3" -//@ normalize-stderr: "thread.*panicked at compiler.*" -> "" +//@ normalize-stderr: ".*error:.*compiler/([^:]+):\d{1,}:\d{1,}:(.*)" -> "error: compiler/$1:LL:CC:$2" +//@ normalize-stderr: "thread.*panicked at .*compiler.*" -> "" //@ normalize-stderr: "note: rustc.*running on.*" -> "note: rustc {version} running on {platform}" //@ normalize-stderr: "note: compiler flags.*\n\n" -> "" //@ normalize-stderr: " +\d{1,}: .*\n" -> "" //@ normalize-stderr: " + at .*\n" -> "" //@ normalize-stderr: ".*omitted \d{1,} frames?.*\n" -> "" //@ normalize-stderr: ".*note: Some details are omitted.*\n" -> "" +//@ normalize-stderr: ".*--> .*/splat-fn-ptr-tuple.rs:\d{1,}:\d{1,}.*\n" -> "" //! Test using `#[splat]` on tuple arguments of simple functions. //! Currently ICEs, but if we fix it, we'll want to know and update this test to pass. diff --git a/tests/ui/splat/splat-fn-ptr-tuple.stderr b/tests/ui/splat/splat-fn-ptr-tuple.stderr index 700d7639241a3..a69175bd5e886 100644 --- a/tests/ui/splat/splat-fn-ptr-tuple.stderr +++ b/tests/ui/splat/splat-fn-ptr-tuple.stderr @@ -1,5 +1,4 @@ - compiler error: compiler/rustc_mir_build/src/thir/cx/expr.rs:LL:CC: no splatted def for function or method callee - --> $DIR/splat-fn-ptr-tuple.rs:29:5 +error: compiler/rustc_mir_build/src/thir/cx/expr.rs:LL:CC: no splatted def for function or method callee | LL | fn_ptr(1, 2); | ^^^^^^^^^^^^ From 5d41c454727a71f2d962101bd590d209eaf2b2b0 Mon Sep 17 00:00:00 2001 From: Manuel Drehwald Date: Fri, 26 Jun 2026 14:54:55 +0200 Subject: [PATCH 32/35] Update Enzyme submodule --- src/tools/enzyme | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/enzyme b/src/tools/enzyme index 7c0141f133a35..a8668c7ca3579 160000 --- a/src/tools/enzyme +++ b/src/tools/enzyme @@ -1 +1 @@ -Subproject commit 7c0141f133a3592daa12cc6cc07f297a5222a42e +Subproject commit a8668c7ca3579c3304d628bca518bafc0fcc62d1 From b049d2bc02eae9506e200c1c7e7d60876f50b050 Mon Sep 17 00:00:00 2001 From: Adwin White Date: Wed, 24 Jun 2026 16:18:11 +0800 Subject: [PATCH 33/35] make use of rigidness marker in fast_reject --- compiler/rustc_type_ir/src/fast_reject.rs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_type_ir/src/fast_reject.rs b/compiler/rustc_type_ir/src/fast_reject.rs index 14ebffb70b8bf..097527a23703b 100644 --- a/compiler/rustc_type_ir/src/fast_reject.rs +++ b/compiler/rustc_type_ir/src/fast_reject.rs @@ -256,13 +256,12 @@ impl { + ty::Param(_) | ty::Alias(ty::IsRigid::Yes, _) => { if INSTANTIATE_RHS_WITH_INFER { return true; } } - // FIXME(#155345): we should fast-path for rigid aliases here. - ty::Error(_) | ty::Alias(..) | ty::Bound(..) => return true, + ty::Error(_) | ty::Alias(ty::IsRigid::No, _) | ty::Bound(..) => return true, ty::Infer(var) => return self.var_and_ty_may_unify(var, lhs), // These types only unify with inference variables or their own @@ -339,12 +338,22 @@ impl self.var_and_ty_may_unify(var, rhs), + // Since we ensure that the rhs is not non-rigid alias, + // lhs rigid alias can only unify with it if it's a rigid alias of the same kind. + ty::Alias(ty::IsRigid::Yes, lhs_alias) => { + INSTANTIATE_LHS_WITH_INFER + || match rhs.kind() { + ty::Alias(ty::IsRigid::Yes, rhs_alias) => { + lhs_alias.kind == rhs_alias.kind + && self.args_may_unify_inner(lhs_alias.args, rhs_alias.args, depth) + } + _ => false, + } + } // As we're walking the whole type, it may encounter projections // inside of binders and what not, so we're just going to assume that - // projections can unify with other stuff. - // - // Looking forward to lazy normalization this is the safer strategy anyways. - ty::Alias(..) => true, + // non-rigid alias can unify with anything. + ty::Alias(ty::IsRigid::No, _) => true, ty::Int(_) | ty::Uint(_) From 8b90f5c45e247662d6cf9bd64d9b2e84988feb16 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 26 Jun 2026 15:51:44 +0200 Subject: [PATCH 34/35] Remove `FIXME` comment that is not needed anymore --- library/core/src/fmt/num_buffer.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/library/core/src/fmt/num_buffer.rs b/library/core/src/fmt/num_buffer.rs index af8acda6633c5..00170346289cd 100644 --- a/library/core/src/fmt/num_buffer.rs +++ b/library/core/src/fmt/num_buffer.rs @@ -74,7 +74,6 @@ impl NumBuffer { #[stable(feature = "int_format_into", since = "CURRENT_RUSTC_VERSION")] #[rustc_const_stable(feature = "int_format_into", since = "CURRENT_RUSTC_VERSION")] pub const fn new() -> Self { - // FIXME: Once const generics feature is working, use `T::BUF_SIZE` instead of 40. NumBuffer { buf: T::DEFAULT, phantom: core::marker::PhantomData } } } From eb78d13769ac4ae9a9e1b324c7f46331085405ac Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 26 Jun 2026 16:05:13 +0200 Subject: [PATCH 35/35] Replace `ilog(10)` with `ilog10()` --- library/core/src/fmt/num.rs | 2 +- library/core/src/fmt/num_buffer.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/library/core/src/fmt/num.rs b/library/core/src/fmt/num.rs index 6c639a8b092b3..050822da8f12a 100644 --- a/library/core/src/fmt/num.rs +++ b/library/core/src/fmt/num.rs @@ -361,7 +361,7 @@ macro_rules! impl_Display { #[cfg(feature = "optimize_for_size")] fn ${concat($fmt_fn, _small)}(n: $T, is_nonnegative: bool, f: &mut fmt::Formatter<'_>) -> fmt::Result { - const MAX_DEC_N: usize = $T::MAX.ilog(10) as usize + 1; + const MAX_DEC_N: usize = $T::MAX.ilog10() as usize + 1; let mut buf = [MaybeUninit::::uninit(); MAX_DEC_N]; let offset = ${concat($fmt_fn, _in_buf_small)}(n, &mut buf); diff --git a/library/core/src/fmt/num_buffer.rs b/library/core/src/fmt/num_buffer.rs index 00170346289cd..624a953062332 100644 --- a/library/core/src/fmt/num_buffer.rs +++ b/library/core/src/fmt/num_buffer.rs @@ -17,13 +17,13 @@ macro_rules! impl_NumBufferTrait { #[stable(feature = "int_format_into", since = "CURRENT_RUSTC_VERSION")] impl NumBufferTrait for $signed { // `+ 2` and not `+ 1` to include the `-` character. - const DEFAULT: Self::Buf = [MaybeUninit::::uninit(); $signed::MAX.ilog(10) as usize + 2]; - type Buf = [MaybeUninit; $signed::MAX.ilog(10) as usize + 2]; + const DEFAULT: Self::Buf = [MaybeUninit::::uninit(); $signed::MAX.ilog10() as usize + 2]; + type Buf = [MaybeUninit; $signed::MAX.ilog10() as usize + 2]; } #[stable(feature = "int_format_into", since = "CURRENT_RUSTC_VERSION")] impl NumBufferTrait for $unsigned { - const DEFAULT: Self::Buf = [MaybeUninit::::uninit(); $unsigned::MAX.ilog(10) as usize + 1]; - type Buf = [MaybeUninit; $unsigned::MAX.ilog(10) as usize + 1]; + const DEFAULT: Self::Buf = [MaybeUninit::::uninit(); $unsigned::MAX.ilog10() as usize + 1]; + type Buf = [MaybeUninit; $unsigned::MAX.ilog10() as usize + 1]; } )* }