diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index b105a97cdf98d..518610bf74f60 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -3054,9 +3054,30 @@ impl FnDecl { pub fn has_self(&self) -> bool { self.inputs.get(0).is_some_and(Param::is_self) } + pub fn c_variadic(&self) -> bool { self.inputs.last().is_some_and(|arg| matches!(arg.ty.kind, TyKind::CVarArgs)) } + + /// The marker index for "no splatted arguments". + /// 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; + + /// Returns a splatted argument index, if any are present. + pub fn splatted(&self) -> Option { + self.inputs.iter().enumerate().find_map(|(index, arg)| { + if index == Self::NO_SPLATTED_ARG_INDEX as usize { + // AST validation has already checked the splatted argument index is valid, so just + // ignore invalid indexes here. + None + } else { + arg.attrs + .iter() + .any(|attr| attr.has_name(sym::splat)) + .then_some(u16::try_from(index).unwrap()) + } + }) + } } /// Is the trait definition an auto trait? diff --git a/compiler/rustc_ast_lowering/src/delegation.rs b/compiler/rustc_ast_lowering/src/delegation.rs index d390934f3b172..88e5e353fe165 100644 --- a/compiler/rustc_ast_lowering/src/delegation.rs +++ b/compiler/rustc_ast_lowering/src/delegation.rs @@ -81,6 +81,19 @@ enum AttrAdditionKind { Inherit { factory: fn(Span, &hir::Attribute) -> hir::Attribute }, } +/// Summary info about function parameters. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +struct ParamInfo { + /// The number of function parameters, including any C variadic `...` parameter. + pub param_count: usize, + + /// Whether the function arguments end in a C variadic `...` parameter. + pub c_variadic: bool, + + /// The index of the splatted parameter, if any. + pub splatted: Option, +} + const PARENT_ID: hir::ItemLocalId = hir::ItemLocalId::ZERO; static ATTRS_ADDITIONS: &[AttrAdditionInfo] = &[ @@ -141,7 +154,7 @@ impl<'hir> LoweringContext<'_, 'hir> { let is_method = self.is_method(sig_id, span); - let (param_count, c_variadic) = self.param_count(sig_id); + let param_info = self.param_info(sig_id); let mut generics = self.uplift_delegation_generics(delegation, sig_id, item_id, is_method); @@ -149,13 +162,12 @@ impl<'hir> LoweringContext<'_, 'hir> { let body_id = self.lower_delegation_body( delegation, is_method, - param_count, + param_info.param_count, &mut generics, span, ); - let decl = - self.lower_delegation_decl(sig_id, param_count, c_variadic, span, &generics); + let decl = self.lower_delegation_decl(sig_id, param_info, span, &generics); let sig = self.lower_delegation_sig(sig_id, decl, span); let ident = self.lower_ident(delegation.ident); @@ -269,20 +281,26 @@ impl<'hir> LoweringContext<'_, 'hir> { self.get_partial_res(node_id).and_then(|r| r.expect_full_res().opt_def_id()) } - // Function parameter count, including C variadic `...` if present. - fn param_count(&self, def_id: DefId) -> (usize, bool /*c_variadic*/) { + /// Returns function parameter info, including C variadic `...` and `#[splat]` if present. + fn param_info(&self, def_id: DefId) -> ParamInfo { let sig = self.tcx.fn_sig(def_id).skip_binder().skip_binder(); - (sig.inputs().len() + usize::from(sig.c_variadic()), sig.c_variadic()) + + ParamInfo { + param_count: sig.inputs().len() + usize::from(sig.c_variadic()), + c_variadic: sig.c_variadic(), + splatted: sig.splatted(), + } } fn lower_delegation_decl( &mut self, sig_id: DefId, - param_count: usize, - c_variadic: bool, + param_info: ParamInfo, span: Span, generics: &GenericsGenerationResults<'hir>, ) -> &'hir hir::FnDecl<'hir> { + let ParamInfo { param_count, c_variadic, splatted } = param_info; + // The last parameter in C variadic functions is skipped in the signature, // like during regular lowering. let decl_param_count = param_count - c_variadic as usize; @@ -314,7 +332,9 @@ impl<'hir> LoweringContext<'_, 'hir> { output: hir::FnRetTy::Return(output), fn_decl_kind: FnDeclFlags::default() .set_lifetime_elision_allowed(true) - .set_c_variadic(c_variadic), + .set_c_variadic(c_variadic) + .set_splatted(splatted, inputs.len()) + .unwrap(), }) } diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 75dab290d6031..b10c2f168d440 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -1759,12 +1759,15 @@ impl<'hir> LoweringContext<'_, 'hir> { coro: Option, ) -> &'hir hir::FnDecl<'hir> { let c_variadic = decl.c_variadic(); + let mut splatted = decl.splatted(); // Skip the `...` (`CVarArgs`) trailing arguments from the AST, // as they are not explicit in HIR/Ty function signatures. // (instead, the `c_variadic` flag is set to `true`) let mut inputs = &decl.inputs[..]; if decl.c_variadic() { + // Splat + variadic errors in AST validation, so just ignore one of them here. + splatted = None; inputs = &inputs[..inputs.len() - 1]; } let inputs = self.arena.alloc_from_iter(inputs.iter().map(|param| { @@ -1852,7 +1855,9 @@ impl<'hir> LoweringContext<'_, 'hir> { } })) .set_lifetime_elision_allowed(self.resolver.lifetime_elision_allowed(fn_node_id)) - .set_c_variadic(c_variadic); + .set_c_variadic(c_variadic) + .set_splatted(splatted, inputs.len()) + .unwrap(); self.arena.alloc(hir::FnDecl { inputs, output, fn_decl_kind }) } diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index d9f03342e1047..7ca4b53000960 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -350,7 +350,8 @@ impl<'a> AstValidator<'a> { fn check_fn_decl(&self, fn_decl: &FnDecl, self_semantic: SelfSemantic) { self.check_decl_num_args(fn_decl); - self.check_decl_cvariadic_pos(fn_decl); + let c_variadic_span = self.check_decl_cvariadic_pos(fn_decl); + self.check_decl_splatting(fn_decl, c_variadic_span); self.check_decl_attrs(fn_decl); self.check_decl_self_param(fn_decl, self_semantic); } @@ -368,17 +369,68 @@ impl<'a> AstValidator<'a> { /// Emits an error if a function declaration has a variadic parameter in the /// beginning or middle of parameter list. /// Example: `fn foo(..., x: i32)` will emit an error. - fn check_decl_cvariadic_pos(&self, fn_decl: &FnDecl) { + /// If a C-variadic parameter is found, returns its span. + fn check_decl_cvariadic_pos(&self, fn_decl: &FnDecl) -> Option { + let mut c_variadic_span = None; + match &*fn_decl.inputs { [ps @ .., _] => { for Param { ty, span, .. } in ps { if let TyKind::CVarArgs = ty.kind { + c_variadic_span = Some(*span); self.dcx().emit_err(errors::FnParamCVarArgsNotLast { span: *span }); } } } _ => {} } + + if let Some(Param { ty, span, .. }) = &fn_decl.inputs.last() { + if let TyKind::CVarArgs = ty.kind { + c_variadic_span = Some(*span); + } + } + + c_variadic_span + } + + /// Emits an error if a function declaration has more than one splatted argument, with a + /// C-variadic parameter, or a splat at an unsupported index (for performance). + /// Example: `fn foo(#[splat] x: (), #[splat] y: ())` will emit an error. + fn check_decl_splatting(&self, fn_decl: &FnDecl, c_variadic_span: Option) { + let (splatted_arg_indexes, mut splatted_spans): (Vec, Vec) = fn_decl + .inputs + .iter() + .enumerate() + .filter_map(|(index, arg)| { + arg.attrs + .iter() + .any(|attr| attr.has_name(sym::splat)) + .then_some((u16::try_from(index).unwrap(), arg.span)) + }) + .unzip(); + + // A splatted argument at the "no splatted" marker index is not supported (this is an + // unlikely edge case). + if let (Some(&splatted_arg_index), Some(&splatted_span)) = + (splatted_arg_indexes.last(), splatted_spans.last()) + && splatted_arg_index == FnDecl::NO_SPLATTED_ARG_INDEX + { + self.dcx() + .emit_err(errors::InvalidSplattedArg { splatted_arg_index, span: splatted_span }); + } + + // Multiple splatted arguments are invalid: we can't know which arguments go in each splat. + if splatted_arg_indexes.len() > 1 { + self.dcx().emit_err(errors::DuplicateSplattedArgs { spans: splatted_spans.clone() }); + } + + if let Some(c_variadic_span) = c_variadic_span + && !splatted_spans.is_empty() + { + splatted_spans.push(c_variadic_span); + self.dcx().emit_err(errors::CVarArgsAndSplat { spans: splatted_spans }); + } } fn check_decl_attrs(&self, fn_decl: &FnDecl) { @@ -394,6 +446,7 @@ impl<'a> AstValidator<'a> { sym::deny, sym::expect, sym::forbid, + sym::splat, sym::warn, ]; !attr.has_any_name(&arr) && rustc_attr_parsing::is_builtin_attr(*attr) diff --git a/compiler/rustc_ast_passes/src/errors.rs b/compiler/rustc_ast_passes/src/errors.rs index a6b75cb70a548..5f3b03bcce96e 100644 --- a/compiler/rustc_ast_passes/src/errors.rs +++ b/compiler/rustc_ast_passes/src/errors.rs @@ -123,6 +123,33 @@ pub(crate) struct FnParamCVarArgsNotLast { pub span: Span, } +#[derive(Diagnostic)] +#[diag("`#[splat]` is not supported on argument index {$splatted_arg_index}")] +#[help("remove `#[splat]`, or use it on an argument closer to the start of the argument list")] +pub(crate) struct InvalidSplattedArg { + pub splatted_arg_index: u16, + + #[primary_span] + #[label("`#[splat]` is not supported here")] + pub span: Span, +} + +#[derive(Diagnostic)] +#[diag("multiple `#[splat]`s are not allowed in the same function")] +#[help("remove `#[splat]` from all but one argument")] +pub(crate) struct DuplicateSplattedArgs { + #[primary_span] + pub spans: Vec, +} + +#[derive(Diagnostic)] +#[diag("`...` and `#[splat]` are not allowed in the same function")] +#[help("remove `#[splat]` or remove `...`")] +pub(crate) struct CVarArgsAndSplat { + #[primary_span] + pub spans: Vec, +} + #[derive(Diagnostic)] #[diag("documentation comments cannot be applied to function parameters")] pub(crate) struct FnParamDocComment { @@ -131,6 +158,7 @@ pub(crate) struct FnParamDocComment { pub span: Span, } +// FIXME(splat): add splat to the allowed built-in attributes when it is complete/stabilized #[derive(Diagnostic)] #[diag( "allow, cfg, cfg_attr, deny, expect, forbid, and warn are the only allowed built-in attributes in function parameters" diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index 2b08906a77434..50c99fef58d7c 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -506,6 +506,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) { gate_all!(pin_ergonomics, "pinned reference syntax is experimental"); gate_all!(postfix_match, "postfix match is experimental"); gate_all!(return_type_notation, "return type notation is experimental"); + gate_all!(splat, "`fn(#[splat] (a, ...))` is incomplete", "call as func((a, ...)) instead"); gate_all!(super_let, "`super let` is experimental"); gate_all!(try_blocks_heterogeneous, "`try bikeshed` expression is experimental"); gate_all!(unsafe_binders, "unsafe binder types are experimental"); diff --git a/compiler/rustc_attr_parsing/src/attributes/mod.rs b/compiler/rustc_attr_parsing/src/attributes/mod.rs index 42c6828ef57b7..f95f2120ea108 100644 --- a/compiler/rustc_attr_parsing/src/attributes/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/mod.rs @@ -64,6 +64,7 @@ pub(crate) mod rustc_allocator; pub(crate) mod rustc_dump; pub(crate) mod rustc_internal; pub(crate) mod semantics; +pub(crate) mod splat; pub(crate) mod stability; pub(crate) mod test_attrs; pub(crate) mod traits; diff --git a/compiler/rustc_attr_parsing/src/attributes/splat.rs b/compiler/rustc_attr_parsing/src/attributes/splat.rs new file mode 100644 index 0000000000000..d30fac4935cc2 --- /dev/null +++ b/compiler/rustc_attr_parsing/src/attributes/splat.rs @@ -0,0 +1,16 @@ +//! Attribute parsing for the `#[splat]` function argument overloading attribute. +//! This attribute modifies typecheck to support overload resolution, then modifies codegen for performance. + +use super::prelude::*; + +pub(crate) struct SplatParser; + +impl NoArgsAttributeParser for SplatParser { + const PATH: &[Symbol] = &[sym::splat]; + const ON_DUPLICATE: OnDuplicate = OnDuplicate::Warn; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ + // FIXME(splat): do we want to allow MacroCall if the macro creates an argument + Allow(Target::Param), + ]); + const CREATE: fn(Span) -> AttributeKind = AttributeKind::Splat; +} diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index bf4989b83200b..69a84f067d864 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -54,6 +54,7 @@ use crate::attributes::rustc_allocator::*; use crate::attributes::rustc_dump::*; use crate::attributes::rustc_internal::*; use crate::attributes::semantics::*; +use crate::attributes::splat::*; use crate::attributes::stability::*; use crate::attributes::test_attrs::*; use crate::attributes::traits::*; @@ -317,6 +318,7 @@ attribute_parsers!( Single>, Single>, Single>, + Single>, Single>, Single>, // tidy-alphabetical-end diff --git a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs index f9c91c3371516..686ad5647f9ac 100644 --- a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs @@ -15,8 +15,7 @@ use rustc_middle::bug; use rustc_middle::hir::place::PlaceBase; use rustc_middle::mir::{AnnotationSource, ConstraintCategory, ReturnConstraint}; use rustc_middle::ty::{ - self, FnSigKind, GenericArgs, Region, RegionVid, Ty, TyCtxt, TypeFoldable, TypeVisitor, - fold_regions, + self, GenericArgs, Region, RegionVid, Ty, TyCtxt, TypeFoldable, TypeVisitor, fold_regions, }; use rustc_span::{Ident, Span, kw}; use rustc_trait_selection::error_reporting::InferCtxtErrorExt; @@ -1085,9 +1084,8 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { } // Build a new closure where the return type is an owned value, instead of a ref. - let fn_sig_kind = FnSigKind::default() - .set_safety(hir::Safety::Safe) - .set_c_variadic(liberated_sig.c_variadic()); + // The new closure is safe, but otherwise has the same ABI, splat, and c-variadic. + let fn_sig_kind = liberated_sig.fn_sig_kind.set_safety(hir::Safety::Safe); let closure_sig_as_fn_ptr_ty = Ty::new_fn_ptr( tcx, ty::Binder::dummy(tcx.mk_fn_sig( diff --git a/compiler/rustc_codegen_cranelift/src/abi/mod.rs b/compiler/rustc_codegen_cranelift/src/abi/mod.rs index 13f5ad5157cef..755ee0aa4bd20 100644 --- a/compiler/rustc_codegen_cranelift/src/abi/mod.rs +++ b/compiler/rustc_codegen_cranelift/src/abi/mod.rs @@ -265,6 +265,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 730272d2be94d..bafd9cbc3c4df 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 55001d85211a4..a61e121d2a22e 100644 --- a/compiler/rustc_codegen_ssa/src/mir/block.rs +++ b/compiler/rustc_codegen_ssa/src/mir/block.rs @@ -1136,6 +1136,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 84013a00d79df..ae0ee837a7d49 100644 --- a/compiler/rustc_codegen_ssa/src/mir/mod.rs +++ b/compiler/rustc_codegen_ssa/src/mir/mod.rs @@ -408,6 +408,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_const_eval/src/const_eval/type_info.rs b/compiler/rustc_const_eval/src/const_eval/type_info.rs index 7b63ab5bb02e8..f2b1bf236211c 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; @@ -465,6 +465,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_u16( + // 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 5ec0344592da5..f99fa50c767a7 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -678,6 +678,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_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index 58bf12855ad6e..a06498051162b 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -392,6 +392,14 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ // - https://github.com/rust-lang/rust/issues/130494 gated!(pin_v2, pin_ergonomics, experimental!(pin_v2)), + // The `#[splat]` attribute is part of the `splat` experiment + // that improves the ergonomics of function overloading, tracked in: + // + // - https://github.com/rust-lang/rust/issues/153629 + gated!( + splat, experimental!(splat), + ), + // ========================================================================== // Internal attributes: Stability, deprecation, and unsafe: // ========================================================================== diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 65bb487c7b8eb..6a90f05d172ea 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -713,6 +713,9 @@ declare_features! ( (unstable, sparc_target_feature, "1.84.0", Some(132783)), /// Allows specialization of implementations (RFC 1210). (incomplete, specialization, "1.7.0", Some(31844)), + /// Experimental "splatting" of function call arguments at the call site. + /// e.g. `foo(a, b, c)` calls `#[splat] fn foo((a: A, b: B, c: C))`. + (incomplete, splat, "CURRENT_RUSTC_VERSION", Some(153629)), /// Allows using `#[rustc_align_static(...)]` on static items. (unstable, static_align, "1.91.0", Some(146177)), /// Allows attributes on expressions and non-item statements. diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 2cdcf75d00be7..f92ebcc4db89d 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -1586,6 +1586,9 @@ pub enum AttributeKind { reason: Option, }, + /// Represents `#[splat]` + Splat(Span), + /// Represents `#[stable]`, `#[unstable]` and `#[rustc_allowed_through_unstable_modules]`. Stability { stability: Stability, diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index f2b528d1dcdc2..8952d05535525 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -191,6 +191,7 @@ impl AttributeKind { RustcUnsafeSpecializationMarker => No, Sanitize { .. } => No, ShouldPanic { .. } => No, + Splat(..) => Yes, Stability { .. } => Yes, TargetFeature { .. } => No, TestRunner(..) => Yes, diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 2f18b09cf1ae8..b449066fe300f 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -3925,13 +3925,29 @@ pub struct Param<'hir> { pub span: Span, } +/// Error type for splatted argument index errors. +#[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 }, + + /// The splatted argument index is outside the bounds of the function arguments. + OutOfBounds { splatted_arg_index: u16, args_len: u16 }, +} + /// Contains the packed non-type fields of a function declaration. -// FIXME(splat): add the splatted argument index as a u16 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Encodable, Decodable, StableHash)] pub struct FnDeclFlags { /// Holds the c_variadic and lifetime_elision_allowed bitflags, and 3 bits for the `ImplicitSelfKind`. 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 + /// details. + splatted: u16, } impl fmt::Debug for FnDeclFlags { @@ -3943,11 +3959,15 @@ impl fmt::Debug for FnDeclFlags { f.field(&"LifetimeElisionAllowed"); } else { f.field(&"NoLifetimeElision"); - }; + } if self.c_variadic() { f.field(&"CVariadic"); - }; + } + + if let Some(index) = self.splatted() { + f.field(&format!("Splatted({})", index)); + } f.finish() } @@ -3963,14 +3983,20 @@ impl FnDeclFlags { /// Bitflag for lifetime elision. const LIFETIME_ELISION_ALLOWED_FLAG: u8 = 1 << 4; - /// Create a new FnDeclKind with no implicit self, no lifetime elision, and no C-style variadic argument. + /// 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; + + /// 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 { - Self { flags: 0 } + Self { flags: 0, splatted: 0 } .set_implicit_self(ImplicitSelfKind::None) .set_lifetime_elision_allowed(false) .set_c_variadic(false) + .set_no_splatted_args() } /// Set the implicit self kind. @@ -4013,6 +4039,41 @@ impl FnDeclFlags { 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 const 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. + return Err(SplattedArgIndexError::InvalidIndex { splatted_arg_index }); + } else if splatted_arg_index as usize >= 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 "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 { + self.splatted = Self::NO_SPLATTED_ARG_INDEX; + + self + } + /// Get the implicit self kind. pub const fn implicit_self(self) -> ImplicitSelfKind { match self.flags & Self::IMPLICIT_SELF_MASK { @@ -4034,6 +4095,11 @@ impl FnDeclFlags { pub const 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 { + if self.splatted == Self::NO_SPLATTED_ARG_INDEX { None } else { Some(self.splatted) } + } } /// Represents the header (not the body) of a function declaration. @@ -4081,6 +4147,10 @@ impl<'hir> FnDecl<'hir> { self.fn_decl_kind.lifetime_elision_allowed() } + pub fn splatted(&self) -> Option { + self.fn_decl_kind.splatted() + } + pub fn dummy(span: Span) -> Self { Self { inputs: &[], diff --git a/compiler/rustc_hir_analysis/src/check/mod.rs b/compiler/rustc_hir_analysis/src/check/mod.rs index 3225e00b24b26..c140af9fc7773 100644 --- a/compiler/rustc_hir_analysis/src/check/mod.rs +++ b/compiler/rustc_hir_analysis/src/check/mod.rs @@ -445,12 +445,14 @@ 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)| { - Some(match ty.kind() { + 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 => { let reg = format!("{reg} "); @@ -477,7 +479,8 @@ fn fn_sig_suggestion<'tcx>( format!("_: {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 63753aee383a0..554108f8f7925 100644 --- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs +++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs @@ -1741,8 +1741,18 @@ fn check_fn_or_method<'tcx>( if sig.abi() == ExternAbi::RustCall { let span = tcx.def_span(def_id); - let has_implicit_self = hir_decl.implicit_self() != hir::ImplicitSelfKind::None; + 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): 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 473a32bac03a9..15995d55e5d00 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs @@ -3611,10 +3611,13 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { debug!(?output_ty); + debug!(?abi, ?safety, ?decl.fn_decl_kind, input_tys_len = ?input_tys.len()); 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.fn_decl_kind.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_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs index 1a401af1d328d..a126141beb7a1 100644 --- a/compiler/rustc_hir_pretty/src/lib.rs +++ b/compiler/rustc_hir_pretty/src/lib.rs @@ -2264,6 +2264,9 @@ impl<'a> State<'a> { assert!(arg_idents.is_empty() || body_id.is_none()); let mut i = 0; let mut print_arg = |s: &mut Self, ty: Option<&hir::Ty<'_>>| { + if Some(i) == decl.splatted().map(usize::from) { + s.word("#[splat]"); + } if i == 0 && decl.implicit_self().has_implicit_self() { s.print_implicit_self(&decl.implicit_self()); } else { diff --git a/compiler/rustc_hir_typeck/src/callee.rs b/compiler/rustc_hir_typeck/src/callee.rs index fc504116101c9..a55212fb6259b 100644 --- a/compiler/rustc_hir_typeck/src/callee.rs +++ b/compiler/rustc_hir_typeck/src/callee.rs @@ -53,9 +53,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>), @@ -536,7 +540,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(); @@ -565,11 +569,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!(), }; @@ -593,14 +597,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fn_sig.output(), expected, arg_exprs, - fn_sig.c_variadic(), - TupleArgumentsFlag::DontTupleArguments, + fn_sig.fn_sig_kind.c_variadic(), + 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), @@ -903,9 +911,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() @@ -975,6 +984,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, @@ -982,9 +998,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/closure.rs b/compiler/rustc_hir_typeck/src/closure.rs index 763d2a27e6cc0..585f6bd57ac57 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/demand.rs b/compiler/rustc_hir_typeck/src/demand.rs index 5454b282d5226..4e1f5ee159e01 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; @@ -401,9 +401,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 + 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/errors.rs b/compiler/rustc_hir_typeck/src/errors.rs index 3f8ac61eed084..319aa796afd87 100644 --- a/compiler/rustc_hir_typeck/src/errors.rs +++ b/compiler/rustc_hir_typeck/src/errors.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_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index fd9c1bc8780ee..6b86f33e12a09 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); @@ -1493,8 +1500,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { NoExpectation, args, false, - TupleArgumentsFlag::DontTupleArguments, + TupleArgumentsFlag::DontTupleArgs, 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 50dc150a76668..a53ffd2963c2d 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; @@ -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, + ) { + self.typeck_results.borrow_mut().splatted_defs_mut().insert(hir_id, r); + } + #[instrument(level = "debug", skip(self))] pub(crate) fn write_method_call_and_enforce_effects( &self, @@ -248,6 +257,32 @@ 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, + 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); + } + } + 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 415630dab38b3..4d704d7afa53d 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 { @@ -550,6 +554,260 @@ 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 mut err_code = None; + + 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: err_code, + 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 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) + .for_each(|_| {}); + } 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(), + ) + .for_each(|_| {}); + 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. @@ -575,6 +833,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 @@ -1348,8 +1607,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. @@ -1439,22 +1699,38 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { 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 + 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)| { @@ -1998,7 +2274,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" @@ -2231,7 +2511,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()) diff --git a/compiler/rustc_hir_typeck/src/lib.rs b/compiler/rustc_hir_typeck/src/lib.rs index 15729bc311e57..9c8d8c4a488e4 100644 --- a/compiler/rustc_hir_typeck/src/lib.rs +++ b/compiler/rustc_hir_typeck/src/lib.rs @@ -50,7 +50,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; @@ -613,12 +613,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)) {} /// ``` @@ -632,10 +630,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 { - DontTupleArguments, - TupleArguments, + /// Arguments are typechecked unchanged. + DontTupleArgs, + /// 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(u16), +} + +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::DontTupleArgs + } + + /// Returns true if the arguments are tupled through "rust-call" or splatting. + fn is_tupled(self) -> bool { + match self { + Self::DontTupleArgs => 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::DontTupleArgs | 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::DontTupleArgs => (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 ba5b55b43049f..0a9d543a1984a 100644 --- a/compiler/rustc_hir_typeck/src/writeback.rs +++ b/compiler/rustc_hir_typeck/src/writeback.rs @@ -663,6 +663,11 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> { self.typeck_results.type_dependent_defs_mut().insert(hir_id, def); } + // 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_lint/src/foreign_modules.rs b/compiler/rustc_lint/src/foreign_modules.rs index c194626a1cb21..22e2af782dc96 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 d1048a65a7be0..bc5e461707c34 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -2068,6 +2068,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 52f37ed4a9eac..7f5b6ecf48e44 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.def_path_str(values.expected), diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index 93da73e1e4505..308182671e04a 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -114,7 +114,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 daead99b977c1..fd53eb2c46b84 100644 --- a/compiler/rustc_middle/src/ty/print/pretty.rs +++ b/compiler/rustc_middle/src/ty/print/pretty.rs @@ -1428,6 +1428,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; @@ -1531,10 +1533,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, ", ")?; @@ -3142,7 +3153,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 b98c0af0a8ae8..0aad09e492f34 100644 --- a/compiler/rustc_mir_build/src/builder/mod.rs +++ b/compiler/rustc_mir_build/src/builder/mod.rs @@ -551,6 +551,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/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index 69260792a95d2..d714d82859284 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -16,7 +16,8 @@ use rustc_middle::ty::adjustment::{ Adjust, Adjustment, AutoBorrow, AutoBorrowMutability, DerefAdjustKind, PointerCoercion, }; use rustc_middle::ty::{ - self, AdtKind, GenericArgs, InlineConstArgs, InlineConstArgsParts, ScalarInt, Ty, UpvarArgs, + self, AdtKind, GenericArgs, InlineConstArgs, InlineConstArgsParts, ScalarInt, SplattedDef, Ty, + UpvarArgs, }; use rustc_middle::{bug, span_bug}; use rustc_span::Span; @@ -323,19 +324,94 @@ 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, + // FIXME(splat): abstract this into a helper function that handles both method and function calls + if self.typeck_results.is_splatted_call(expr) { + // The callee has a splatted tuple argument. + let (method, 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 method args: {:?} + {:?} > {:?} for method call {:?}, receiver {:?}, args {:?}", + tupled_arg_index, + tupled_args_count, + args.len(), + segment, + receiver, + args, + ); + } + + // FIXME(splat): do we need to rewrite `a.f(b, c)` into `Trait::f(a, #[splat] (b, c))`? + info!("Using splatted method span: {:?}", method.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 tupled_args = Expr { + ty: Ty::new_tup_from_iter(tcx, tupled_arg_tys), + temp_scope_id: method.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 = vec![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 method_span = method.span; + ExprKind::Call { + // FIXME(splat): should this be method.ty, or be the same as it?? + ty: method.ty, + fun: self.thir.exprs.push(method), + args: args.into_boxed_slice(), + from_hir_call: true, + fn_span: method_span, + } + } 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, + } } } @@ -366,6 +442,70 @@ 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))` + + let (function, tupled_arg_index, tupled_args_count) = + self.splatted_callee(expr, fun.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: `f() -> f(#[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 {:?}, args {:?}", + tupled_arg_index, + tupled_args_count, + args.len(), + fun, + args, + ); + } + + // 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 tupled_args = Expr { + ty: Ty::new_tup_from_iter(tcx, tupled_arg_tys), + temp_scope_id: expr.hir_id.local_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 = initial_non_tupled_args; + args.push(tupled_args); + args.extend(final_non_tupled_args); + + ExprKind::Call { + ty: function.ty, + fun: self.thir.exprs.push(function), + args: args.into_boxed_slice(), + from_hir_call: true, + fn_span: expr.span, + } } 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 @@ -1159,6 +1299,37 @@ 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, + ) + } + fn convert_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) -> ArmId { let arm = Arm { pattern: self.pattern_from_hir(&arm.pat), diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index a978138e3e1ff..1b1387d62cdcf 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -354,6 +354,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { | AttributeKind::RustcTrivialFieldReads | AttributeKind::RustcUnsafeSpecializationMarker | AttributeKind::ShouldPanic { .. } + | AttributeKind::Splat(..) | AttributeKind::Stability { .. } | AttributeKind::TestRunner(..) | AttributeKind::ThreadLocal diff --git a/compiler/rustc_public/src/unstable/convert/internal.rs b/compiler/rustc_public/src/unstable/convert/internal.rs index 165ad737fccb8..8695da727baee 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_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 695103a249b49..91d0b29d974a0 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1115,6 +1115,7 @@ symbols! { irrefutable_let_patterns, is, is_auto, + is_splatted, is_val_statically_known, isa_attribute, isize, @@ -1945,6 +1946,8 @@ symbols! { specialization, speed, spirv, + splat, + splatted_index, spotlight, sqrtf16, sqrtf32, diff --git a/compiler/rustc_symbol_mangling/src/v0.rs b/compiler/rustc_symbol_mangling/src/v0.rs index 46a7e092e6bc9..9a981ce9685f3 100644 --- a/compiler/rustc_symbol_mangling/src/v0.rs +++ b/compiler/rustc_symbol_mangling/src/v0.rs @@ -576,6 +576,7 @@ impl<'tcx> Printer<'tcx> for V0SymbolMangler<'tcx> { } } } + // FIXME(splat): should splatted arguments be part of symbol mangling? for &ty in sig.inputs() { ty.print(p)?; } 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 21951cee6d5ab..ab684c7cc5b8b 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs @@ -824,9 +824,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 148f1471b1b66..56780a0700424 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -4969,6 +4969,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 15fbd985d9630..56c0851a18d3c 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 425436dabafb2..1938a9759d202 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 a08bd00eeed65..3db5f8e414a84 100644 --- a/compiler/rustc_type_ir/src/ty_kind.rs +++ b/compiler/rustc_type_ir/src/ty_kind.rs @@ -321,6 +321,40 @@ impl TyKind { } } + pub fn def_id(self) -> Option { + match self { + ty::Adt(adt, ..) => Some(adt.def_id().into()), + ty::Foreign(def_id) => Some(def_id.into()), + ty::FnDef(def_id, ..) => Some(def_id.into()), + ty::Closure(def_id, ..) => Some(def_id.into()), + ty::CoroutineClosure(def_id, ..) => Some(def_id.into()), + ty::Coroutine(def_id, ..) => Some(def_id.into()), + ty::CoroutineWitness(def_id, ..) => Some(def_id.into()), + ty::Alias(alias) => Some(alias.kind.def_id().into()), + ty::Bool + | ty::Char + | ty::Int(_) + | ty::Uint(_) + | ty::Float(_) + | ty::Str + | ty::Array(_, _) + | ty::Pat(_, _) + | ty::Slice(_) + | ty::RawPtr(_, _) + | ty::Ref(_, _, _) + | ty::FnPtr(..) + | ty::UnsafeBinder(_) + | ty::Dynamic(_, _) + | ty::Never + | ty::Tuple(_) + | ty::Param(_) + | ty::Bound(_, _) + | ty::Placeholder(_) + | ty::Infer(_) + | ty::Error(..) => None, + } + } + /// Returns `true` when the outermost type cannot be further normalized, /// resolved, or instantiated. /// @@ -761,10 +795,20 @@ 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. + /// Currently the only unsupported index is `u16::MAX`, which is used to indicate that no argument + /// is splatted. + InvalidIndex { splatted_arg_index: u16 }, + + /// The splatted argument index is outside the bounds of the function arguments. + OutOfBounds { splatted_arg_index: u16, 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)] #[cfg_attr( feature = "nightly", derive(Encodable_NoContext, Decodable_NoContext, StableHash_NoContext) @@ -772,18 +816,20 @@ impl Eq for TypeAndMut {} pub struct FnSigKind { /// Holds the c_variadic and safety bitflags, and 6 bits for the `ExternAbi` variant and unwind /// flag. - #[type_visitable(ignore)] - #[type_foldable(identity)] flags: u8, - #[type_visitable(ignore)] - #[type_foldable(identity)] + + /// Which function argument is splatted into multiple arguments in callers, if any? + /// Splatting the `u16::MAX`th argument is not supported, because it likely pushes the caller + /// over the fn args limit. (And spending an extra byte on an edge case is not worth the perf.) + splatted: u16, + _marker: PhantomData I>, } impl crate::lift::Lift for FnSigKind { type Lifted = FnSigKind; fn lift_to_interner(self, _cx: J) -> Self::Lifted { - FnSigKind { flags: self.flags, _marker: PhantomData } + FnSigKind { flags: self.flags, splatted: self.splatted, _marker: PhantomData } } } @@ -801,12 +847,29 @@ 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; @@ -817,19 +880,30 @@ 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". + /// 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: u16 = u16::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. @@ -868,6 +942,40 @@ 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. + return Err(SplattedArgIndexError::InvalidIndex { splatted_arg_index }); + } else if splatted_arg_index as usize >= 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; @@ -888,6 +996,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)] @@ -918,8 +1031,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 { @@ -934,11 +1060,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() } } } @@ -971,6 +1100,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() } @@ -1006,6 +1139,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() { @@ -1167,8 +1303,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 3b8ed0a15994d..b74ab24c9f51b 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/library/core/src/mem/type_info.rs b/library/core/src/mem/type_info.rs index e4d47dedb8606..34ed592954798 100644 --- a/library/core/src/mem/type_info.rs +++ b/library/core/src/mem/type_info.rs @@ -216,7 +216,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, } @@ -343,6 +343,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: u16, +} + +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/lib.rs b/library/coretests/tests/lib.rs index c4292c2a421b1..bcfdc0cd4fd78 100644 --- a/library/coretests/tests/lib.rs +++ b/library/coretests/tests/lib.rs @@ -106,6 +106,7 @@ #![feature(slice_shift)] #![feature(slice_split_once)] #![feature(sliceindex_wrappers)] +#![feature(splat)] #![feature(split_array)] #![feature(split_as_slice)] #![feature(std_internals)] @@ -128,6 +129,7 @@ #![feature(widening_mul)] // tidy-alphabetical-end #![allow(internal_features)] +#![expect(incomplete_features)] #![deny(fuzzy_provenance_casts)] #![deny(unsafe_op_in_unsafe_fn)] 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); +} diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index 512ea6ab1a2ff..7075cd77a32f0 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -407,6 +407,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 3d478912a3db2..d06f8de85e8c5 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 @@ -159,6 +159,8 @@ impl<'db> InferenceContext<'_, 'db> { // impl forces the closure kind to `FnOnce` i.e. `u8`. let kind_ty = autoderef.ctx().table.next_ty_var(); 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 d004b5e3ef1d6..00276f5ade6c5 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs @@ -577,6 +577,7 @@ pub fn callable_sig_from_fn_trait<'db>( Binder::dummy(FnSig { inputs_and_output, c_variadic: false, + // FIXME(splat): handle splatted arguments safety: abi::Safety::Safe, abi: FnAbi::RustCall, }), 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 335aff2c1df16..e80a2249365e5 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs @@ -574,6 +574,7 @@ impl<'db, 'a> TyLoweringContext<'db, 'a> { abi: fn_.abi.as_ref().map_or(FnAbi::Rust, FnAbi::from_symbol), safety: if fn_.is_unsafe { Safety::Unsafe } else { Safety::Safe }, c_variadic: 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 cfb55e2e00a05..fc9329edb40c1 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 @@ -2333,6 +2333,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 39abdaf079b63..fbe019a334336 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 @@ -1444,6 +1444,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, FnAbi::Rust) }) } diff --git a/tests/ui/README.md b/tests/ui/README.md index 2fe1657e7ecf2..9085bcd820f17 100644 --- a/tests/ui/README.md +++ b/tests/ui/README.md @@ -1272,6 +1272,12 @@ An assorted collection of tests that involves specific diagnostic spans. See [Tracking issue for specialization (RFC 1210) #31844](https://github.com/rust-lang/rust/issues/31844). +## `tests/ui/splat` + +Tests for the `#![feature(splat)]` attribute. + +See [Tracking Issue for argument splatting #153629](https://github.com/rust-lang/rust/issues/153629). + ## `tests/ui/stability-attribute/` Stability attributes used internally by the standard library: `#[stable()]` and `#[unstable()]`. diff --git a/tests/ui/feature-gates/feature-gate-splat.rs b/tests/ui/feature-gates/feature-gate-splat.rs new file mode 100644 index 0000000000000..ebcfc0e5a1d9f --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-splat.rs @@ -0,0 +1,8 @@ +#[rustfmt::skip] +fn tuple_args( + #[splat] //~ ERROR the `#[splat]` attribute is an experimental feature + (a, b, c): (u32, i8, char), +) { +} + +fn main() {} diff --git a/tests/ui/feature-gates/feature-gate-splat.stderr b/tests/ui/feature-gates/feature-gate-splat.stderr new file mode 100644 index 0000000000000..9881cacef4074 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-splat.stderr @@ -0,0 +1,13 @@ +error[E0658]: the `#[splat]` attribute is an experimental feature + --> $DIR/feature-gate-splat.rs:3:5 + | +LL | #[splat] + | ^^^^^^^^ + | + = note: see issue #153629 for more information + = help: add `#![feature(splat)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0658`. 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-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-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-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`. 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-invalid.rs b/tests/ui/splat/splat-invalid.rs new file mode 100644 index 0000000000000..2c4bf57ef972a --- /dev/null +++ b/tests/ui/splat/splat-invalid.rs @@ -0,0 +1,47 @@ +//! Test using `#[splat]` incorrectly, in ways not covered by other tests. + +#![allow(incomplete_features)] +#![feature(splat)] +#![feature(c_variadic)] + +fn multisplat_bad(#[splat] (_a, _b): (u32, i8), #[splat] (_c, _d): (u32, i8)) {} +//~^ ERROR multiple `#[splat]`s are not allowed in the same function + +unsafe extern "C" fn splat_variadic(#[splat] (_a, _b): (u32, i8), varargs: ...) {} +//~^ ERROR `...` and `#[splat]` are not allowed in the same function + +unsafe extern "C" fn splat_variadic2(varargs: ..., #[splat] (_a, _b): (u32, i8)) {} +//~^ ERROR `...` and `#[splat]` are not allowed in the same function +//~| ERROR `...` must be the last argument of a C-variadic function + +extern "C" { + fn splat_variadic3(#[splat] (_a, _b): (u32, i8), ...) {} + //~^ ERROR incorrect function inside `extern` block + //~| ERROR `...` and `#[splat]` are not allowed in the same function + + fn splat_variadic4(..., #[splat] (_a, _b): (u32, i8)) {} + //~^ ERROR incorrect function inside `extern` block + //~| ERROR `...` and `#[splat]` are not allowed in the same function + //~| ERROR `...` must be the last argument of a C-variadic function + + // FIXME(splat): tuple layouts are unspecified. Should this error in addition to + // the existing `improper_ctypes` lint? + #[expect(improper_ctypes)] + fn bar_2(#[splat] _: (u32, i8)); +} + +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 new file mode 100644 index 0000000000000..74c54c00cd285 --- /dev/null +++ b/tests/ui/splat/splat-invalid.stderr @@ -0,0 +1,106 @@ +error: multiple `#[splat]`s are not allowed in the same function + --> $DIR/splat-invalid.rs:7:19 + | +LL | fn multisplat_bad(#[splat] (_a, _b): (u32, i8), #[splat] (_c, _d): (u32, i8)) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: remove `#[splat]` from all but one argument + +error: `...` and `#[splat]` are not allowed in the same function + --> $DIR/splat-invalid.rs:10:37 + | +LL | unsafe extern "C" fn splat_variadic(#[splat] (_a, _b): (u32, i8), varargs: ...) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^ + | + = help: remove `#[splat]` or remove `...` + +error: `...` must be the last argument of a C-variadic function + --> $DIR/splat-invalid.rs:13:38 + | +LL | unsafe extern "C" fn splat_variadic2(varargs: ..., #[splat] (_a, _b): (u32, i8)) {} + | ^^^^^^^^^^^^ + +error: `...` and `#[splat]` are not allowed in the same function + --> $DIR/splat-invalid.rs:13:38 + | +LL | unsafe extern "C" fn splat_variadic2(varargs: ..., #[splat] (_a, _b): (u32, i8)) {} + | ^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: remove `#[splat]` or remove `...` + +error: incorrect function inside `extern` block + --> $DIR/splat-invalid.rs:18:8 + | +LL | extern "C" { + | ---------- `extern` blocks define existing foreign functions and functions inside of them cannot have a body +LL | fn splat_variadic3(#[splat] (_a, _b): (u32, i8), ...) {} + | ^^^^^^^^^^^^^^^ cannot have a body -- help: remove the invalid body: `;` + | + = help: you might have meant to write a function accessible through FFI, which can be done by writing `extern fn` outside of the `extern` block + = note: for more information, visit https://doc.rust-lang.org/std/keyword.extern.html + +error: `...` and `#[splat]` are not allowed in the same function + --> $DIR/splat-invalid.rs:18:24 + | +LL | fn splat_variadic3(#[splat] (_a, _b): (u32, i8), ...) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ + | + = help: remove `#[splat]` or remove `...` + +error: incorrect function inside `extern` block + --> $DIR/splat-invalid.rs:22:8 + | +LL | extern "C" { + | ---------- `extern` blocks define existing foreign functions and functions inside of them cannot have a body +... +LL | fn splat_variadic4(..., #[splat] (_a, _b): (u32, i8)) {} + | ^^^^^^^^^^^^^^^ cannot have a body -- help: remove the invalid body: `;` + | + = help: you might have meant to write a function accessible through FFI, which can be done by writing `extern fn` outside of the `extern` block + = note: for more information, visit https://doc.rust-lang.org/std/keyword.extern.html + +error: `...` must be the last argument of a C-variadic function + --> $DIR/splat-invalid.rs:22:24 + | +LL | fn splat_variadic4(..., #[splat] (_a, _b): (u32, i8)) {} + | ^^^ + +error: `...` and `#[splat]` are not allowed in the same function + --> $DIR/splat-invalid.rs:22:24 + | +LL | fn splat_variadic4(..., #[splat] (_a, _b): (u32, i8)) {} + | ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: remove `#[splat]` or remove `...` + +error[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-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-non-fn-arg.rs b/tests/ui/splat/splat-non-fn-arg.rs new file mode 100644 index 0000000000000..6f2815e2b6579 --- /dev/null +++ b/tests/ui/splat/splat-non-fn-arg.rs @@ -0,0 +1,59 @@ +//! Test that using `#[splat]` on non-function-arguments is an error. + +#![allow(incomplete_features)] +#![feature(splat)] + +#[splat] //~ ERROR `#[splat]` attribute cannot be used on functions +fn tuple_args_bad((a, b): (u32, i8)) {} + +#[splat] //~ ERROR `#[splat]` attribute cannot be used on traits +trait FooTraitBad { + fn tuple_1(_: (u32,)); + + fn tuple_4(self, _: (u32, i8, (), f32)); +} + +struct Foo; + +#[splat] //~ ERROR `#[splat]` attribute cannot be used on inherent impl blocks +impl Foo { + fn tuple_1_bad((a,): (u32,)) {} +} + +impl Foo { + #[splat] //~ ERROR `#[splat]` attribute cannot be used on inherent methods + fn tuple_3_bad((a, b, c): (u32, i32, i8)) {} + + #[splat] //~ ERROR `#[splat]` attribute cannot be used on inherent methods + fn tuple_2_bad(self, (a, b): (u32, i8)) -> u32 { + a + } +} + +#[splat] //~ ERROR `#[splat]` attribute cannot be used on trait impl blocks +impl FooTraitBad for Foo { + fn tuple_1(_: (u32,)) {} + + fn tuple_4(self, _: (u32, i8, (), f32)) {} +} + +#[splat] //~ ERROR `#[splat]` attribute cannot be used on foreign modules +extern "C" { + fn foo_2(_: (u32, i8)); +} + +extern "C" { + #[splat] //~ ERROR `#[splat]` attribute cannot be used on foreign functions + fn bar_2_bad(_: (u32, i8)); +} + +#[splat] //~ ERROR `#[splat]` attribute cannot be used on modules +mod foo_mod {} + +#[splat] //~ ERROR `#[splat]` attribute cannot be used on use statements +use std::mem; + +#[splat] //~ ERROR `#[splat]` attribute cannot be used on structs +struct FooStruct; + +fn main() {} diff --git a/tests/ui/splat/splat-non-fn-arg.stderr b/tests/ui/splat/splat-non-fn-arg.stderr new file mode 100644 index 0000000000000..089636f3d4dff --- /dev/null +++ b/tests/ui/splat/splat-non-fn-arg.stderr @@ -0,0 +1,90 @@ +error: `#[splat]` attribute cannot be used on functions + --> $DIR/splat-non-fn-arg.rs:6:1 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can only be applied to function params + +error: `#[splat]` attribute cannot be used on traits + --> $DIR/splat-non-fn-arg.rs:9:1 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can only be applied to function params + +error: `#[splat]` attribute cannot be used on inherent impl blocks + --> $DIR/splat-non-fn-arg.rs:18:1 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can only be applied to function params + +error: `#[splat]` attribute cannot be used on inherent methods + --> $DIR/splat-non-fn-arg.rs:24:5 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can only be applied to function params + +error: `#[splat]` attribute cannot be used on inherent methods + --> $DIR/splat-non-fn-arg.rs:27:5 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can only be applied to function params + +error: `#[splat]` attribute cannot be used on trait impl blocks + --> $DIR/splat-non-fn-arg.rs:33:1 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can only be applied to function params + +error: `#[splat]` attribute cannot be used on foreign modules + --> $DIR/splat-non-fn-arg.rs:40:1 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can only be applied to function params + +error: `#[splat]` attribute cannot be used on foreign functions + --> $DIR/splat-non-fn-arg.rs:46:5 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can only be applied to function params + +error: `#[splat]` attribute cannot be used on modules + --> $DIR/splat-non-fn-arg.rs:50:1 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can only be applied to function params + +error: `#[splat]` attribute cannot be used on use statements + --> $DIR/splat-non-fn-arg.rs:53:1 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can only be applied to function params + +error: `#[splat]` attribute cannot be used on structs + --> $DIR/splat-non-fn-arg.rs:56:1 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can only be applied to function params + +error: aborting due to 11 previous errors + 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`. 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) +} diff --git a/tests/ui/symbol-names/basic.legacy.stderr b/tests/ui/symbol-names/basic.legacy.stderr index f88fdf7509c76..d4d86cc9c4500 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(_ZN5basic4main17had1822273ff65b1bE) --> $DIR/basic.rs:8:1 | LL | #[rustc_dump_symbol_name] | ^^^^^^^^^^^^^^^^^^^^^^^^^ -error: demangling(basic::main::h27f62b2d0b2beac6) +error: demangling(basic::main::had1822273ff65b1b) --> $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..2b29917356ce6 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$3foo17h7de75f168fb45cf7E) --> $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::h7de75f168fb45cf7) --> $DIR/issue-60925.rs:21:9 | LL | #[rustc_dump_symbol_name]