From 0fbb6c950cb6d9f472f8570a9356fccf2479f41e Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 21 Jul 2026 13:05:02 +1000 Subject: [PATCH 1/3] Inline the splatted_callee function --- compiler/rustc_mir_build/src/thir/cx/expr.rs | 136 ++++++++----------- 1 file changed, 60 insertions(+), 76 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index 10dc59a8950fd..b70e820681811 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -1229,86 +1229,32 @@ 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 expr = if let Some(def_id) = def_id { - // We're calling a function via a FnDef, and its possibly generic type - 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 FnDef: 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, - // FIXME(156581): actually instantiate the binder correctly (turbofishing/fndef changes) - ty: Ty::new_fn_def( - self.tcx, - def_id, - ty::Binder::dummy(self.typeck_results.node_args(expr.hir_id)), - ), - span, - kind: ExprKind::ZstLiteral { user_ty }, - } - } else { - // We're calling a function via a FnPtr and its type - // FIXME(splat): populate the side-tables for FnPtrs, using liberated_fn_sigs if needed - let fn_ty = self.typeck_results.expr_ty_adjusted(expr); - let user_ty = - self.typeck_results.user_provided_types().get(expr.hir_id).copied().map(Box::new); - debug!( - "splatted_callee FnPtr: user_ty={:?} fn_ty={:?} arg_index={:?} arg_count={:?}", - user_ty, fn_ty, arg_index, arg_count, - ); - - if !fn_ty.is_fn() { - span_bug!(expr.span, "splatted FnPtr side-tables are not yet implemented") - } - - Expr { - temp_scope_id: expr.hir_id.local_id, - // Create a new FnPtr FnSig type, representing the splatted function arguments with - // user-supplied generic types applied - ty: Ty::new_fn_ptr(self.tcx, fn_ty.fn_sig(self.tcx)), - span, - kind: ExprKind::ZstLiteral { user_ty }, - } - }; - - (expr, 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<'_>, + call_expr: &'tcx 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); + // Look up the typeck results + let splatted_def = + self.typeck_results.splatted_def(call_expr.hir_id).unwrap_or_else(|| { + span_bug!(call_expr.span, "no splatted def for function or method callee") + }); + + let tupled_arg_index = usize::from(splatted_def.arg_index); + let tupled_args_count = usize::from(splatted_def.arg_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, + call_expr.span, "splatted arg index out of bounds of function args: {:?} + {:?} > {:?} for function call: receiver {:?}, args {:?}", tupled_arg_index, tupled_args_count, @@ -1318,7 +1264,7 @@ impl<'tcx> ThirBuildCx<'tcx> { ); } - info!("Using splatted function span: {:?}", func.span); + debug!("Using splatted function span: {:?}", fn_span); // Split into non-tupled and tupled arguments let initial_non_tupled_args = @@ -1337,29 +1283,67 @@ impl<'tcx> ThirBuildCx<'tcx> { 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 }; + // We need the tupled arguments in HIR/MIR for type checking + // FIXME(splat): de-tuple args in codegen for performance let tupled_args = Expr { ty: Ty::new_tup_from_iter(tcx, tupled_arg_tys), - temp_scope_id, - span: expr.span, + temp_scope_id: call_expr.hir_id.local_id, + span: call_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![] }; + // Handle the receiver as the first arg, if present + let mut args = Vec::with_capacity( + usize::from(receiver.is_some()) + + initial_non_tupled_args.len() + + 1 + + final_non_tupled_args.len(), + ); + if let Some(receiver) = receiver { + args.push(self.mirror_expr(receiver)); + } 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 }; + let fn_span = if receiver.is_some() { fn_span } else { call_expr.span }; + + let (fn_ty, fun_expr) = match (splatted_def, receiver) { + // Create a FnDef shim for user-provided types + (SplattedDef { def_id: Some(def_id), arg_index, arg_count }, _) => { + // We're calling a function via a FnDef, and its possibly generic type + let def_kind = self.tcx.def_kind(def_id); + let user_ty = + self.user_args_applied_to_res(call_expr.hir_id, Res::Def(def_kind, def_id)); + debug!( + "splatted_callee FnDef: user_ty={:?} def_kind={:?} def_id={:?} arg_index={:?} arg_count={:?}", + user_ty, def_kind, def_id, arg_index, arg_count, + ); + + // Create a new FnDef expression with user-provided type applied + let callee_expr = Expr { + temp_scope_id: call_expr.hir_id.local_id, + // FIXME(156581): actually instantiate the binder correctly (turbofishing/fndef changes) + ty: Ty::new_fn_def( + self.tcx, + def_id, + ty::Binder::dummy(self.typeck_results.node_args(call_expr.hir_id)), + ), + span: fn_span, + kind: ExprKind::ZstLiteral { user_ty }, + }; + (callee_expr.ty, self.thir.exprs.push(callee_expr)) + } + (SplattedDef { def_id: None, .. }, _) => { + span_bug!(call_expr.span, "splatted FnPtr side-tables are not yet implemented"); + } + }; + ExprKind::Call { - ty: func.ty, - fun: self.thir.exprs.push(func), + ty: fn_ty, + fun: fun_expr, args: args.into_boxed_slice(), from_hir_call: true, fn_span, From bdbe148c983aae26761e1d496b869ad416b7c076 Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 28 Jul 2026 14:24:06 +1000 Subject: [PATCH 2/3] Refactor splat using custom enums (with stubs) --- compiler/rustc_hir_typeck/src/callee.rs | 26 +++++-- compiler/rustc_hir_typeck/src/expr.rs | 11 +-- .../rustc_hir_typeck/src/fn_ctxt/_impl.rs | 42 ++++++++---- .../rustc_hir_typeck/src/fn_ctxt/checks.rs | 68 +++++++++++-------- .../rustc_middle/src/ty/typeck_results.rs | 68 +++++++++++++++---- compiler/rustc_mir_build/src/thir/cx/expr.rs | 66 ++++++++++++++---- 6 files changed, 204 insertions(+), 77 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/callee.rs b/compiler/rustc_hir_typeck/src/callee.rs index 1ba299df23613..75af70024a6ff 100644 --- a/compiler/rustc_hir_typeck/src/callee.rs +++ b/compiler/rustc_hir_typeck/src/callee.rs @@ -31,6 +31,16 @@ use crate::method::TreatNotYetDefinedOpaques; use crate::method::confirm::ConfirmContext; use crate::method::probe::{IsSuggestion, Mode}; +/// Side-table info for lowering splatted function arguments. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub(crate) enum SplatLoweringInfo<'tcx> { + /// The DefId of the FnDef being called, used to look up the function type. + /// Also used during argument suggestion for non-splatted function calls. + FnDef(DefId), + /// FIXME(splat): Stub for non-FnDef + NotAFnDef(std::marker::PhantomData<&'tcx ()>), +} + /// Checks that it is legal to call methods of the trait corresponding /// to `trait_id` (this only cares about the trait, not the specific /// method that is called). @@ -600,13 +610,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); let fn_sig = self.normalize(call_expr.span, Unnormalized::new_wip(fn_sig)); + // Splatted FnDefs use the DefId to look up the type, FnPtrs need it directly + let fn_id = match def_id { + Some(x) => SplatLoweringInfo::FnDef(x), + None => SplatLoweringInfo::NotAFnDef(std::marker::PhantomData), + }; + self.check_argument_types_maybe_method_like( &fn_sig, call_expr, arg_exprs, expected, TupleArgumentsFlag::with_fn_sig_kind(fn_sig.fn_sig_kind, false), - def_id, + fn_id, callee_generic_args, ); @@ -643,7 +659,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { arg_exprs: &'tcx [hir::Expr<'tcx>], expected: Expectation<'tcx>, tuple_arguments_flag: TupleArgumentsFlag, - def_id: Option, + fn_id: SplatLoweringInfo<'tcx>, callee_generic_args: Option>, ) { let do_check = || { @@ -656,7 +672,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { arg_exprs, fn_sig.c_variadic(), tuple_arguments_flag, - def_id, + fn_id, callee_generic_args, ); }; @@ -1074,7 +1090,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { arg_exprs, fn_sig.fn_sig_kind.c_variadic(), TupleArgumentsFlag::rust_fn_trait_call(), - Some(closure_def_id.to_def_id()), + SplatLoweringInfo::FnDef(closure_def_id.to_def_id()), None, ); @@ -1172,7 +1188,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { arg_exprs, method.sig.fn_sig_kind.c_variadic(), TupleArgumentsFlag::rust_fn_trait_call(), - Some(method.def_id), + SplatLoweringInfo::FnDef(method.def_id), None, ); diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 6ffbbed8fd642..75518183638b9 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -39,6 +39,7 @@ use rustc_trait_selection::traits::{self, ObligationCauseCode, ObligationCtxt}; use tracing::{debug, instrument, trace}; use crate::Expectation::{self, ExpectCastableToType, ExpectHasType, NoExpectation}; +use crate::callee::SplatLoweringInfo; use crate::coercion::CoerceMany; use crate::diagnostics::{ AddressOfTemporaryTaken, BaseExpressionDoubleDot, BaseExpressionDoubleDotAddExpr, @@ -1479,7 +1480,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { args, method.sig.fn_sig_kind.c_variadic(), method_tuple_args_flag, - Some(method.def_id), + SplatLoweringInfo::FnDef(method.def_id), Some(method.args), ); @@ -1491,22 +1492,22 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let guar = self.report_method_error(expr.hir_id, rcvr_t, error, expected, false); let err_inputs = self.err_args(args.len(), guar); - let err_output = Ty::new_error(self.tcx, guar); + let err_ty = Ty::new_error(self.tcx, guar); self.check_argument_types( segment.ident.span, expr, &err_inputs, - err_output, + err_ty, NoExpectation, args, false, TupleArgumentsFlag::DontTupleArguments, - None, + SplatLoweringInfo::NotAFnDef(std::marker::PhantomData), Some(GenericArgsRef::default()), ); - err_output + err_ty } } } diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs index b413d0c0bb2de..0886b2841b3a4 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs @@ -41,7 +41,7 @@ use rustc_trait_selection::traits::{ }; use tracing::{debug, instrument}; -use crate::callee::{self, DeferredCallResolution}; +use crate::callee::{self, DeferredCallResolution, SplatLoweringInfo}; use crate::diagnostics::{self, CtorIsPrivate}; use crate::method::{self, MethodCallee}; use crate::{BreakableCtxt, Diverges, Expectation, FnCtxt, LoweredTy}; @@ -238,7 +238,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { pub(crate) fn write_splatted_resolution( &self, hir_id: HirId, - r: Result, + r: Result, ErrorGuaranteed>, ) { self.typeck_results.borrow_mut().splatted_defs_mut().insert(hir_id, r); } @@ -260,7 +260,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { &self, hir_id: HirId, span: Span, - callee_def_id: Option, + fn_id: SplatLoweringInfo<'tcx>, callee_generic_args: Option>, first_tupled_arg_index: u16, tupled_args_count: u16, @@ -268,16 +268,32 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // 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, - 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); + match fn_id { + // We're splatting a FnDef based on its DefId + SplatLoweringInfo::FnDef(def_id) => { + self.write_splatted_resolution( + hir_id, + Ok(SplattedDef::FnDef { + 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); + } + } + // FIXME(splat): handle FnPtrs + SplatLoweringInfo::NotAFnDef(_) => { + self.write_splatted_resolution( + hir_id, + Ok(SplattedDef::NotAFnDef { + not_yet_implemented: std::marker::PhantomData, + arg_index: first_tupled_arg_index, + arg_count: tupled_args_count, + }), + ); + } } } diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index c76febd539ef8..2c8bb9f78981f 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -31,6 +31,7 @@ use tracing::debug; use crate::Expectation::*; use crate::TupleArgumentsFlag::*; +use crate::callee::SplatLoweringInfo; use crate::coercion::CoerceMany; use crate::diagnostics::SuggestPtrNullMut; use crate::fn_ctxt::arg_matrix::{ArgMatrix, Compatibility, Error, ExpectedIdx, ProvidedIdx}; @@ -202,8 +203,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { c_variadic: bool, // 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, + // Lowering info if a splatted function is being called. + fn_id: SplatLoweringInfo<'tcx>, // The generics of the function being called. Only used for splatting callee_generic_args: Option>, ) { @@ -300,7 +301,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { provided_args, expected_input_tys, tuple_arguments, - fn_def_id, + fn_id, callee_generic_args, ); let TupledArgCheckOutcome { @@ -551,7 +552,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { provided_args, c_variadic, err_code, - fn_def_id, + fn_id, call_span, call_expr, tuple_arguments, @@ -574,8 +575,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { mut expected_input_tys: Option>>, // 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, + // Lowering info if a splatted function is being called. + fn_id: SplatLoweringInfo<'tcx>, // The generics of the function being called. Only used for splatting callee_generic_args: Option>, ) -> TupledArgCheckOutcome<'tcx> { @@ -735,7 +736,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // 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 spans = if let SplatLoweringInfo::FnDef(def_id) = fn_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_usz) @@ -796,7 +797,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.write_splatted_call( call_expr.hir_id, call_span, - fn_def_id, + fn_id, callee_generic_args, first_tupled_arg_index, tupled_args_count.unwrap().try_into().unwrap(), @@ -833,7 +834,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { provided_args: IndexVec>, c_variadic: bool, err_code: ErrCode, - fn_def_id: Option, + // Lowering info if a splatted function is being called. + fn_id: SplatLoweringInfo<'tcx>, call_span: Span, call_expr: &'tcx hir::Expr<'tcx>, // FIXME(splat): when the feature design is settled, improve the errors here @@ -848,7 +850,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { provided_args, c_variadic, err_code, - fn_def_id, + fn_id, call_span, call_expr, tuple_arguments, @@ -922,7 +924,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Call out where the function is defined fn_call_diag_ctxt.label_fn_like( &mut err, - fn_def_id, + fn_id, fn_call_diag_ctxt.callee_ty, call_expr, None, @@ -1592,7 +1594,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fn label_fn_like( &self, err: &mut Diag<'_>, - callable_def_id: Option, + // Lowering info if a splatted function is being called. + callable_id: SplatLoweringInfo<'tcx>, callee_ty: Option>, call_expr: &'tcx hir::Expr<'tcx>, expected_ty: Option>, @@ -1603,7 +1606,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { is_method: bool, tuple_arguments: TupleArgumentsFlag, ) { - let Some(mut def_id) = callable_def_id else { + let SplatLoweringInfo::FnDef(mut def_id) = callable_id else { + // FIXME(FnPtr, splat): Handle FnPtr types and splatting here return; }; @@ -1942,14 +1946,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fn label_generic_mismatches( &self, err: &mut Diag<'_>, - callable_def_id: Option, + // Lowering info if a splatted function is being called. + callable_id: SplatLoweringInfo<'tcx>, matched_inputs: &IndexVec>, 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 { + let SplatLoweringInfo::FnDef(def_id) = callable_id else { + // FIXME(FnPtr, splat): Handle FnPtr types and splatting here return; }; @@ -2186,7 +2192,8 @@ impl<'a, 'tcx> FnCallDiagCtxt<'a, 'tcx> { provided_args: IndexVec>, c_variadic: bool, err_code: ErrCode, - fn_def_id: Option, + // Lowering info if a splatted function is being called. + fn_id: SplatLoweringInfo<'tcx>, call_span: Span, call_expr: &'tcx Expr<'tcx>, tuple_arguments: TupleArgumentsFlag, @@ -2198,7 +2205,7 @@ impl<'a, 'tcx> FnCallDiagCtxt<'a, 'tcx> { provided_args, c_variadic, err_code, - fn_def_id, + fn_id, call_span, call_expr, tuple_arguments, @@ -2309,7 +2316,7 @@ impl<'a, 'tcx> FnCallDiagCtxt<'a, 'tcx> { }; self.arg_matching_ctxt.args_ctxt.call_ctxt.fn_ctxt.label_fn_like( &mut err, - self.fn_def_id, + self.fn_id, self.callee_ty, self.call_expr, None, @@ -2467,7 +2474,7 @@ impl<'a, 'tcx> FnCallDiagCtxt<'a, 'tcx> { // Call out where the function is defined self.label_fn_like( &mut err, - self.fn_def_id, + self.fn_id, self.callee_ty, self.call_expr, Some(expected_ty), @@ -2886,7 +2893,7 @@ impl<'a, 'tcx> FnCallDiagCtxt<'a, 'tcx> { fn label_generic_mismatches(&self, err: &mut Diag<'a>) { self.fn_ctxt.label_generic_mismatches( err, - self.fn_def_id, + self.fn_id, &self.matched_inputs, &self.provided_arg_tys, &self.formal_and_expected_inputs, @@ -3081,7 +3088,8 @@ impl<'a, 'tcx> ArgMatchingCtxt<'a, 'tcx> { provided_args: IndexVec>, c_variadic: bool, err_code: ErrCode, - fn_def_id: Option, + // Lowering info if a splatted function is being called. + fn_id: SplatLoweringInfo<'tcx>, call_span: Span, call_expr: &'tcx Expr<'tcx>, tuple_arguments: TupleArgumentsFlag, @@ -3093,7 +3101,7 @@ impl<'a, 'tcx> ArgMatchingCtxt<'a, 'tcx> { provided_args, c_variadic, err_code, - fn_def_id, + fn_id, call_span, call_expr, tuple_arguments, @@ -3228,7 +3236,8 @@ impl<'a, 'tcx> ArgsCtxt<'a, 'tcx> { provided_args: IndexVec>, c_variadic: bool, err_code: ErrCode, - fn_def_id: Option, + // Lowering info if a splatted function is being called. + fn_id: SplatLoweringInfo<'tcx>, call_span: Span, call_expr: &'tcx Expr<'tcx>, tuple_arguments: TupleArgumentsFlag, @@ -3240,7 +3249,7 @@ impl<'a, 'tcx> ArgsCtxt<'a, 'tcx> { provided_args, c_variadic, err_code, - fn_def_id, + fn_id, call_span, call_expr, tuple_arguments, @@ -3347,7 +3356,8 @@ struct CallCtxt<'a, 'tcx> { provided_args: IndexVec>, c_variadic: bool, err_code: ErrCode, - fn_def_id: Option, + /// Lowering info if a splatted function is being called. + fn_id: SplatLoweringInfo<'tcx>, call_span: Span, call_expr: &'tcx hir::Expr<'tcx>, tuple_arguments: TupleArgumentsFlag, @@ -3371,7 +3381,8 @@ impl<'a, 'tcx> CallCtxt<'a, 'tcx> { provided_args: IndexVec>, c_variadic: bool, err_code: ErrCode, - fn_def_id: Option, + // Lowering info if a splatted function is being called. + fn_id: SplatLoweringInfo<'tcx>, call_span: Span, call_expr: &'tcx hir::Expr<'tcx>, tuple_arguments: TupleArgumentsFlag, @@ -3403,7 +3414,7 @@ impl<'a, 'tcx> CallCtxt<'a, 'tcx> { provided_args, c_variadic, err_code, - fn_def_id, + fn_id, call_span, call_expr, tuple_arguments, @@ -3490,7 +3501,7 @@ impl<'a, 'tcx> CallCtxt<'a, 'tcx> { "()".to_string() } else if ty.is_suggestable(self.tcx, false) { format!("/* {ty} */") - } else if let Some(fn_def_id) = self.fn_def_id + } else if let SplatLoweringInfo::FnDef(fn_def_id) = self.fn_id && self.tcx.def_kind(fn_def_id).is_fn_like() && let self_implicit = matches!(self.call_expr.kind, hir::ExprKind::MethodCall(..)) as usize @@ -3500,6 +3511,7 @@ impl<'a, 'tcx> CallCtxt<'a, 'tcx> { { format!("/* {} */", arg.name) } else { + // FIXME(FnPtr, splat): What suggestions are needed for FnPtrs? "/* value */".to_string() } } diff --git a/compiler/rustc_middle/src/ty/typeck_results.rs b/compiler/rustc_middle/src/ty/typeck_results.rs index e11bc6d38495b..bca3ee7eac20b 100644 --- a/compiler/rustc_middle/src/ty/typeck_results.rs +++ b/compiler/rustc_middle/src/ty/typeck_results.rs @@ -37,7 +37,7 @@ pub struct TypeckResults<'tcx> { type_dependent_defs: ItemLocalMap>, /// Resolved definitions for splatted function calls. - splatted_defs: ItemLocalMap>, + splatted_defs: ItemLocalMap, ErrorGuaranteed>>, /// 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 @@ -291,18 +291,20 @@ impl<'tcx> TypeckResults<'tcx> { LocalTableInContextMut { hir_owner: self.hir_owner, data: &mut self.type_dependent_defs } } - pub fn splatted_defs(&self) -> LocalTableInContext<'_, Result> { + pub fn splatted_defs( + &self, + ) -> LocalTableInContext<'_, Result, ErrorGuaranteed>> { LocalTableInContext { hir_owner: self.hir_owner, data: &self.splatted_defs } } - pub fn splatted_def(&self, id: HirId) -> Option { + 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<'_, Result, ErrorGuaranteed>> { LocalTableInContextMut { hir_owner: self.hir_owner, data: &mut self.splatted_defs } } @@ -427,7 +429,7 @@ impl<'tcx> TypeckResults<'tcx> { } pub fn is_splatted_call(&self, expr: &hir::Expr<'_>) -> bool { - matches!(self.splatted_defs().get(expr.hir_id), Some(Ok(SplattedDef { .. }))) + matches!(self.splatted_defs().get(expr.hir_id), Some(Ok(_))) } /// Returns the computed binding mode for a `PatKind::Binding` pattern @@ -594,14 +596,54 @@ 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, +pub enum SplattedDef<'tcx> { + /// A resolved FnDef call. + FnDef { + /// The DefId of the FnDef (used to look up its type). + def_id: DefId, + + /// The index of the first argument in the callee's splatted tuple, and the index of the + /// splatted tuple argument in the caller. + arg_index: u16, + + /// The number of arguments in the splatted tuple. + arg_count: u16, + }, + + /// FIXME(splat): handle FnPtrs + NotAFnDef { + not_yet_implemented: std::marker::PhantomData<&'tcx ()>, + + /// The index of the first argument in the callee's splatted tuple, and the index of the + /// splatted tuple argument in the caller. + arg_index: u16, + + /// The number of arguments in the splatted tuple. + arg_count: u16, + }, +} + +impl<'tcx> SplattedDef<'tcx> { + pub fn def_id(&self) -> Option { + match self { + SplattedDef::FnDef { def_id, .. } => Some(*def_id), + SplattedDef::NotAFnDef { .. } => None, + } + } + + pub fn arg_index(&self) -> u16 { + match self { + SplattedDef::FnDef { arg_index, .. } => *arg_index, + SplattedDef::NotAFnDef { arg_index, .. } => *arg_index, + } + } + + pub fn arg_count(&self) -> u16 { + match self { + SplattedDef::FnDef { arg_count, .. } => *arg_count, + SplattedDef::NotAFnDef { arg_count, .. } => *arg_count, + } + } } /// Validate that the given HirId (respectively its `local_id` part) can be diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index b70e820681811..def70f1506770 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -28,6 +28,36 @@ use tracing::{debug, info, instrument, trace}; use crate::diagnostics::*; use crate::thir::cx::ThirBuildCx; +/// The receiver of a splatted method, or the expression for a splatted function call. +#[derive(Copy, Clone, Debug)] +enum SplattedFunc<'tcx> { + /// The expression for a method receiver. Always a FnDef. + FnDefReceiver(&'tcx hir::Expr<'tcx>), + /// The expression or path for a function call. + /// This can be a FnDef or FnPtr. + FnExpression(&'tcx hir::Expr<'tcx>), +} + +impl<'tcx> SplattedFunc<'tcx> { + fn has_receiver(&self) -> bool { + matches!(self, SplattedFunc::FnDefReceiver(_)) + } + + fn receiver(&self) -> Option<&'tcx hir::Expr<'tcx>> { + match self { + SplattedFunc::FnDefReceiver(receiver) => Some(receiver), + SplattedFunc::FnExpression(_fn_expression) => None, + } + } + + fn fn_expression(&self) -> Option<&'tcx hir::Expr<'tcx>> { + match self { + SplattedFunc::FnDefReceiver(_receiver) => None, + SplattedFunc::FnExpression(fn_expression) => Some(fn_expression), + } + } +} + fn parsed_attrs(id: HirId, tcx: TyCtxt<'_>) -> ThinVec { HasAttrs::get_attrs(id, &tcx) .into_iter() @@ -376,7 +406,12 @@ impl<'tcx> ThirBuildCx<'tcx> { 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)) + self.convert_splatted_callee( + expr, + fn_span, + args, + SplattedFunc::FnDefReceiver(receiver), + ) } else { // Rewrite a.b(c) into UFCS form like Trait::b(a, c) let expr = self.method_callee(expr, segment.ident.span, None); @@ -426,7 +461,12 @@ impl<'tcx> ThirBuildCx<'tcx> { } 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) + self.convert_splatted_callee( + expr, + fun.span, + args, + SplattedFunc::FnExpression(fun), + ) } 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 @@ -1237,7 +1277,7 @@ impl<'tcx> ThirBuildCx<'tcx> { call_expr: &'tcx hir::Expr<'_>, fn_span: Span, args: &'tcx [hir::Expr<'tcx>], - receiver: Option<&'tcx hir::Expr<'tcx>>, + receiver_or_func: SplattedFunc<'tcx>, ) -> ExprKind<'tcx> { let tcx = self.tcx; @@ -1247,19 +1287,19 @@ impl<'tcx> ThirBuildCx<'tcx> { span_bug!(call_expr.span, "no splatted def for function or method callee") }); - let tupled_arg_index = usize::from(splatted_def.arg_index); - let tupled_args_count = usize::from(splatted_def.arg_count); + let tupled_arg_index = usize::from(splatted_def.arg_index()); + let tupled_args_count = usize::from(splatted_def.arg_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!( call_expr.span, - "splatted arg index out of bounds of function args: {:?} + {:?} > {:?} for function call: receiver {:?}, args {:?}", + "splatted arg index out of bounds of function args: {:?} + {:?} > {:?} for function call: {:?}, args {:?}", tupled_arg_index, tupled_args_count, args.len(), - receiver, + receiver_or_func, args, ); } @@ -1296,23 +1336,23 @@ impl<'tcx> ThirBuildCx<'tcx> { // Handle the receiver as the first arg, if present let mut args = Vec::with_capacity( - usize::from(receiver.is_some()) + usize::from(receiver_or_func.has_receiver()) + initial_non_tupled_args.len() + 1 + final_non_tupled_args.len(), ); - if let Some(receiver) = receiver { + if let Some(receiver) = receiver_or_func.receiver() { args.push(self.mirror_expr(receiver)); } args.extend(initial_non_tupled_args); args.push(tupled_args); args.extend(final_non_tupled_args); - let fn_span = if receiver.is_some() { fn_span } else { call_expr.span }; + let fn_span = if receiver_or_func.has_receiver() { fn_span } else { call_expr.span }; - let (fn_ty, fun_expr) = match (splatted_def, receiver) { + let (fn_ty, fun_expr) = match (splatted_def, receiver_or_func.fn_expression()) { // Create a FnDef shim for user-provided types - (SplattedDef { def_id: Some(def_id), arg_index, arg_count }, _) => { + (SplattedDef::FnDef { def_id, arg_index, arg_count }, _) => { // We're calling a function via a FnDef, and its possibly generic type let def_kind = self.tcx.def_kind(def_id); let user_ty = @@ -1336,7 +1376,7 @@ impl<'tcx> ThirBuildCx<'tcx> { }; (callee_expr.ty, self.thir.exprs.push(callee_expr)) } - (SplattedDef { def_id: None, .. }, _) => { + (SplattedDef::NotAFnDef { not_yet_implemented: _, .. }, _) => { span_bug!(call_expr.span, "splatted FnPtr side-tables are not yet implemented"); } }; From 10a3f33a7771bb3d0603c8c9db86b057d9c2756d Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 28 Jul 2026 15:01:22 +1000 Subject: [PATCH 3/3] Make splatted FnPtr calls work (rather than ICE) Add tests for generic function pointers Change FnPtr tests to use assert_eq!() rather than println!() --- compiler/rustc_hir_typeck/src/callee.rs | 8 +- compiler/rustc_hir_typeck/src/expr.rs | 2 +- .../rustc_hir_typeck/src/fn_ctxt/_impl.rs | 20 ++- .../rustc_hir_typeck/src/fn_ctxt/checks.rs | 2 + .../rustc_middle/src/ty/typeck_results.rs | 20 ++- compiler/rustc_mir_build/src/thir/cx/expr.rs | 30 +++- tests/ui/splat/splat-fn-ptr-cast.rs | 5 +- tests/ui/splat/splat-fn-ptr-generic.rs | 57 ++++++++ tests/ui/splat/splat-fn-ptr-ptr-tuple.rs | 129 ++++++++++++++---- tests/ui/splat/splat-fn-ptr-ptr-tuple.stderr | 24 ---- tests/ui/splat/splat-fn-ptr-tuple-const.rs | 24 +--- .../ui/splat/splat-fn-ptr-tuple-const.stderr | 46 +------ tests/ui/splat/splat-fn-ptr-tuple-fail.rs | 18 +++ .../splat/splat-fn-ptr-tuple-fail.run.stderr | 3 + tests/ui/splat/splat-fn-ptr-tuple.rs | 73 +++++----- tests/ui/splat/splat-fn-ptr-tuple.stderr | 23 ---- 16 files changed, 289 insertions(+), 195 deletions(-) create mode 100644 tests/ui/splat/splat-fn-ptr-generic.rs delete mode 100644 tests/ui/splat/splat-fn-ptr-ptr-tuple.stderr create mode 100644 tests/ui/splat/splat-fn-ptr-tuple-fail.rs create mode 100644 tests/ui/splat/splat-fn-ptr-tuple-fail.run.stderr delete mode 100644 tests/ui/splat/splat-fn-ptr-tuple.stderr diff --git a/compiler/rustc_hir_typeck/src/callee.rs b/compiler/rustc_hir_typeck/src/callee.rs index 75af70024a6ff..ff4ef21ce87c9 100644 --- a/compiler/rustc_hir_typeck/src/callee.rs +++ b/compiler/rustc_hir_typeck/src/callee.rs @@ -37,8 +37,10 @@ pub(crate) enum SplatLoweringInfo<'tcx> { /// The DefId of the FnDef being called, used to look up the function type. /// Also used during argument suggestion for non-splatted function calls. FnDef(DefId), - /// FIXME(splat): Stub for non-FnDef - NotAFnDef(std::marker::PhantomData<&'tcx ()>), + /// The type of the FnPtr being called. + FnPtr(Ty<'tcx>), + /// Type resolution errored. + Error(ErrorGuaranteed), } /// Checks that it is legal to call methods of the trait corresponding @@ -613,7 +615,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Splatted FnDefs use the DefId to look up the type, FnPtrs need it directly let fn_id = match def_id { Some(x) => SplatLoweringInfo::FnDef(x), - None => SplatLoweringInfo::NotAFnDef(std::marker::PhantomData), + None => SplatLoweringInfo::FnPtr(callee_ty), }; self.check_argument_types_maybe_method_like( diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 75518183638b9..ca51e4287c87a 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -1503,7 +1503,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { args, false, TupleArgumentsFlag::DontTupleArguments, - SplatLoweringInfo::NotAFnDef(std::marker::PhantomData), + SplatLoweringInfo::Error(guar), Some(GenericArgsRef::default()), ); diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs index 0886b2841b3a4..8d0d5f67754ce 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs @@ -283,16 +283,28 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.write_args(hir_id, callee_generic_args); } } - // FIXME(splat): handle FnPtrs - SplatLoweringInfo::NotAFnDef(_) => { + // We're splatting a FnPtr based on its type + SplatLoweringInfo::FnPtr(fn_ty) => { + // FIXME(splat): do we need to look up both these HirIds? + // They can be different (and are different in some UI tests) self.write_splatted_resolution( hir_id, - Ok(SplattedDef::NotAFnDef { - not_yet_implemented: std::marker::PhantomData, + Ok(SplattedDef::FnPtr { + fn_ptr_type: fn_ty, arg_index: first_tupled_arg_index, arg_count: tupled_args_count, }), ); + // FIXME(splat): is this actually populated and used correctly? + if let Some(callee_generic_args) = callee_generic_args { + self.write_args(hir_id, callee_generic_args); + } + } + SplatLoweringInfo::Error(guar) => { + self.write_splatted_resolution(hir_id, Err(guar)); + 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/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index 2c8bb9f78981f..bc636697c861f 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -3512,6 +3512,8 @@ impl<'a, 'tcx> CallCtxt<'a, 'tcx> { format!("/* {} */", arg.name) } else { // FIXME(FnPtr, splat): What suggestions are needed for FnPtrs? + // SplatLoweringInfo::FnPtr(Ty) and SplatLoweringInfo::Error currently fall through to + // this placeholder "/* value */".to_string() } } diff --git a/compiler/rustc_middle/src/ty/typeck_results.rs b/compiler/rustc_middle/src/ty/typeck_results.rs index bca3ee7eac20b..3c78181276d50 100644 --- a/compiler/rustc_middle/src/ty/typeck_results.rs +++ b/compiler/rustc_middle/src/ty/typeck_results.rs @@ -610,9 +610,10 @@ pub enum SplattedDef<'tcx> { arg_count: u16, }, - /// FIXME(splat): handle FnPtrs - NotAFnDef { - not_yet_implemented: std::marker::PhantomData<&'tcx ()>, + /// A resolved FnPtr Call. + FnPtr { + /// The resolved type of the FnPtr. + fn_ptr_type: Ty<'tcx>, /// The index of the first argument in the callee's splatted tuple, and the index of the /// splatted tuple argument in the caller. @@ -627,21 +628,28 @@ impl<'tcx> SplattedDef<'tcx> { pub fn def_id(&self) -> Option { match self { SplattedDef::FnDef { def_id, .. } => Some(*def_id), - SplattedDef::NotAFnDef { .. } => None, + SplattedDef::FnPtr { .. } => None, + } + } + + pub fn fn_ptr_type(&self) -> Option> { + match self { + SplattedDef::FnDef { .. } => None, + SplattedDef::FnPtr { fn_ptr_type, .. } => Some(*fn_ptr_type), } } pub fn arg_index(&self) -> u16 { match self { SplattedDef::FnDef { arg_index, .. } => *arg_index, - SplattedDef::NotAFnDef { arg_index, .. } => *arg_index, + SplattedDef::FnPtr { arg_index, .. } => *arg_index, } } pub fn arg_count(&self) -> u16 { match self { SplattedDef::FnDef { arg_count, .. } => *arg_count, - SplattedDef::NotAFnDef { arg_count, .. } => *arg_count, + SplattedDef::FnPtr { arg_count, .. } => *arg_count, } } } diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index def70f1506770..3295b66536544 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -1376,8 +1376,34 @@ impl<'tcx> ThirBuildCx<'tcx> { }; (callee_expr.ty, self.thir.exprs.push(callee_expr)) } - (SplattedDef::NotAFnDef { not_yet_implemented: _, .. }, _) => { - span_bug!(call_expr.span, "splatted FnPtr side-tables are not yet implemented"); + + // We're calling a function via a FnPtr and its type + // FIXME(splat): do we need to populate and apply user_provided_types() ? + (SplattedDef::FnPtr { fn_ptr_type, arg_index, arg_count }, Some(fn_expression)) => { + debug!( + "splatted_callee FnPtr: fn_ty={:?} arg_index={:?} arg_count={:?}", + fn_ptr_type, arg_index, arg_count, + ); + + if !fn_ptr_type.is_fn() { + span_bug!( + call_expr.span, + "splatted FnPtr side-tables were not populated correctly, non-fn type received: {:?}", + fn_ptr_type + ) + } + + // Pass through the FnPtr type and the mirrored function path + (fn_ptr_type, self.mirror_expr(fn_expression)) + } + // FnPtrs must have a function expression (and they never have method receivers) + (SplattedDef::FnPtr { .. }, None) => { + span_bug!( + call_expr.span, + "convert_splatted_callee: FnPtr without fn expression (or with receiver) is invalid: splatted_def={:?}, receiver_or_func={:?}", + splatted_def, + receiver_or_func, + ); } }; diff --git a/tests/ui/splat/splat-fn-ptr-cast.rs b/tests/ui/splat/splat-fn-ptr-cast.rs index 918e26c4dd741..ef6610a8efd74 100644 --- a/tests/ui/splat/splat-fn-ptr-cast.rs +++ b/tests/ui/splat/splat-fn-ptr-cast.rs @@ -8,9 +8,8 @@ fn main() { // Bug #158603 regression test variants #[rustfmt::skip] - let _x: fn(#[splat] (f32,)) = None.unwrap(); - // FIXME(splat): causes an ICE until #158603 is fixed - //x(1.0); + let x: fn(#[splat] (f32,)) = None.unwrap(); + x(1.0); let x: fn((i32,)) = None.unwrap(); x((1,)); diff --git a/tests/ui/splat/splat-fn-ptr-generic.rs b/tests/ui/splat/splat-fn-ptr-generic.rs new file mode 100644 index 0000000000000..8494bb56c9b04 --- /dev/null +++ b/tests/ui/splat/splat-fn-ptr-generic.rs @@ -0,0 +1,57 @@ +//! Test using `#[splat]` on tuple arguments of pointers to generic functions. +//@ run-pass + +#![expect(incomplete_features)] +#![feature(splat, tuple_trait)] + +use std::fmt::Debug; +use std::marker::Tuple; + +fn generic(#[splat] a: T) -> String { + format!("{a:?}") +} + +// FIXME(rustfmt): the attribute gets deleted by rustfmt +#[rustfmt::skip] +fn main() { + let fn_ptr: fn(#[splat] (u32, i8)) -> String = generic as fn(#[splat] (u32, i8)) -> String; + assert_eq!(fn_ptr(1, -2), "(1, -2)"); + assert_eq!(fn_ptr(1u32, -2i8), "(1, -2)"); + + let fn_ptr: fn(#[splat] (u32, i8)) -> String + = generic::<(u32, i8)> as fn(#[splat] (u32, i8)) -> String; + assert_eq!(fn_ptr(1, -2), "(1, -2)"); + assert_eq!(fn_ptr(1u32, -2i8), "(1, -2)"); + + let fn_ptr = generic as fn(#[splat] (u32, i8)) -> String; + assert_eq!(fn_ptr(1, -2), "(1, -2)"); + assert_eq!(fn_ptr(1u32, -2i8), "(1, -2)"); + + let fn_ptr = generic::<(u32, i8)> as fn(#[splat] (u32, i8)) -> String; + assert_eq!(fn_ptr(1, -2), "(1, -2)"); + assert_eq!(fn_ptr(1u32, -2i8), "(1, -2)"); + + let fn_ptr: fn(#[splat] (u32, i8)) -> String = generic as _; + assert_eq!(fn_ptr(1, -2), "(1, -2)"); + assert_eq!(fn_ptr(1u32, -2i8), "(1, -2)"); + + let fn_ptr: fn(#[splat] (u32, i8)) -> String = generic::<(u32, i8)> as _; + assert_eq!(fn_ptr(1, -2), "(1, -2)"); + assert_eq!(fn_ptr(1u32, -2i8), "(1, -2)"); + + // Now without explicit `as`, this requires turbofish + let fn_ptr: fn(#[splat] (f64, i8)) -> String = generic::<(f64, i8)>; + assert_eq!(fn_ptr(3.5, -2), "(3.5, -2)"); + assert_eq!(fn_ptr(3.5f64, -2i8), "(3.5, -2)"); + + // FIXME(unused_variables): This is obviously used + #[expect(unused_variables)] + let fn_ptr = generic; + assert_eq!(fn_ptr(-1, 2, 3.5), "(-1, 2, 3.5)"); + assert_eq!(fn_ptr(-1i8, 2u32, 3.5f64), "(-1, 2, 3.5)"); + + #[expect(unused_variables)] + let fn_ptr = generic::<(i8, u32, f64)>; + assert_eq!(fn_ptr(-1, 2, 3.5), "(-1, 2, 3.5)"); + assert_eq!(fn_ptr(-1i8, 2u32, 3.5f64), "(-1, 2, 3.5)"); +} diff --git a/tests/ui/splat/splat-fn-ptr-ptr-tuple.rs b/tests/ui/splat/splat-fn-ptr-ptr-tuple.rs index 321511f270915..0127873b4d348 100644 --- a/tests/ui/splat/splat-fn-ptr-ptr-tuple.rs +++ b/tests/ui/splat/splat-fn-ptr-ptr-tuple.rs @@ -1,42 +1,113 @@ //! Test using `#[splat]` on tuple arguments of pointers to pointers to simple functions. -//! Currently ICEs, but if we fix it, we'll want to know and update this test to pass. +//! Bug #158603 regression test +//@ run-pass -//@ failure-status: 101 - -//@ 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" -> "" - -#![allow(incomplete_features)] +#![expect(incomplete_features)] #![feature(splat)] -fn tuple_args(#[splat] (_a, _b): (u32, i8)) {} +use std::ptr; + +fn tuple_args(#[splat] (a, b): (u32, i8)) -> (i8, u32) { + // Permute the returned values as a codegen test + (b, a) +} -fn splat_non_terminal_arg(#[splat] (_a, _b): (u32, i8), _c: f64) {} +fn splat_non_terminal_arg(#[splat] (a, b): (u32, i8), c: f64) -> (i8, f64, u32) { + // Permute the returned values as a codegen test + (b, c, a) +} +// FIXME(rustfmt): the attribute gets deleted by rustfmt +#[rustfmt::skip] 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 - #[rustfmt::skip] - let fn_pp: *const fn(#[splat] (u32, i8)) = tuple_args as *const fn(#[splat] (u32, i8)); + let fn_pp: &fn(#[splat] (u32, i8)) -> (i8, u32) + = &(tuple_args as fn(#[splat] (u32, i8)) -> (i8, u32)); + assert_eq!((*fn_pp)(1, 2), (2, 1)); + assert_eq!((*fn_pp)(1u32, 2i8), (2i8, 1u32)); + + let fn_pp: &fn(#[splat] (u32, i8)) -> (i8, u32) = &(tuple_args as _); + assert_eq!((*fn_pp)(1, 2), (2, 1)); + assert_eq!((*fn_pp)(1u32, 2i8), (2i8, 1u32)); + + let fn_pp = &(tuple_args as fn(#[splat] (u32, i8)) -> (i8, u32)); + assert_eq!((*fn_pp)(1, 2), (2, 1)); + assert_eq!((*fn_pp)(1u32, 2i8), (2i8, 1u32)); + + // FIXME(unused_variables): This is obviously used + #[expect(unused_variables)] + let fn_pp = &tuple_args; + assert_eq!((*fn_pp)(1, 2), (2, 1)); + assert_eq!((*fn_pp)(1u32, 2i8), (2i8, 1u32)); + + // Now with *const + let fn_pp: *const fn(#[splat] (u32, i8)) -> (i8, u32) + = ptr::from_ref(&(tuple_args as fn(#[splat] (u32, i8)) -> (i8, u32))); + unsafe { + assert_eq!((*fn_pp)(1, 2), (2, 1)); + assert_eq!((*fn_pp)(1u32, 2i8), (2i8, 1u32)); + } + + let fn_pp: *const fn(#[splat] (u32, i8)) -> (i8, u32) = ptr::from_ref(&(tuple_args as _)); + unsafe { + assert_eq!((*fn_pp)(1, 2), (2, 1)); + assert_eq!((*fn_pp)(1u32, 2i8), (2i8, 1u32)); + } + + let fn_pp = ptr::from_ref(&(tuple_args as fn(#[splat] (u32, i8)) -> (i8, u32))); + unsafe { + assert_eq!((*fn_pp)(1, 2), (2, 1)); + assert_eq!((*fn_pp)(1u32, 2i8), (2i8, 1u32)); + } + + #[expect(unused_variables)] + let fn_pp = ptr::from_ref(&tuple_args); + // FIXME(unsafe): dereferencing *const should require unsafe + assert_eq!((*fn_pp)(1, 2), (2, 1)); + assert_eq!((*fn_pp)(1u32, 2i8), (2i8, 1u32)); + + // Now with *mut and non-terminal splat + let fn_pp: *mut fn(#[splat] (u32, i8), f64) -> (i8, f64, u32) + = ptr::from_mut( + &mut (splat_non_terminal_arg as fn(#[splat] (u32, i8), f64) -> (i8, f64, u32)) + ); + unsafe { + assert_eq!((*fn_pp)(1, 2, 3.5), (2, 3.5, 1)); + assert_eq!((*fn_pp)(1u32, 2i8, 3.5f64), (2i8, 3.5f64, 1u32)); + } + + let fn_pp: *mut fn(#[splat] (u32, i8), f64) -> (i8, f64, u32) + = ptr::from_mut(&mut (splat_non_terminal_arg as _)); + unsafe { + assert_eq!((*fn_pp)(1, 2, 3.5), (2, 3.5, 1)); + assert_eq!((*fn_pp)(1u32, 2i8, 3.5f64), (2i8, 3.5f64, 1u32)); + } + + let fn_pp = ptr::from_mut( + &mut (splat_non_terminal_arg as fn(#[splat] (u32, i8), f64) -> (i8, f64, u32)) + ); + unsafe { + assert_eq!((*fn_pp)(1, 2, 3.5), (2, 3.5, 1)); + assert_eq!((*fn_pp)(1u32, 2i8, 3.5f64), (2i8, 3.5f64, 1u32)); + } + + #[expect(unused_variables)] + let fn_pp = ptr::from_mut(&mut splat_non_terminal_arg); + // FIXME(unsafe): dereferencing *mut should require unsafe + assert_eq!((*fn_pp)(1, 2, 3.5), (2, 3.5, 1)); + assert_eq!((*fn_pp)(1u32, 2i8, 3.5f64), (2i8, 3.5f64, 1u32)); + + // Now with & as *const and non-terminal splat + let fn_pp: *const fn(#[splat] (u32, i8), f64) -> (i8, f64, u32) + = &(splat_non_terminal_arg as fn(#[splat] (u32, i8), f64) -> (i8, f64, u32)); unsafe { - (*fn_pp)(1, 2); //~ ERROR splatted FnPtr side-tables are not yet implemented - // The ICE means that code after this line is not fully checked - (*fn_pp)(1u32, 2i8); + assert_eq!((*fn_pp)(1, 2, 3.5), (2, 3.5, 1)); + assert_eq!((*fn_pp)(1u32, 2i8, 3.5f64), (2i8, 3.5f64, 1u32)); } - #[rustfmt::skip] - let fn_pp: *const fn(#[splat] (u32, i8), f64) = - splat_non_terminal_arg as *const fn(#[splat] (u32, i8), f64); + let fn_pp: *const fn(#[splat] (u32, i8), f64) -> (i8, f64, u32) + = &(splat_non_terminal_arg as _); unsafe { - (*fn_pp)(1, 2, 3.5); - (*fn_pp)(1u32, 2i8, 3.5f64); + assert_eq!((*fn_pp)(1, 2, 3.5), (2, 3.5, 1)); + assert_eq!((*fn_pp)(1u32, 2i8, 3.5f64), (2i8, 3.5f64, 1u32)); } } diff --git a/tests/ui/splat/splat-fn-ptr-ptr-tuple.stderr b/tests/ui/splat/splat-fn-ptr-ptr-tuple.stderr deleted file mode 100644 index 726561b366f80..0000000000000 --- a/tests/ui/splat/splat-fn-ptr-ptr-tuple.stderr +++ /dev/null @@ -1,24 +0,0 @@ -error: compiler/rustc_mir_build/src/thir/cx/expr.rs:LL:CC: splatted FnPtr side-tables are not yet implemented - --> $DIR/splat-fn-ptr-ptr-tuple.rs:30:9 - | -LL | (*fn_pp)(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_ptr_tuple` -end of query stack -error: aborting due to 1 previous error - diff --git a/tests/ui/splat/splat-fn-ptr-tuple-const.rs b/tests/ui/splat/splat-fn-ptr-tuple-const.rs index faf8501cc650a..659f115a0221b 100644 --- a/tests/ui/splat/splat-fn-ptr-tuple-const.rs +++ b/tests/ui/splat/splat-fn-ptr-tuple-const.rs @@ -1,17 +1,4 @@ //! Test using `#[splat]` on tuple arguments of generic function constants. -//! Currently ICEs (#158603), but if we fix it, we'll want to know and update this test to pass. - -//@ failure-status: 101 - -//@ 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" -> "" #![allow(incomplete_features)] #![feature(splat, tuple_trait)] @@ -20,15 +7,12 @@ use std::marker::Tuple; fn f(#[splat] args: Args) {} +// FIXME(rustfmt): the attribute gets deleted by rustfmt +#[rustfmt::skip] 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 - #[rustfmt::skip] const F2: fn(#[splat] (u8, u32)) = f::<(u8, u32)>; - const R2: () = F2(1, 2); //~ ERROR splatted FnPtr side-tables are not yet implemented + const R2: () = F2(1, 2); //~ ERROR function pointer calls are not allowed in constants - #[rustfmt::skip] const F1: fn(#[splat] ((u8, u32),)) = f::<((u8, u32),)>; - const R1: () = F1((1, 2)); //~ ERROR splatted FnPtr side-tables are not yet implemented + const R1: () = F1((1, 2)); //~ ERROR function pointer calls are not allowed in constants } diff --git a/tests/ui/splat/splat-fn-ptr-tuple-const.stderr b/tests/ui/splat/splat-fn-ptr-tuple-const.stderr index 1767782a9535e..f4b033445b3b2 100644 --- a/tests/ui/splat/splat-fn-ptr-tuple-const.stderr +++ b/tests/ui/splat/splat-fn-ptr-tuple-const.stderr @@ -1,52 +1,14 @@ -error: compiler/rustc_mir_build/src/thir/cx/expr.rs:LL:CC: splatted FnPtr side-tables are not yet implemented - --> $DIR/splat-fn-ptr-tuple-const.rs:29:20 +error: function pointer calls are not allowed in constants + --> $DIR/splat-fn-ptr-tuple-const.rs:14:20 | LL | const R2: () = F2(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::R2` -#1 [check_match] match-checking `main::R2` -#2 [mir_built] building MIR for `main::R2` -#3 [trivial_const] checking if `main::R2` is a trivial const -#4 [eval_to_const_value_raw] simplifying constant for the type system `main::R2` -#5 [analysis] running analysis passes on crate `splat_fn_ptr_tuple_const` -end of query stack -error: compiler/rustc_mir_build/src/thir/cx/expr.rs:LL:CC: splatted FnPtr side-tables are not yet implemented - --> $DIR/splat-fn-ptr-tuple-const.rs:33:20 +error: function pointer calls are not allowed in constants + --> $DIR/splat-fn-ptr-tuple-const.rs:17:20 | LL | const R1: () = F1((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::R1` -#1 [check_match] match-checking `main::R1` -#2 [mir_built] building MIR for `main::R1` -#3 [trivial_const] checking if `main::R1` is a trivial const -#4 [eval_to_const_value_raw] simplifying constant for the type system `main::R1` -#5 [analysis] running analysis passes on crate `splat_fn_ptr_tuple_const` -end of query stack error: aborting due to 2 previous errors diff --git a/tests/ui/splat/splat-fn-ptr-tuple-fail.rs b/tests/ui/splat/splat-fn-ptr-tuple-fail.rs new file mode 100644 index 0000000000000..59ceefa7de427 --- /dev/null +++ b/tests/ui/splat/splat-fn-ptr-tuple-fail.rs @@ -0,0 +1,18 @@ +//! Test using `#[splat]` on tuple arguments of pointers to invalid simple functions. +//! Bug #158603 regression test +//@ run-fail +//@ check-run-results +//@ exec-env: RUST_BACKTRACE=0 + +//@ normalize-stderr: "thread '.*'" -> "thread 'NAME'" +//@ normalize-stderr: "note: run with.*\n" -> "" + +#![expect(incomplete_features)] +#![feature(splat)] + +fn main() { + // FIXME(rustfmt): the attribute gets deleted by rustfmt + #[rustfmt::skip] + let x: fn(#[splat] (i32,)) = None.unwrap(); + x(1); +} diff --git a/tests/ui/splat/splat-fn-ptr-tuple-fail.run.stderr b/tests/ui/splat/splat-fn-ptr-tuple-fail.run.stderr new file mode 100644 index 0000000000000..68a284cf4ec98 --- /dev/null +++ b/tests/ui/splat/splat-fn-ptr-tuple-fail.run.stderr @@ -0,0 +1,3 @@ + +thread 'NAME' ($TID) panicked at $DIR/splat-fn-ptr-tuple-fail.rs:16:39: +called `Option::unwrap()` on a `None` value diff --git a/tests/ui/splat/splat-fn-ptr-tuple.rs b/tests/ui/splat/splat-fn-ptr-tuple.rs index 395d30baedaaf..21f11f92269fc 100644 --- a/tests/ui/splat/splat-fn-ptr-tuple.rs +++ b/tests/ui/splat/splat-fn-ptr-tuple.rs @@ -1,46 +1,43 @@ //! Test using `#[splat]` on tuple arguments of pointers to simple functions. -//! Currently ICEs, but if we fix it, we'll want to know and update this test to pass. +//! Bug #158603 regression test +//@ run-pass -//@ failure-status: 101 - -//@ 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" -> "" - -#![allow(incomplete_features)] +#![expect(incomplete_features)] #![feature(splat)] -fn tuple_args(#[splat] (_a, _b): (u32, i8)) {} +fn tuple_args(#[splat] (a, b): (u32, i8)) -> (u32, i8) { + (a, b) +} -fn splat_non_terminal_arg(#[splat] (_a, _b): (u32, i8), _c: f64) {} +fn splat_non_terminal_arg(#[splat] (a, b): (u32, i8), c: f64) -> (f64, i8, u32) { + // Permute the returned values as a codegen test + (c, b, a) +} +// FIXME(rustfmt): the attribute gets deleted by rustfmt +#[rustfmt::skip] 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 - #[rustfmt::skip] - let fn_ptr: fn(#[splat] (u32, i8)) = tuple_args; - fn_ptr(1, 2); //~ ERROR splatted FnPtr side-tables are not yet implemented - // The ICE means that code after this line is not fully checked - 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); - - // Bug #158603 regression test - #[rustfmt::skip] - let x: fn(#[splat] (i32,)) = None.unwrap(); - x(1); + let fn_ptr: fn(#[splat] (u32, i8)) -> (u32, i8) + = tuple_args as fn(#[splat] (u32, i8)) -> (u32, i8); + assert_eq!(fn_ptr(1, 2), (1, 2)); + assert_eq!(fn_ptr(1u32, 2i8), (1u32, 2i8)); + + let fn_ptr = tuple_args as fn(#[splat] (u32, i8)) -> (u32, i8); + assert_eq!(fn_ptr(1, 2), (1, 2)); + assert_eq!(fn_ptr(1u32, 2i8), (1u32, 2i8)); + + let fn_ptr: fn(#[splat] (u32, i8)) -> (u32, i8) = tuple_args as _; + assert_eq!(fn_ptr(1, 2), (1, 2)); + assert_eq!(fn_ptr(1u32, 2i8), (1u32, 2i8)); + + // Now without explicit `as` + let fn_ptr: fn(#[splat] (u32, i8), f64) -> (f64, i8, u32) = splat_non_terminal_arg; + assert_eq!(fn_ptr(1, 2, 3.5), (3.5, 2, 1)); + assert_eq!(fn_ptr(1u32, 2i8, 3.5f64), (3.5f64, 2i8, 1u32)); + + // FIXME(unused_variables): This is obviously used + #[expect(unused_variables)] + let fn_ptr = splat_non_terminal_arg; + assert_eq!(fn_ptr(1, 2, 3.5), (3.5, 2, 1)); + assert_eq!(fn_ptr(1u32, 2i8, 3.5f64), (3.5f64, 2i8, 1u32)); } diff --git a/tests/ui/splat/splat-fn-ptr-tuple.stderr b/tests/ui/splat/splat-fn-ptr-tuple.stderr deleted file mode 100644 index 4cc861cafe968..0000000000000 --- a/tests/ui/splat/splat-fn-ptr-tuple.stderr +++ /dev/null @@ -1,23 +0,0 @@ -error: compiler/rustc_mir_build/src/thir/cx/expr.rs:LL:CC: splatted FnPtr side-tables are not yet implemented - | -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 -