From 0a60096c43d204d55f7814dd5ba407632190091d Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 11 Mar 2026 17:34:32 +1000 Subject: [PATCH 1/5] Add splat builtin attribute & feature gate --- .../rustc_ast_passes/src/ast_validation.rs | 1 + compiler/rustc_ast_passes/src/errors.rs | 1 + compiler/rustc_ast_passes/src/feature_gate.rs | 1 + .../rustc_attr_parsing/src/attributes/mod.rs | 1 + .../src/attributes/splat.rs | 17 ++++ compiler/rustc_attr_parsing/src/context.rs | 2 + compiler/rustc_codegen_ssa/src/mir/block.rs | 1 + compiler/rustc_codegen_ssa/src/mir/mod.rs | 1 + .../rustc_const_eval/src/interpret/call.rs | 1 + compiler/rustc_feature/src/builtin_attrs.rs | 9 ++ compiler/rustc_feature/src/unstable.rs | 3 + .../rustc_hir/src/attrs/data_structures.rs | 3 + .../rustc_hir/src/attrs/encode_cross_crate.rs | 1 + .../rustc_hir_analysis/src/check/entry.rs | 1 + compiler/rustc_mir_build/src/builder/mod.rs | 1 + compiler/rustc_passes/src/check_attr.rs | 1 + compiler/rustc_span/src/symbol.rs | 1 + tests/ui/README.md | 6 ++ tests/ui/feature-gates/feature-gate-splat.rs | 8 ++ .../feature-gates/feature-gate-splat.stderr | 13 +++ tests/ui/splat/splat-non-function.rs | 90 +++++++++++++++++++ tests/ui/splat/splat-non-function.stderr | 82 +++++++++++++++++ 22 files changed, 245 insertions(+) create mode 100644 compiler/rustc_attr_parsing/src/attributes/splat.rs create mode 100644 tests/ui/feature-gates/feature-gate-splat.rs create mode 100644 tests/ui/feature-gates/feature-gate-splat.stderr create mode 100644 tests/ui/splat/splat-non-function.rs create mode 100644 tests/ui/splat/splat-non-function.stderr diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index dd14e91435690..5107b54c1a63a 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -392,6 +392,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 b3a22c0c99549..7cc0043e9e33c 100644 --- a/compiler/rustc_ast_passes/src/errors.rs +++ b/compiler/rustc_ast_passes/src/errors.rs @@ -132,6 +132,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 830eb3d6d8170..b5c67f13ca2d1 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -597,6 +597,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) { gate_all!(const_block_items, "const block items are experimental"); gate_all!(final_associated_functions, "`final` on trait functions is experimental"); gate_all!(impl_restriction, "`impl` restrictions are experimental"); + gate_all!(splat, "`#[splat] fn` is incomplete", "call as func((a, ...)) instead"); if !visitor.features.never_patterns() { if let Some(spans) = spans.get(&sym::never_patterns) { diff --git a/compiler/rustc_attr_parsing/src/attributes/mod.rs b/compiler/rustc_attr_parsing/src/attributes/mod.rs index 67147642921c2..d4061d13d0d19 100644 --- a/compiler/rustc_attr_parsing/src/attributes/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/mod.rs @@ -63,6 +63,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..89fe6cc29aee0 --- /dev/null +++ b/compiler/rustc_attr_parsing/src/attributes/splat.rs @@ -0,0 +1,17 @@ +//! 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(&[ + Allow(Target::Param), + // FIXME(splat): only allow MacroCall if the macro creates an argument + Allow(Target::MacroCall), + ]); + 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 259a73de59853..2220d06c50d35 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -53,6 +53,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::*; @@ -327,6 +328,7 @@ attribute_parsers!( Single>, Single>, Single>, + Single>, Single>, Single>, // tidy-alphabetical-end diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs index dcbd7f7e7708c..6945c7f816624 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 5c14d6f2c0930..425e162c6a89c 100644 --- a/compiler/rustc_codegen_ssa/src/mir/mod.rs +++ b/compiler/rustc_codegen_ssa/src/mir/mod.rs @@ -405,6 +405,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/interpret/call.rs b/compiler/rustc_const_eval/src/interpret/call.rs index 802aa9ef4645a..febd084e95f92 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -651,6 +651,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 acbcba90fbcc0..b6debd9d740e9 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -905,6 +905,15 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ EncodeCrossCrate::Yes, 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, Normal, template!(Word), WarnFollowing, + EncodeCrossCrate::Yes, 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 224b7273314b5..24dcb1d71c786 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -670,6 +670,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 a18ddff947099..e864fd8e2e205 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -1603,6 +1603,9 @@ pub enum AttributeKind { span: Span, }, + /// 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 c19fc6976c6e6..13f78df0283b2 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -190,6 +190,7 @@ impl AttributeKind { RustcUnsafeSpecializationMarker(..) => No, Sanitize { .. } => No, ShouldPanic { .. } => No, + Splat(..) => Yes, Stability { .. } => Yes, TargetFeature { .. } => No, TestRunner(..) => Yes, diff --git a/compiler/rustc_hir_analysis/src/check/entry.rs b/compiler/rustc_hir_analysis/src/check/entry.rs index a6dae521db884..c325b850e8fa6 100644 --- a/compiler/rustc_hir_analysis/src/check/entry.rs +++ b/compiler/rustc_hir_analysis/src/check/entry.rs @@ -98,6 +98,7 @@ fn check_main_fn_ty(tcx: TyCtxt<'_>, main_def_id: DefId) { error = true; } + // FIXME(splat): also reject `#[splat]` on main function arguments if let Some(attr_span) = find_attr!(tcx, main_def_id, TrackCaller(span) => *span) { tcx.dcx().emit_err(errors::TrackCallerOnMain { span: attr_span, annotated: main_span }); error = true; diff --git a/compiler/rustc_mir_build/src/builder/mod.rs b/compiler/rustc_mir_build/src/builder/mod.rs index 01c1e2e79b501..163af7d70805e 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): so does splat Some(Local::new(arguments.len())) } else { None diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 26ba2b0e8f42d..d046d4eeba86c 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -371,6 +371,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { | AttributeKind::RustcTrivialFieldReads | AttributeKind::RustcUnsafeSpecializationMarker(..) | AttributeKind::ShouldPanic { .. } + | AttributeKind::Splat(..) | AttributeKind::Stability { .. } | AttributeKind::TestRunner(..) | AttributeKind::ThreadLocal diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index f5a8f1d7ebad4..fae520ae0f402 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1928,6 +1928,7 @@ symbols! { specialization, speed, spirv, + splat, spotlight, sqrtf16, sqrtf32, diff --git a/tests/ui/README.md b/tests/ui/README.md index eb14039d8151a..6eddc6b2bee2a 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-non-function.rs b/tests/ui/splat/splat-non-function.rs new file mode 100644 index 0000000000000..a9e2b280a511d --- /dev/null +++ b/tests/ui/splat/splat-non-function.rs @@ -0,0 +1,90 @@ +#![allow(incomplete_features)] +#![feature(splat)] + +fn tuple_args(#[splat] (a, b): (u32, i8)) {} + +#[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)); +} + +trait FooTrait { + fn tuple_1(#[splat] _: (u32,)); + + fn tuple_4(#[splat] 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,)) {} + + fn tuple_4_bad(self, (a, b, c, d): (u32, i8, (), f32)) -> u32 { + a + } +} + +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 + } + + fn tuple_1(#[splat] (a,): (u32,)) {} + + // FIXME(splat): this should error except when `self` (or any splatted arg) is a tuple. + // Tuple structs should also error until we have a specific use case for them, and so should + // multiple splats in a fn. + fn tuple_2_self(#[splat] self, (a, b): (u32, i8)) -> u32 { + a + } + + fn tuple_3(#[splat] (a, b, c): (u32, i32, i8)) {} + + 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 + } +} + +impl FooTrait for Foo { + // FIXME(splat): should conflicting splat attributes be allowed on traits and impls? + fn tuple_1(_: (u32,)) {} + + fn tuple_4(#[splat] self, _: (u32, i8, (), f32)) {} +} + +#[splat] //~ ERROR `#[splat]` attribute cannot be used on foreign modules +extern "C" { + fn foo_2(_: (u32, i8)); +} + +extern "C" { + fn bar_2(#[splat] _: (u32, i8)); + + #[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-function.stderr b/tests/ui/splat/splat-non-function.stderr new file mode 100644 index 0000000000000..bb7fc6bf5c84f --- /dev/null +++ b/tests/ui/splat/splat-non-function.stderr @@ -0,0 +1,82 @@ +error: `#[splat]` attribute cannot be used on functions + --> $DIR/splat-non-function.rs:6:1 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can be applied to function params and macro calls + +error: `#[splat]` attribute cannot be used on traits + --> $DIR/splat-non-function.rs:9:1 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can be applied to function params and macro calls + +error: `#[splat]` attribute cannot be used on inherent impl blocks + --> $DIR/splat-non-function.rs:24:1 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can be applied to function params and macro calls + +error: `#[splat]` attribute cannot be used on inherent methods + --> $DIR/splat-non-function.rs:34:5 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can be applied to function params and macro calls + +error: `#[splat]` attribute cannot be used on inherent methods + --> $DIR/splat-non-function.rs:37:5 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can be applied to function params and macro calls + +error: `#[splat]` attribute cannot be used on foreign modules + --> $DIR/splat-non-function.rs:69:1 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can be applied to function params and macro calls + +error: `#[splat]` attribute cannot be used on foreign functions + --> $DIR/splat-non-function.rs:77:5 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can be applied to function params and macro calls + +error: `#[splat]` attribute cannot be used on modules + --> $DIR/splat-non-function.rs:81:1 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can be applied to function params and macro calls + +error: `#[splat]` attribute cannot be used on use statements + --> $DIR/splat-non-function.rs:84:1 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can be applied to function params and macro calls + +error: `#[splat]` attribute cannot be used on structs + --> $DIR/splat-non-function.rs:87:1 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can be applied to function params and macro calls + +error: aborting due to 10 previous errors + From a9893589df2bb688c806c094a58c3a4d7b2147bb Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 18 Mar 2026 18:09:57 +1000 Subject: [PATCH 2/5] Plumb arg splatting through to typecheck Changing the function header also modifies the legacy symbol name encoding --- compiler/rustc_ast/src/ast.rs | 3 ++ compiler/rustc_ast_lowering/src/delegation.rs | 21 +++++++---- compiler/rustc_ast_lowering/src/expr.rs | 1 + compiler/rustc_ast_lowering/src/lib.rs | 2 ++ .../src/diagnostics/region_errors.rs | 1 + .../src/type_check/input_output.rs | 1 + .../src/value_and_place.rs | 7 ++++ .../rustc_codegen_gcc/src/intrinsic/mod.rs | 3 ++ compiler/rustc_codegen_llvm/src/intrinsic.rs | 3 ++ .../src/const_eval/type_info.rs | 5 ++- compiler/rustc_hir/src/hir.rs | 2 ++ compiler/rustc_hir/src/intravisit.rs | 10 ++++-- .../rustc_hir_analysis/src/check/entry.rs | 1 + .../rustc_hir_analysis/src/check/intrinsic.rs | 4 ++- compiler/rustc_hir_analysis/src/collect.rs | 3 +- .../src/hir_ty_lowering/mod.rs | 3 +- compiler/rustc_hir_pretty/src/lib.rs | 6 ++++ compiler/rustc_hir_typeck/src/callee.rs | 1 + compiler/rustc_hir_typeck/src/check.rs | 19 ++++++++-- compiler/rustc_hir_typeck/src/closure.rs | 9 +++++ compiler/rustc_hir_typeck/src/upvar.rs | 1 + compiler/rustc_middle/src/ty/context.rs | 4 ++- compiler/rustc_middle/src/ty/error.rs | 7 ++++ compiler/rustc_mir_transform/src/shim.rs | 1 + .../src/shim/async_destructor_ctor.rs | 2 ++ compiler/rustc_passes/src/check_attr.rs | 1 + compiler/rustc_public/src/ty.rs | 1 + .../src/unstable/convert/internal.rs | 1 + .../src/unstable/convert/stable/ty.rs | 1 + .../src/handle_cycle_error.rs | 1 + .../traits/fulfillment_errors.rs | 2 ++ .../src/error_reporting/traits/suggestions.rs | 2 ++ .../src/traits/project.rs | 1 + compiler/rustc_ty_utils/src/abi.rs | 5 +++ compiler/rustc_type_ir/src/error.rs | 3 +- compiler/rustc_type_ir/src/relate.rs | 5 +++ compiler/rustc_type_ir/src/ty_kind.rs | 36 +++++++++++++++++-- compiler/rustc_type_ir/src/ty_kind/closure.rs | 3 ++ .../clippy/clippy_lints/src/eta_reduction.rs | 3 +- src/tools/miri/src/bin/miri.rs | 1 + src/tools/miri/src/helpers.rs | 1 + src/tools/miri/src/shims/sig.rs | 1 + .../rust-analyzer/crates/hir-ty/src/lib.rs | 1 + .../rust-analyzer/crates/hir-ty/src/lower.rs | 1 + tests/ui/symbol-names/basic.legacy.stderr | 4 +-- .../ui/symbol-names/issue-60925.legacy.stderr | 4 +-- 46 files changed, 174 insertions(+), 24 deletions(-) diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 024624cd3bb87..5f3d4af92c4b6 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -3057,6 +3057,9 @@ impl FnDecl { pub fn c_variadic(&self) -> bool { self.inputs.last().is_some_and(|arg| matches!(arg.ty.kind, TyKind::CVarArgs)) } + pub fn splatted(&self) -> bool { + self.inputs.last().is_some_and(|arg| arg.attrs.iter().any(|attr| attr.has_name(sym::splat))) + } } /// 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 022f9e3c83f18..1da35991cabca 100644 --- a/compiler/rustc_ast_lowering/src/delegation.rs +++ b/compiler/rustc_ast_lowering/src/delegation.rs @@ -144,7 +144,7 @@ impl<'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'_, 'hir, R> { let is_method = self.is_method(sig_id, span); - let (param_count, c_variadic) = self.param_count(sig_id); + let (param_count, c_variadic, splatted) = self.param_count(sig_id); let mut generics = self.uplift_delegation_generics(delegation, sig_id, item_id); @@ -156,8 +156,14 @@ impl<'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'_, 'hir, R> { span, ); - let decl = - self.lower_delegation_decl(sig_id, param_count, c_variadic, span, &generics); + let decl = self.lower_delegation_decl( + sig_id, + param_count, + c_variadic, + splatted, + span, + &generics, + ); let sig = self.lower_delegation_sig(sig_id, decl, span); let ident = self.lower_ident(delegation.ident); @@ -271,10 +277,10 @@ impl<'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'_, 'hir, R> { self.resolver.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*/) { + // Function parameter count, including C variadic `...` and `#[splat]` if present. + fn param_count(&self, def_id: DefId) -> (usize, bool /*c_variadic*/, bool /*splatted*/) { let sig = self.tcx.fn_sig(def_id).skip_binder().skip_binder(); - (sig.inputs().len() + usize::from(sig.c_variadic), sig.c_variadic) + (sig.inputs().len() + usize::from(sig.c_variadic), sig.c_variadic, sig.splatted) } fn lower_delegation_decl( @@ -282,6 +288,7 @@ impl<'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'_, 'hir, R> { sig_id: DefId, param_count: usize, c_variadic: bool, + splatted: bool, span: Span, generics: &GenericsGenerationResults<'hir>, ) -> &'hir hir::FnDecl<'hir> { @@ -313,6 +320,7 @@ impl<'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'_, 'hir, R> { inputs, output: hir::FnRetTy::Return(output), c_variadic, + splatted, lifetime_elision_allowed: true, implicit_self: hir::ImplicitSelfKind::None, }) @@ -612,6 +620,7 @@ impl<'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'_, 'hir, R> { inputs: &[], output: hir::FnRetTy::DefaultReturn(span), c_variadic: false, + splatted: false, lifetime_elision_allowed: true, implicit_self: hir::ImplicitSelfKind::None, }); diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index b6bc122051cbc..d49e9ed5aac4a 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -763,6 +763,7 @@ impl<'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'_, 'hir, R> { inputs, output, c_variadic: false, + splatted: false, implicit_self: hir::ImplicitSelfKind::None, lifetime_elision_allowed: false, }); diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 5fcc8f0161194..46f9c4dea8f1e 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -1826,6 +1826,7 @@ impl<'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'_, 'hir, R> { coro: Option, ) -> &'hir hir::FnDecl<'hir> { let c_variadic = decl.c_variadic(); + let splatted = decl.splatted(); // Skip the `...` (`CVarArgs`) trailing arguments from the AST, // as they are not explicit in HIR/Ty function signatures. @@ -1898,6 +1899,7 @@ impl<'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'_, 'hir, R> { inputs, output, c_variadic, + splatted, lifetime_elision_allowed: self.resolver.lifetime_elision_allowed(fn_node_id), implicit_self: decl.inputs.get(0).map_or(hir::ImplicitSelfKind::None, |arg| { let is_mutable_pat = matches!( diff --git a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs index a3738689ed05f..6f67453e627e4 100644 --- a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs @@ -1087,6 +1087,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { liberated_sig.inputs().iter().copied(), peeled_ty, liberated_sig.c_variadic, + liberated_sig.splatted, hir::Safety::Safe, rustc_abi::ExternAbi::Rust, )), diff --git a/compiler/rustc_borrowck/src/type_check/input_output.rs b/compiler/rustc_borrowck/src/type_check/input_output.rs index 4e762b368496d..a3fb1c8368dbc 100644 --- a/compiler/rustc_borrowck/src/type_check/input_output.rs +++ b/compiler/rustc_borrowck/src/type_check/input_output.rs @@ -95,6 +95,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { user_provided_sig.inputs().iter().copied(), output_ty, user_provided_sig.c_variadic, + user_provided_sig.splatted, user_provided_sig.safety, user_provided_sig.abi, ); diff --git a/compiler/rustc_codegen_cranelift/src/value_and_place.rs b/compiler/rustc_codegen_cranelift/src/value_and_place.rs index 5b76a4cb97793..1743bf9850c19 100644 --- a/compiler/rustc_codegen_cranelift/src/value_and_place.rs +++ b/compiler/rustc_codegen_cranelift/src/value_and_place.rs @@ -873,6 +873,7 @@ pub(crate) fn assert_assignable<'tcx>( let FnSig { inputs_and_output: types_from, c_variadic: c_variadic_from, + splatted: splatted_from, safety: unsafety_from, abi: abi_from, } = from_sig; @@ -881,6 +882,7 @@ pub(crate) fn assert_assignable<'tcx>( let FnSig { inputs_and_output: types_to, c_variadic: c_variadic_to, + splatted: splatted_to, safety: unsafety_to, abi: abi_to, } = to_sig; @@ -898,6 +900,11 @@ pub(crate) fn assert_assignable<'tcx>( "Can't write fn ptr with incompatible sig {:?} to place with sig {:?}\n\n{:#?}", from_sig, to_sig, fx, ); + assert_eq!( + splatted_from, splatted_to, + "Can't write fn ptr with incompatible sig {:?} to place with sig {:?}\n\n{:#?}", + from_sig, to_sig, fx, + ); assert_eq!( unsafety_from, unsafety_to, "Can't write fn ptr with incompatible sig {:?} to place with sig {:?}\n\n{:#?}", diff --git a/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs b/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs index 3f1b33c73e638..36675988b03eb 100644 --- a/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs +++ b/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs @@ -1472,6 +1472,7 @@ fn get_rust_try_fn<'a, 'gcc, 'tcx>( iter::once(i8p), tcx.types.unit, false, + false, rustc_hir::Safety::Unsafe, ExternAbi::Rust, )), @@ -1483,6 +1484,7 @@ fn get_rust_try_fn<'a, 'gcc, 'tcx>( [i8p, i8p].iter().cloned(), tcx.types.unit, false, + false, rustc_hir::Safety::Unsafe, ExternAbi::Rust, )), @@ -1492,6 +1494,7 @@ fn get_rust_try_fn<'a, 'gcc, 'tcx>( [try_fn_ty, i8p, catch_fn_ty], tcx.types.i32, false, + false, rustc_hir::Safety::Unsafe, ExternAbi::Rust, )); diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index cc6ecee60b0e4..5f6602f85cdca 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -1279,6 +1279,7 @@ fn get_rust_try_fn<'a, 'll, 'tcx>( [i8p], tcx.types.unit, false, + false, hir::Safety::Unsafe, ExternAbi::Rust, )), @@ -1290,6 +1291,7 @@ fn get_rust_try_fn<'a, 'll, 'tcx>( [i8p, i8p], tcx.types.unit, false, + false, hir::Safety::Unsafe, ExternAbi::Rust, )), @@ -1299,6 +1301,7 @@ fn get_rust_try_fn<'a, 'll, 'tcx>( [try_fn_ty, i8p, catch_fn_ty], tcx.types.i32, false, + false, hir::Safety::Unsafe, ExternAbi::Rust, )); 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 0ed04a5ab20b4..05bcaf69d7296 100644 --- a/compiler/rustc_const_eval/src/const_eval/type_info.rs +++ b/compiler/rustc_const_eval/src/const_eval/type_info.rs @@ -419,7 +419,7 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> { sig: &FnSigTys>, fn_header: &FnHeader>, ) -> InterpResult<'tcx> { - let FnHeader { safety, c_variadic, abi } = fn_header; + let FnHeader { safety, c_variadic, splatted, abi } = fn_header; for (field_idx, field) in place.layout().ty.ty_adt_def().unwrap().non_enum_variant().fields.iter_enumerated() @@ -465,6 +465,9 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> { sym::variadic => { self.write_scalar(Scalar::from_bool(*c_variadic), &field_place)?; } + sym::splat => { + self.write_scalar(Scalar::from_bool(*splatted), &field_place)?; + } other => span_bug!(self.tcx.def_span(field.did), "unimplemented field {other}"), } } diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 57cf42cc54794..4cf90cc33aa69 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -3926,6 +3926,8 @@ pub struct FnDecl<'hir> { pub inputs: &'hir [Ty<'hir>], pub output: FnRetTy<'hir>, pub c_variadic: bool, + /// Is the last argument of the function splatted into multiple arguments in callers? + pub splatted: bool, /// Does the function have an implicit self? pub implicit_self: ImplicitSelfKind, /// Is lifetime elision allowed. diff --git a/compiler/rustc_hir/src/intravisit.rs b/compiler/rustc_hir/src/intravisit.rs index 25ef56f8b0f2c..f180724892873 100644 --- a/compiler/rustc_hir/src/intravisit.rs +++ b/compiler/rustc_hir/src/intravisit.rs @@ -1212,8 +1212,14 @@ pub fn walk_fn_decl<'v, V: Visitor<'v>>( visitor: &mut V, function_declaration: &'v FnDecl<'v>, ) -> V::Result { - let FnDecl { inputs, output, c_variadic: _, implicit_self: _, lifetime_elision_allowed: _ } = - function_declaration; + let FnDecl { + inputs, + output, + c_variadic: _, + splatted: _, + implicit_self: _, + lifetime_elision_allowed: _, + } = function_declaration; walk_list!(visitor, visit_ty_unambig, *inputs); visitor.visit_fn_ret_ty(output) } diff --git a/compiler/rustc_hir_analysis/src/check/entry.rs b/compiler/rustc_hir_analysis/src/check/entry.rs index c325b850e8fa6..ef31bc1320d88 100644 --- a/compiler/rustc_hir_analysis/src/check/entry.rs +++ b/compiler/rustc_hir_analysis/src/check/entry.rs @@ -156,6 +156,7 @@ fn check_main_fn_ty(tcx: TyCtxt<'_>, main_def_id: DefId) { [], expected_return_type, false, + false, hir::Safety::Safe, ExternAbi::Rust, )); diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index 47420997a509a..a8d5ade97d290 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -646,6 +646,7 @@ pub(crate) fn check_intrinsic_type( [mut_u8], tcx.types.unit, false, + false, hir::Safety::Safe, ExternAbi::Rust, )); @@ -653,6 +654,7 @@ pub(crate) fn check_intrinsic_type( [mut_u8, mut_u8], tcx.types.unit, false, + false, hir::Safety::Safe, ExternAbi::Rust, )); @@ -816,7 +818,7 @@ pub(crate) fn check_intrinsic_type( return; } }; - let sig = tcx.mk_fn_sig(inputs, output, false, safety, ExternAbi::Rust); + let sig = tcx.mk_fn_sig(inputs, output, false, false, safety, ExternAbi::Rust); let sig = ty::Binder::bind_with_vars(sig, bound_vars); equate_intrinsic_type(tcx, span, intrinsic_id, n_tps, n_lts, n_cts, sig) } diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index 80ef2001cc72e..37232e4a56668 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -1025,7 +1025,7 @@ fn fn_sig(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::EarlyBinder<'_, ty::PolyFn (Bound::Unbounded, Bound::Unbounded) => hir::Safety::Safe, _ => hir::Safety::Unsafe, }; - ty::Binder::dummy(tcx.mk_fn_sig(inputs, ty, false, safety, ExternAbi::Rust)) + ty::Binder::dummy(tcx.mk_fn_sig(inputs, ty, false, false, safety, ExternAbi::Rust)) } Expr(&hir::Expr { kind: hir::ExprKind::Closure { .. }, .. }) => { @@ -1219,6 +1219,7 @@ fn recover_infer_ret_ty<'tcx>( fn_sig.inputs().iter().copied(), recovered_ret_ty.unwrap_or_else(|| Ty::new_error(tcx, guar)), fn_sig.c_variadic, + fn_sig.splatted, fn_sig.safety, fn_sig.abi, ); 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 91660fc655377..4d7e5f849fe0f 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs @@ -3485,7 +3485,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { debug!(?output_ty); - let fn_ty = tcx.mk_fn_sig(input_tys, output_ty, decl.c_variadic, safety, abi); + let fn_ty = + tcx.mk_fn_sig(input_tys, output_ty, decl.c_variadic, decl.splatted, safety, abi); let fn_ptr_ty = ty::Binder::bind_with_vars(fn_ty, bound_vars); if let hir::Node::Ty(hir::Ty { kind: hir::TyKind::FnPtr(fn_ptr_ty), span, .. }) = diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs index 82540a9327410..a9f2b07292728 100644 --- a/compiler/rustc_hir_pretty/src/lib.rs +++ b/compiler/rustc_hir_pretty/src/lib.rs @@ -2265,6 +2265,9 @@ impl<'a> State<'a> { if i == 0 && decl.implicit_self.has_implicit_self() { s.print_implicit_self(&decl.implicit_self); } else { + if decl.splatted && Some(i) == decl.inputs.len().checked_sub(1) { + s.word("#[splat]"); + } if let Some(arg_ident) = arg_idents.get(i) { if let Some(arg_ident) = arg_ident { s.word(arg_ident.to_string()); @@ -2294,6 +2297,9 @@ impl<'a> State<'a> { print_arg(self, None); self.word("..."); } + if decl.splatted && decl.inputs.is_empty() { + self.word("#[splat] ()"); + } self.pclose(); self.print_fn_output(decl); diff --git a/compiler/rustc_hir_typeck/src/callee.rs b/compiler/rustc_hir_typeck/src/callee.rs index 93662b36a05df..8a514a746aa07 100644 --- a/compiler/rustc_hir_typeck/src/callee.rs +++ b/compiler/rustc_hir_typeck/src/callee.rs @@ -276,6 +276,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { tupled_upvars_ty, ), coroutine_closure_sig.c_variadic, + coroutine_closure_sig.splatted, coroutine_closure_sig.safety, coroutine_closure_sig.abi, ); diff --git a/compiler/rustc_hir_typeck/src/check.rs b/compiler/rustc_hir_typeck/src/check.rs index 612396858841f..a0e9eb1ce8449 100644 --- a/compiler/rustc_hir_typeck/src/check.rs +++ b/compiler/rustc_hir_typeck/src/check.rs @@ -204,7 +204,14 @@ fn check_panic_info_fn(tcx: TyCtxt<'_>, fn_id: LocalDefId, fn_sig: ty::FnSig<'_> ty::BoundVariableKind::Region(ty::BoundRegionKind::Anon), ]); let expected_sig = ty::Binder::bind_with_vars( - tcx.mk_fn_sig([panic_info_ref_ty], tcx.types.never, false, fn_sig.safety, ExternAbi::Rust), + tcx.mk_fn_sig( + [panic_info_ref_ty], + tcx.types.never, + false, + false, + fn_sig.safety, + ExternAbi::Rust, + ), bounds, ); @@ -227,7 +234,14 @@ fn check_lang_start_fn<'tcx>(tcx: TyCtxt<'tcx>, fn_sig: ty::FnSig<'tcx>, def_id: let generic_ty = Ty::new_param(tcx, fn_generic.index, fn_generic.name); let main_fn_ty = Ty::new_fn_ptr( tcx, - Binder::dummy(tcx.mk_fn_sig([], generic_ty, false, hir::Safety::Safe, ExternAbi::Rust)), + Binder::dummy(tcx.mk_fn_sig( + [], + generic_ty, + false, + false, + hir::Safety::Safe, + ExternAbi::Rust, + )), ); let expected_sig = ty::Binder::dummy(tcx.mk_fn_sig( @@ -239,6 +253,7 @@ fn check_lang_start_fn<'tcx>(tcx: TyCtxt<'tcx>, fn_sig: ty::FnSig<'tcx>, def_id: ], tcx.types.isize, false, + false, fn_sig.safety, ExternAbi::Rust, )); diff --git a/compiler/rustc_hir_typeck/src/closure.rs b/compiler/rustc_hir_typeck/src/closure.rs index 82b7c578a1f25..fda93301f7adf 100644 --- a/compiler/rustc_hir_typeck/src/closure.rs +++ b/compiler/rustc_hir_typeck/src/closure.rs @@ -89,6 +89,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { [Ty::new_tup(tcx, sig.inputs())], sig.output(), sig.c_variadic, + sig.splatted, sig.safety, sig.abi, ) @@ -232,6 +233,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ], Ty::new_tup(tcx, &[bound_yield_ty, bound_return_ty]), sig.c_variadic, + sig.splatted, sig.safety, sig.abi, ) @@ -274,6 +276,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { liberated_sig.inputs().iter().copied(), coroutine_output_ty, liberated_sig.c_variadic, + liberated_sig.splatted, liberated_sig.safety, liberated_sig.abi, ); @@ -548,6 +551,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { input_tys, ret_param_ty, false, + false, hir::Safety::Safe, ExternAbi::Rust, )); @@ -634,6 +638,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { input_tys, return_ty, false, + false, hir::Safety::Safe, ExternAbi::Rust, )); @@ -746,6 +751,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { sig.inputs().iter().cloned(), sig.output(), sig.c_variadic, + sig.splatted, hir::Safety::Safe, ExternAbi::RustCall, ) @@ -885,6 +891,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { inputs, supplied_output_ty, expected_sigs.liberated_sig.c_variadic, + expected_sigs.liberated_sig.splatted, hir::Safety::Safe, ExternAbi::RustCall, ); @@ -962,6 +969,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { supplied_arguments, supplied_return, decl.c_variadic, + decl.splatted, hir::Safety::Safe, ExternAbi::RustCall, ), @@ -1125,6 +1133,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { supplied_arguments, err_ty, decl.c_variadic, + decl.splatted, hir::Safety::Safe, ExternAbi::RustCall, )); diff --git a/compiler/rustc_hir_typeck/src/upvar.rs b/compiler/rustc_hir_typeck/src/upvar.rs index df02974d2fb2d..3759562908ef7 100644 --- a/compiler/rustc_hir_typeck/src/upvar.rs +++ b/compiler/rustc_hir_typeck/src/upvar.rs @@ -442,6 +442,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { [], tupled_upvars_ty_for_borrow, false, + false, hir::Safety::Safe, rustc_abi::ExternAbi::Rust, ), diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index 58a2edca8ecef..0f4c90c2e2d0a 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -2144,7 +2144,7 @@ impl<'tcx> TyCtxt<'tcx> { ty::Tuple(params) => *params, _ => bug!(), }; - self.mk_fn_sig(params, s.output(), s.c_variadic, safety, ExternAbi::Rust) + self.mk_fn_sig(params, s.output(), s.c_variadic, s.splatted, safety, ExternAbi::Rust) }) } @@ -2422,6 +2422,7 @@ impl<'tcx> TyCtxt<'tcx> { inputs: I, output: I::Item, c_variadic: bool, + splatted: bool, safety: hir::Safety, abi: ExternAbi, ) -> T::Output @@ -2432,6 +2433,7 @@ impl<'tcx> TyCtxt<'tcx> { T::collect_and_apply(inputs.into_iter().chain(iter::once(output)), |xs| ty::FnSig { inputs_and_output: self.mk_type_list(xs), c_variadic, + splatted, safety, abi, }) diff --git a/compiler/rustc_middle/src/ty/error.rs b/compiler/rustc_middle/src/ty/error.rs index 66542525d2841..353e510c57279 100644 --- a/compiler/rustc_middle/src/ty/error.rs +++ b/compiler/rustc_middle/src/ty/error.rs @@ -96,6 +96,13 @@ impl<'tcx> TypeError<'tcx> { if values.found { "variadic" } else { "non-variadic" } ) .into(), + // FIXME(splat): for now, we assume splat is the last argument + TypeError::SplatMismatch(ref values) => format!( + "expected {} fn, found {} function", + if values.expected { "splatted" } else { "non-splatted" }, + if values.found { "splatted" } else { "non-splatted" } + ) + .into(), TypeError::ProjectionMismatched(ref values) => format!( "expected `{}`, found `{}`", tcx.def_path_str(values.expected), diff --git a/compiler/rustc_mir_transform/src/shim.rs b/compiler/rustc_mir_transform/src/shim.rs index 4fd0629befecf..326d61f2a7758 100644 --- a/compiler/rustc_mir_transform/src/shim.rs +++ b/compiler/rustc_mir_transform/src/shim.rs @@ -1172,6 +1172,7 @@ fn build_construct_coroutine_by_move_shim<'tcx>( args.as_coroutine_closure().coroutine_captures_by_ref_ty(), ), sig.c_variadic, + sig.splatted, sig.safety, sig.abi, ) diff --git a/compiler/rustc_mir_transform/src/shim/async_destructor_ctor.rs b/compiler/rustc_mir_transform/src/shim/async_destructor_ctor.rs index a0f1260cd986d..4c9eeeb865652 100644 --- a/compiler/rustc_mir_transform/src/shim/async_destructor_ctor.rs +++ b/compiler/rustc_mir_transform/src/shim/async_destructor_ctor.rs @@ -71,6 +71,7 @@ pub(super) fn build_async_drop_shim<'tcx>( [ty, resume_ty], tcx.types.unit, false, + false, Safety::Safe, ExternAbi::Rust, )); @@ -314,6 +315,7 @@ fn build_adrop_for_adrop_shim<'tcx>( [env_ty, Ty::new_task_context(tcx)], ret_ty, false, + false, hir::Safety::Safe, ExternAbi::Rust, ); diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index d046d4eeba86c..bb0de435e37c8 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -1733,6 +1733,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { ), token_stream, false, + false, Safety::Safe, ExternAbi::Rust, ); diff --git a/compiler/rustc_public/src/ty.rs b/compiler/rustc_public/src/ty.rs index 7ec8b688402ad..f2cf8c188745e 100644 --- a/compiler/rustc_public/src/ty.rs +++ b/compiler/rustc_public/src/ty.rs @@ -1089,6 +1089,7 @@ impl PolyFnSig { pub struct FnSig { pub inputs_and_output: Vec, pub c_variadic: bool, + pub splatted: bool, pub safety: Safety, pub abi: Abi, } diff --git a/compiler/rustc_public/src/unstable/convert/internal.rs b/compiler/rustc_public/src/unstable/convert/internal.rs index 8594f65100415..713299e5fb93b 100644 --- a/compiler/rustc_public/src/unstable/convert/internal.rs +++ b/compiler/rustc_public/src/unstable/convert/internal.rs @@ -311,6 +311,7 @@ impl RustcInternal for FnSig { tcx.lift(rustc_ty::FnSig { inputs_and_output: tcx.mk_type_list(&self.inputs_and_output.internal(tables, tcx)), c_variadic: self.c_variadic, + splatted: self.splatted, safety: self.safety.internal(tables, tcx), abi: self.abi.internal(tables, tcx), }) diff --git a/compiler/rustc_public/src/unstable/convert/stable/ty.rs b/compiler/rustc_public/src/unstable/convert/stable/ty.rs index 31cc6bd46959e..e8a6e5ce6f781 100644 --- a/compiler/rustc_public/src/unstable/convert/stable/ty.rs +++ b/compiler/rustc_public/src/unstable/convert/stable/ty.rs @@ -265,6 +265,7 @@ impl<'tcx> Stable<'tcx> for ty::FnSig<'tcx> { .map(|ty| ty.stable(tables, cx)) .collect(), c_variadic: self.c_variadic, + splatted: self.splatted, safety: self.safety.stable(tables, cx), abi: self.abi.stable(tables, cx), } diff --git a/compiler/rustc_query_impl/src/handle_cycle_error.rs b/compiler/rustc_query_impl/src/handle_cycle_error.rs index 5676669bf1c0e..b6c72fa4681e9 100644 --- a/compiler/rustc_query_impl/src/handle_cycle_error.rs +++ b/compiler/rustc_query_impl/src/handle_cycle_error.rs @@ -67,6 +67,7 @@ fn fn_sig<'tcx>( std::iter::repeat_n(err, arity), err, false, + false, rustc_hir::Safety::Safe, rustc_abi::ExternAbi::Rust, ))) diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs index eeccf1eb0ecfa..2ff7c878e0ac6 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs @@ -3274,6 +3274,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { given, self.tcx.types.unit, false, + false, hir::Safety::Safe, ExternAbi::Rust, )), @@ -3284,6 +3285,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { expected, self.tcx.types.unit, false, + false, hir::Safety::Safe, ExternAbi::Rust, )), 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 4a6d5eb48f8f1..41b1a0674e5b2 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -2064,6 +2064,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { *inputs, infcx.next_ty_var(DUMMY_SP), false, + false, hir::Safety::Safe, ExternAbi::Rust, ) @@ -2072,6 +2073,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { [inputs], infcx.next_ty_var(DUMMY_SP), false, + false, hir::Safety::Safe, ExternAbi::Rust, ), diff --git a/compiler/rustc_trait_selection/src/traits/project.rs b/compiler/rustc_trait_selection/src/traits/project.rs index 3df5c9e33438a..cad5ba6a13db7 100644 --- a/compiler/rustc_trait_selection/src/traits/project.rs +++ b/compiler/rustc_trait_selection/src/traits/project.rs @@ -1657,6 +1657,7 @@ fn confirm_closure_candidate<'cx, 'tcx>( [sig.tupled_inputs_ty], output_ty, sig.c_variadic, + sig.splatted, sig.safety, sig.abi, ) diff --git a/compiler/rustc_ty_utils/src/abi.rs b/compiler/rustc_ty_utils/src/abi.rs index 5008794bcb191..3991afb2c2e99 100644 --- a/compiler/rustc_ty_utils/src/abi.rs +++ b/compiler/rustc_ty_utils/src/abi.rs @@ -43,6 +43,7 @@ fn fn_sig_for_fn_abi<'tcx>( [], tcx.thread_local_ptr_ty(instance.def_id()), false, + false, hir::Safety::Safe, rustc_abi::ExternAbi::Rust, ); @@ -75,6 +76,7 @@ fn fn_sig_for_fn_abi<'tcx>( iter::once(env_ty).chain(sig.inputs().iter().cloned()), sig.output(), sig.c_variadic, + sig.splatted, sig.safety, sig.abi, ) @@ -120,6 +122,7 @@ fn fn_sig_for_fn_abi<'tcx>( args.as_coroutine_closure().coroutine_captures_by_ref_ty(), ), sig.c_variadic, + sig.splatted, sig.safety, sig.abi, ) @@ -228,6 +231,7 @@ fn fn_sig_for_fn_abi<'tcx>( [env_ty, resume_ty], ret_ty, false, + false, hir::Safety::Safe, rustc_abi::ExternAbi::Rust, ) @@ -237,6 +241,7 @@ fn fn_sig_for_fn_abi<'tcx>( [env_ty], ret_ty, false, + false, hir::Safety::Safe, rustc_abi::ExternAbi::Rust, ) diff --git a/compiler/rustc_type_ir/src/error.rs b/compiler/rustc_type_ir/src/error.rs index eba4c7c6644ac..8415daa2dccdd 100644 --- a/compiler/rustc_type_ir/src/error.rs +++ b/compiler/rustc_type_ir/src/error.rs @@ -40,6 +40,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 @@ -75,7 +76,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 d33c6036dadd8..a1898dcc09ef4 100644 --- a/compiler/rustc_type_ir/src/relate.rs +++ b/compiler/rustc_type_ir/src/relate.rs @@ -161,6 +161,10 @@ impl Relate for ty::FnSig { ))); } + if a.splatted != b.splatted { + return Err(TypeError::SplatMismatch(ExpectedFound::new(a.splatted, b.splatted))); + } + if a.safety != b.safety { return Err(TypeError::SafetyMismatch(ExpectedFound::new(a.safety, b.safety))); } @@ -203,6 +207,7 @@ impl Relate for ty::FnSig { Ok(ty::FnSig { inputs_and_output: cx.mk_type_list_from_iter(inputs_and_output)?, c_variadic: a.c_variadic, + splatted: a.splatted, safety: a.safety, abi: a.abi, }) diff --git a/compiler/rustc_type_ir/src/ty_kind.rs b/compiler/rustc_type_ir/src/ty_kind.rs index 983d8f0820b6b..d2e9a1cdb7cd6 100644 --- a/compiler/rustc_type_ir/src/ty_kind.rs +++ b/compiler/rustc_type_ir/src/ty_kind.rs @@ -281,6 +281,7 @@ impl TyKind { ty::Binder::dummy(ty::FnSig { inputs_and_output: Default::default(), c_variadic: false, + splatted: false, safety: I::Safety::safe(), abi: I::Abi::rust(), }) @@ -750,6 +751,9 @@ impl Eq for TypeAndMut {} pub struct FnSig { pub inputs_and_output: I::Tys, pub c_variadic: bool, + /// Is the final argument of this function splatted? + /// FIXME(splat): combine this with c_variadic in an enum, they are mutually exclusive. + pub splatted: bool, #[type_visitable(ignore)] #[type_foldable(identity)] pub safety: I::Safety, @@ -773,6 +777,14 @@ impl FnSig { let FnSig { safety, abi, c_variadic, .. } = self; !c_variadic && safety.is_safe() && abi.is_rust() } + + pub fn splatted_arg_index(self) -> Option { + // A function with no inputs behaves the same regardless of splatting. + let last_index = self.inputs().len().checked_sub(1)?; + + debug_assert!(last_index <= u16::MAX as usize); + self.splatted.then_some(last_index as u16) + } } impl ty::Binder> { @@ -800,6 +812,10 @@ impl ty::Binder> { self.skip_binder().c_variadic } + pub fn splatted(self) -> bool { + self.skip_binder().splatted + } + pub fn safety(self) -> I::Safety { self.skip_binder().safety } @@ -814,8 +830,12 @@ impl ty::Binder> { // Used to split a single value into the two fields in `TyKind::FnPtr`. pub fn split(self) -> (ty::Binder>, FnHeader) { - let hdr = - FnHeader { c_variadic: self.c_variadic(), safety: self.safety(), abi: self.abi() }; + let hdr = FnHeader { + c_variadic: self.c_variadic(), + splatted: self.splatted(), + safety: self.safety(), + abi: self.abi(), + }; (self.map_bound(|sig| FnSigTys { inputs_and_output: sig.inputs_and_output }), hdr) } } @@ -823,7 +843,7 @@ impl ty::Binder> { impl fmt::Debug for FnSig { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let sig = self; - let FnSig { inputs_and_output: _, c_variadic, safety, abi } = sig; + let FnSig { inputs_and_output: _, c_variadic, splatted, safety, abi } = sig; write!(f, "{}", safety.prefix_str())?; if !abi.is_rust() { @@ -832,12 +852,20 @@ impl fmt::Debug for FnSig { write!(f, "fn(")?; let inputs = sig.inputs(); + // FIXME(splat): for now, we assume splat is the last argument + let splatted_arg_index = splatted.then(|| inputs.len().checked_sub(1)).flatten(); for (i, ty) in inputs.iter().enumerate() { if i > 0 { write!(f, ", ")?; } + if splatted_arg_index == Some(i) { + write!(f, "#[splat] ")?; + } write!(f, "{ty:?}")?; } + if *splatted && splatted_arg_index.is_none() { + write!(f, "#[splat] ()")?; + } if *c_variadic { if inputs.is_empty() { write!(f, "...")?; @@ -948,6 +976,7 @@ impl ty::Binder> { self.map_bound(|sig_tys| FnSig { inputs_and_output: sig_tys.inputs_and_output, c_variadic: hdr.c_variadic, + splatted: hdr.splatted, safety: hdr.safety, abi: hdr.abi, }) @@ -982,6 +1011,7 @@ impl ty::Binder> { #[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic, Lift_Generic)] pub struct FnHeader { pub c_variadic: bool, + pub splatted: bool, pub safety: I::Safety, pub abi: I::Abi, } diff --git a/compiler/rustc_type_ir/src/ty_kind/closure.rs b/compiler/rustc_type_ir/src/ty_kind/closure.rs index e8f94c8e7cc92..f2691feafe4f1 100644 --- a/compiler/rustc_type_ir/src/ty_kind/closure.rs +++ b/compiler/rustc_type_ir/src/ty_kind/closure.rs @@ -308,6 +308,7 @@ impl CoroutineClosureArgs { yield_ty, return_ty, c_variadic: hdr.c_variadic, + splatted: hdr.splatted, safety: hdr.safety, abi: hdr.abi, } @@ -368,6 +369,8 @@ pub struct CoroutineClosureSignature { // from scratch just for good measure. /// Always false pub c_variadic: bool, + // FIXME(splat): is this always false? + pub splatted: bool, /// Always `Normal` (safe) #[type_visitable(ignore)] #[type_foldable(identity)] diff --git a/src/tools/clippy/clippy_lints/src/eta_reduction.rs b/src/tools/clippy/clippy_lints/src/eta_reduction.rs index 3562200cbd929..a813ca2496d56 100644 --- a/src/tools/clippy/clippy_lints/src/eta_reduction.rs +++ b/src/tools/clippy/clippy_lints/src/eta_reduction.rs @@ -173,7 +173,8 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx && let output = typeck.expr_ty(body.value) && let ty::Tuple(tys) = *subs.type_at(1).kind() { - cx.tcx.mk_fn_sig(tys, output, false, Safety::Safe, ExternAbi::Rust) + cx.tcx + .mk_fn_sig(tys, output, false, false, Safety::Safe, ExternAbi::Rust) } else { return; } diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs index e73c870508bd0..0c6f49669e1da 100644 --- a/src/tools/miri/src/bin/miri.rs +++ b/src/tools/miri/src/bin/miri.rs @@ -102,6 +102,7 @@ fn entry_fn(tcx: TyCtxt<'_>) -> (DefId, MiriEntryFnType) { [tcx.types.isize, Ty::new_imm_ptr(tcx, Ty::new_imm_ptr(tcx, tcx.types.u8))], tcx.types.isize, false, + false, hir::Safety::Safe, ExternAbi::Rust, )); diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index a40ad4b55317f..d0c32896afd68 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -409,6 +409,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { args.iter().map(|a| a.layout.ty), dest.layout.ty, /*c_variadic*/ false, + /*splatted*/ false, Safety::Safe, caller_abi, ); diff --git a/src/tools/miri/src/shims/sig.rs b/src/tools/miri/src/shims/sig.rs index 43b913edbebf5..14443edfa0ed7 100644 --- a/src/tools/miri/src/shims/sig.rs +++ b/src/tools/miri/src/shims/sig.rs @@ -276,6 +276,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let fn_sig_binder = Binder::dummy(FnSig { inputs_and_output: this.machine.tcx.mk_type_list(&inputs_and_output), c_variadic: false, + splatted: false, // This does not matter for the ABI. safety: Safety::Safe, abi: shim_sig.abi, 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 8d87276a0b152..208fbd1aaee94 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs @@ -584,6 +584,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 49594f34fd732..64ef71dea35fa 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs @@ -513,6 +513,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/tests/ui/symbol-names/basic.legacy.stderr b/tests/ui/symbol-names/basic.legacy.stderr index a028f4331725e..f526be05a265d 100644 --- a/tests/ui/symbol-names/basic.legacy.stderr +++ b/tests/ui/symbol-names/basic.legacy.stderr @@ -1,10 +1,10 @@ -error: symbol-name(_ZN5basic4main17h1dddcfd03744167fE) +error: symbol-name(_ZN5basic4main17h2dcbca9790685248E) --> $DIR/basic.rs:8:1 | LL | #[rustc_symbol_name] | ^^^^^^^^^^^^^^^^^^^^ -error: demangling(basic::main::h1dddcfd03744167f) +error: demangling(basic::main::h2dcbca9790685248) --> $DIR/basic.rs:8:1 | LL | #[rustc_symbol_name] diff --git a/tests/ui/symbol-names/issue-60925.legacy.stderr b/tests/ui/symbol-names/issue-60925.legacy.stderr index 14cbd877d9f8a..cf2af0b4111fe 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$3foo17h4b3099ec5dc5d306E) +error: symbol-name(_ZN11issue_609253foo37Foo$LT$issue_60925..llv$u6d$..Foo$GT$3foo17h155b1200a8077536E) --> $DIR/issue-60925.rs:21:9 | LL | #[rustc_symbol_name] | ^^^^^^^^^^^^^^^^^^^^ -error: demangling(issue_60925::foo::Foo::foo::h4b3099ec5dc5d306) +error: demangling(issue_60925::foo::Foo::foo::h155b1200a8077536) --> $DIR/issue-60925.rs:21:9 | LL | #[rustc_symbol_name] From d5f4bfa7f5385d13d98567eb5286b0809fea4dc6 Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 18 Mar 2026 18:12:06 +1000 Subject: [PATCH 3/5] Impl typeck for argument splatting --- compiler/rustc_hir_typeck/src/callee.rs | 78 ++++++++++++- compiler/rustc_hir_typeck/src/errors.rs | 4 +- compiler/rustc_hir_typeck/src/expr.rs | 10 +- .../rustc_hir_typeck/src/fn_ctxt/checks.rs | 108 ++++++++++++++---- compiler/rustc_hir_typeck/src/lib.rs | 32 +++++- compiler/rustc_type_ir/src/ty_kind.rs | 8 -- ...at-non-function.rs => splat-non-fn-arg.rs} | 48 ++------ ...unction.stderr => splat-non-fn-arg.stderr} | 43 +++++-- tests/ui/splat/splat-non-tuple.rs | 78 +++++++++++++ tests/ui/splat/splat-overload-at-home.rs | 54 +++++++++ tests/ui/splat/splat-tuple.rs | 101 ++++++++++++++++ 11 files changed, 478 insertions(+), 86 deletions(-) rename tests/ui/splat/{splat-non-function.rs => splat-non-fn-arg.rs} (56%) rename tests/ui/splat/{splat-non-function.stderr => splat-non-fn-arg.stderr} (64%) create mode 100644 tests/ui/splat/splat-non-tuple.rs create mode 100644 tests/ui/splat/splat-overload-at-home.rs create mode 100644 tests/ui/splat/splat-tuple.rs diff --git a/compiler/rustc_hir_typeck/src/callee.rs b/compiler/rustc_hir_typeck/src/callee.rs index 8a514a746aa07..fdeeb933e47c5 100644 --- a/compiler/rustc_hir_typeck/src/callee.rs +++ b/compiler/rustc_hir_typeck/src/callee.rs @@ -53,12 +53,17 @@ 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. 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>), + /// Caller argument tupling, when callee uses `#[splat]`. + Splatted(Ty<'tcx>), } impl<'a, 'tcx> FnCtxt<'a, 'tcx> { @@ -147,6 +152,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { Some(CallStep::Overloaded(method_callee)) => { self.confirm_overloaded_call(call_expr, arg_exprs, expected, method_callee) } + + Some(CallStep::Splatted(callee_ty)) => { + self.confirm_splatted_call(call_expr, callee_ty, arg_exprs, expected) + } }; // we must check that return type of called functions is WF: @@ -219,12 +228,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ty::FnDef(..) | ty::FnPtr(..) => { let adjustments = self.adjust_steps(autoderef); self.apply_adjustments(callee_expr, adjustments); + + // If the callee has `#[splat]` on an argument + if let hir::ExprKind::Path(ref qpath) = callee_expr.kind + && let Res::Def(_def_kind, def_id) = + self.typeck_results.borrow().qpath_res(qpath, callee_expr.hir_id) + && self.tcx.fn_sig(def_id).skip_binder().skip_binder().splatted + { + return Some(CallStep::Splatted(adjusted_ty)); + } + return Some(CallStep::Builtin(adjusted_ty)); } // Check whether this is a call to a closure where we // haven't yet decided on whether the closure is fn vs // fnmut vs fnonce. If so, we have to defer further processing. + // FIXME(splat): handle splatting of closure arguments ty::Closure(def_id, args) if self.closure_kind(adjusted_ty).is_none() => { let def_id = def_id.expect_local(); let closure_sig = args.as_closure().sig(); @@ -252,6 +272,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // signature with an infer var for the `tupled_upvars_ty` of the coroutine, // and record a deferred call resolution which will constrain that var // as part of `AsyncFn*` trait confirmation. + // FIXME(splat): handle splatting of coroutine closure arguments ty::CoroutineClosure(def_id, args) if self.closure_kind(adjusted_ty).is_none() => { let def_id = def_id.expect_local(); let closure_args = args.as_coroutine_closure(); @@ -907,7 +928,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { expected, arg_exprs, fn_sig.c_variadic, - TupleArgumentsFlag::TupleArguments, + TupleArgumentsFlag::TupleAllArguments, Some(closure_def_id.to_def_id()), ); @@ -978,7 +999,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { expected, arg_exprs, method.sig.c_variadic, - TupleArgumentsFlag::TupleArguments, + TupleArgumentsFlag::TupleAllArguments, Some(method.def_id), ); @@ -986,6 +1007,59 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { method.sig.output() } + + fn confirm_splatted_call( + &self, + call_expr: &'tcx hir::Expr<'tcx>, + callee_ty: Ty<'tcx>, + arg_exprs: &'tcx [hir::Expr<'tcx>], + expected: Expectation<'tcx>, + ) -> Ty<'tcx> { + let (fn_sig, def_id) = 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); + (fn_sig, Some(def_id)) + } + + // FIXME(const_trait_impl): these arms should error because we can't enforce them + ty::FnPtr(sig_tys, hdr) => (sig_tys.with(hdr), None), + + _ => unreachable!(), + }; + + // Replace any late-bound regions that appear in the function + // signature with region variables. We also have to + // renormalize the associated types at this point, since they + // previously appeared within a `Binder<>` and hence would not + // have been normalized before. + let fn_sig = self.instantiate_binder_with_fresh_vars( + call_expr.span, + BoundRegionConversionTime::FnCall, + fn_sig, + ); + let fn_sig = self.normalize(call_expr.span, fn_sig); + + self.check_argument_types( + call_expr.span, + call_expr, + fn_sig.inputs(), + fn_sig.output(), + expected, + arg_exprs, + fn_sig.c_variadic, + TupleArgumentsFlag::TupleSplattedArguments, + def_id, + ); + + // FIXME(splat): is splatting incompatible with RustCall? + if fn_sig.abi == rustc_abi::ExternAbi::RustCall { + let sp = arg_exprs.last().map_or(call_expr.span, |expr| expr.span); + self.dcx().emit_err(errors::RustCallIncorrectArgs { span: sp }); + } + + fn_sig.output() + } } #[derive(Debug)] diff --git a/compiler/rustc_hir_typeck/src/errors.rs b/compiler/rustc_hir_typeck/src/errors.rs index 6eef156846972..091dd4d25dd57 100644 --- a/compiler/rustc_hir_typeck/src/errors.rs +++ b/compiler/rustc_hir_typeck/src/errors.rs @@ -101,7 +101,9 @@ impl IntoDiagArg for ReturnLikeStatementKind { } #[derive(Diagnostic)] -#[diag("functions with the \"rust-call\" ABI must take a single non-self tuple argument")] +#[diag( + "functions with the \"rust-call\" ABI must take a single non-self, non-splatted tuple argument" +)] pub(crate) struct RustCallIncorrectArgs { #[primary_span] pub span: Span, diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 6b77169994a03..f46c84a652f61 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -1482,6 +1482,14 @@ 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 + let tuple_arguments = if method.sig.splatted { + // self is already handled as `rcvr`, so it's never splatted here + TupleArgumentsFlag::TupleSplattedArguments + } else { + TupleArgumentsFlag::DontTupleArguments + }; + self.check_argument_types( segment.ident.span, expr, @@ -1490,7 +1498,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { expected, args, method.sig.c_variadic, - TupleArgumentsFlag::DontTupleArguments, + tuple_arguments, Some(method.def_id), ); diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index 0471fd965cd82..38eb4558fe9ad 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -216,9 +216,10 @@ 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 .only_has_type(self) @@ -284,39 +285,99 @@ 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]); + // If the arguments should be wrapped in a tuple (ex: closures, splats), unwrap them here + let (formal_input_tys, expected_input_tys) = if tuple_arguments.last_argument_is_tupled() { + // An empty argument list should be a single unit type argument in the callee. + // The Fn* traits ensure this by construction, and `#[splat]` can only be applied to an actual argument. + let tupled_arg_index = + formal_input_tys.len().checked_sub(1).expect("must have a tuple argument"); + // 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 tuple_type = self + .try_structurally_resolve_type(call_span, formal_input_tys[tupled_arg_index]); + if tuple_type.is_ty_var() { + let tuple_len = provided_args.len().checked_sub(tupled_arg_index); + if let Some(tuple_len) = tuple_len { + // FIXME(splat before merging): how do we force the type variable to resolve to (a supertype) of the caller's tupled argument types? + Ty::new_tup_from_iter( + self.tcx, + iter::repeat_with(|| self.next_ty_var(call_span)).take(tuple_len), + ) + } else { + // Just let it likely fail later + tuple_type + } + } else { + tuple_type + } + } else { + self.structurally_resolve_type(call_span, formal_input_tys[tupled_arg_index]) + }; 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() { + // FIXME(splat): update the error code E0057 docs when splat is stabilized + if Some(arg_types.len()) != provided_args.len().checked_sub(tupled_arg_index) { 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, - }, + Some(expected_input_tys) => { + match expected_input_tys.get(tupled_arg_index) { + Some(ty) => match ty.kind() { + ty::Tuple(tys) => Some( + expected_input_tys + .into_iter() + .take(tupled_arg_index) + .chain(tys.iter()) + .collect::>(), + ), + _ => None, + }, + None => None, + } + } None => None, }; - (arg_types.iter().collect(), expected_input_tys) + // FIXME(splat before merging): if splatting, update the caller's arguments to be a tuple, so MIR typecheck passes + ( + formal_input_tys[..tupled_arg_index] + .into_iter() + .cloned() + .chain(arg_types.into_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(); + let guar = match tuple_arguments { + TupleAllArguments => 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(), + TupleSplattedArguments { .. } => struct_span_code_err!( + self.dcx(), + call_span, + // FIXME(splat): add a new error code before stabilization + E0277, + "cannot use splat attribute; the last type parameter \ + for the function must be a tuple or unit, not a {:?} ({:?})", + tuple_type.kind(), + self.structurally_resolve_type( + call_span, + formal_input_tys[tupled_arg_index] + ) + .kind(), + ) + .emit(), + DontTupleArguments => unreachable!(), + }; (self.err_args(provided_args.len(), guar), None) } } @@ -1371,8 +1432,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 + // TupleAllArguments is set only when this is an implicit call `my_closure(...)` rather + // than explicit `my_closure.call(...)`. + if tuple_arguments == TupleAllArguments && 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. diff --git a/compiler/rustc_hir_typeck/src/lib.rs b/compiler/rustc_hir_typeck/src/lib.rs index 734e5136f8806..346a4f4ea7553 100644 --- a/compiler/rustc_hir_typeck/src/lib.rs +++ b/compiler/rustc_hir_typeck/src/lib.rs @@ -611,9 +611,9 @@ fn report_unexpected_variant_res( } /// Controls whether the arguments are tupled. This is used for the call -/// operator. +/// operator and function argument splatting. /// -/// Tupling means that all call-side arguments are packed into a tuple and +/// Tupling means that trailing call-side arguments are packed into a tuple and /// passed as a single parameter. For example, if tupling is enabled, this /// function: /// ``` @@ -629,10 +629,34 @@ fn report_unexpected_variant_res( /// # fn f(x: (isize, isize)) {} /// f((1, 2)); /// ``` -#[derive(Copy, Clone, Eq, PartialEq)] +/// +/// For the call operator, all arguments are tupled. For splatting, trailing arguments after +/// `#[splat]` in the function signature are tupled. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] enum TupleArgumentsFlag { + /// Arguments are typechecked unchanged. DontTupleArguments, - TupleArguments, + /// All arguments are tupled before typechecking. + TupleAllArguments, + /// Only the splatted final argument is tupled before typechecking. + TupleSplattedArguments, +} + +impl TupleArgumentsFlag { + // Is the last argument in the callee tupled? + fn last_argument_is_tupled(&self) -> bool { + match self { + Self::DontTupleArguments => false, + // The callee always has a single tuple argument. + Self::TupleAllArguments => true, + // The last argument of the callee is a splatted tuple in the caller. + Self::TupleSplattedArguments { .. } => true, + } + } + + fn is_splatted(&self) -> bool { + matches!(self, Self::TupleSplattedArguments { .. }) + } } fn fatally_break_rust(tcx: TyCtxt<'_>, span: Span) -> ! { diff --git a/compiler/rustc_type_ir/src/ty_kind.rs b/compiler/rustc_type_ir/src/ty_kind.rs index d2e9a1cdb7cd6..3a2de0af85395 100644 --- a/compiler/rustc_type_ir/src/ty_kind.rs +++ b/compiler/rustc_type_ir/src/ty_kind.rs @@ -777,14 +777,6 @@ impl FnSig { let FnSig { safety, abi, c_variadic, .. } = self; !c_variadic && safety.is_safe() && abi.is_rust() } - - pub fn splatted_arg_index(self) -> Option { - // A function with no inputs behaves the same regardless of splatting. - let last_index = self.inputs().len().checked_sub(1)?; - - debug_assert!(last_index <= u16::MAX as usize); - self.splatted.then_some(last_index as u16) - } } impl ty::Binder> { diff --git a/tests/ui/splat/splat-non-function.rs b/tests/ui/splat/splat-non-fn-arg.rs similarity index 56% rename from tests/ui/splat/splat-non-function.rs rename to tests/ui/splat/splat-non-fn-arg.rs index a9e2b280a511d..92c758cb14582 100644 --- a/tests/ui/splat/splat-non-function.rs +++ b/tests/ui/splat/splat-non-fn-arg.rs @@ -1,8 +1,8 @@ +//! Test that using `#[splat]` on non-function-arguments is an error. + #![allow(incomplete_features)] #![feature(splat)] -fn tuple_args(#[splat] (a, b): (u32, i8)) {} - #[splat] //~ ERROR `#[splat]` attribute cannot be used on functions fn tuple_args_bad((a, b): (u32, i8)) {} @@ -13,21 +13,11 @@ trait FooTraitBad { fn tuple_4(self, _: (u32, i8, (), f32)); } -trait FooTrait { - fn tuple_1(#[splat] _: (u32,)); - - fn tuple_4(#[splat] 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,)) {} - - fn tuple_4_bad(self, (a, b, c, d): (u32, i8, (), f32)) -> u32 { - a - } } impl Foo { @@ -38,32 +28,13 @@ impl Foo { fn tuple_2_bad(self, (a, b): (u32, i8)) -> u32 { a } - - fn tuple_1(#[splat] (a,): (u32,)) {} - - // FIXME(splat): this should error except when `self` (or any splatted arg) is a tuple. - // Tuple structs should also error until we have a specific use case for them, and so should - // multiple splats in a fn. - fn tuple_2_self(#[splat] self, (a, b): (u32, i8)) -> u32 { - a - } - - fn tuple_3(#[splat] (a, b, c): (u32, i32, i8)) {} - - 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 - } } -impl FooTrait for Foo { - // FIXME(splat): should conflicting splat attributes be allowed on traits and impls? +#[splat] //~ ERROR `#[splat]` attribute cannot be used on trait impl blocks +impl FooTraitBad for Foo { fn tuple_1(_: (u32,)) {} - fn tuple_4(#[splat] self, _: (u32, i8, (), f32)) {} + fn tuple_4(self, _: (u32, i8, (), f32)) {} } #[splat] //~ ERROR `#[splat]` attribute cannot be used on foreign modules @@ -72,8 +43,6 @@ extern "C" { } extern "C" { - fn bar_2(#[splat] _: (u32, i8)); - #[splat] //~ ERROR `#[splat]` attribute cannot be used on foreign functions fn bar_2_bad(_: (u32, i8)); } @@ -87,4 +56,11 @@ use std::mem; #[splat] //~ ERROR `#[splat]` attribute cannot be used on structs struct FooStruct; +fn multisplat_bad( + #[splat] + #[splat] //~ WARN unused attribute + (a, b): (u32, i8), +) { +} + fn main() {} diff --git a/tests/ui/splat/splat-non-function.stderr b/tests/ui/splat/splat-non-fn-arg.stderr similarity index 64% rename from tests/ui/splat/splat-non-function.stderr rename to tests/ui/splat/splat-non-fn-arg.stderr index bb7fc6bf5c84f..95b28fde5825f 100644 --- a/tests/ui/splat/splat-non-function.stderr +++ b/tests/ui/splat/splat-non-fn-arg.stderr @@ -1,5 +1,5 @@ error: `#[splat]` attribute cannot be used on functions - --> $DIR/splat-non-function.rs:6:1 + --> $DIR/splat-non-fn-arg.rs:6:1 | LL | #[splat] | ^^^^^^^^ @@ -7,7 +7,7 @@ LL | #[splat] = help: `#[splat]` can be applied to function params and macro calls error: `#[splat]` attribute cannot be used on traits - --> $DIR/splat-non-function.rs:9:1 + --> $DIR/splat-non-fn-arg.rs:9:1 | LL | #[splat] | ^^^^^^^^ @@ -15,7 +15,7 @@ LL | #[splat] = help: `#[splat]` can be applied to function params and macro calls error: `#[splat]` attribute cannot be used on inherent impl blocks - --> $DIR/splat-non-function.rs:24:1 + --> $DIR/splat-non-fn-arg.rs:18:1 | LL | #[splat] | ^^^^^^^^ @@ -23,7 +23,7 @@ LL | #[splat] = help: `#[splat]` can be applied to function params and macro calls error: `#[splat]` attribute cannot be used on inherent methods - --> $DIR/splat-non-function.rs:34:5 + --> $DIR/splat-non-fn-arg.rs:24:5 | LL | #[splat] | ^^^^^^^^ @@ -31,15 +31,23 @@ LL | #[splat] = help: `#[splat]` can be applied to function params and macro calls error: `#[splat]` attribute cannot be used on inherent methods - --> $DIR/splat-non-function.rs:37:5 + --> $DIR/splat-non-fn-arg.rs:27:5 | LL | #[splat] | ^^^^^^^^ | = help: `#[splat]` can be applied to function params and macro calls +error: `#[splat]` attribute cannot be used on trait impl blocks + --> $DIR/splat-non-fn-arg.rs:33:1 + | +LL | #[splat] + | ^^^^^^^^ + | + = help: `#[splat]` can be applied to function params and macro calls + error: `#[splat]` attribute cannot be used on foreign modules - --> $DIR/splat-non-function.rs:69:1 + --> $DIR/splat-non-fn-arg.rs:40:1 | LL | #[splat] | ^^^^^^^^ @@ -47,7 +55,7 @@ LL | #[splat] = help: `#[splat]` can be applied to function params and macro calls error: `#[splat]` attribute cannot be used on foreign functions - --> $DIR/splat-non-function.rs:77:5 + --> $DIR/splat-non-fn-arg.rs:46:5 | LL | #[splat] | ^^^^^^^^ @@ -55,7 +63,7 @@ LL | #[splat] = help: `#[splat]` can be applied to function params and macro calls error: `#[splat]` attribute cannot be used on modules - --> $DIR/splat-non-function.rs:81:1 + --> $DIR/splat-non-fn-arg.rs:50:1 | LL | #[splat] | ^^^^^^^^ @@ -63,7 +71,7 @@ LL | #[splat] = help: `#[splat]` can be applied to function params and macro calls error: `#[splat]` attribute cannot be used on use statements - --> $DIR/splat-non-function.rs:84:1 + --> $DIR/splat-non-fn-arg.rs:53:1 | LL | #[splat] | ^^^^^^^^ @@ -71,12 +79,25 @@ LL | #[splat] = help: `#[splat]` can be applied to function params and macro calls error: `#[splat]` attribute cannot be used on structs - --> $DIR/splat-non-function.rs:87:1 + --> $DIR/splat-non-fn-arg.rs:56:1 | LL | #[splat] | ^^^^^^^^ | = help: `#[splat]` can be applied to function params and macro calls -error: aborting due to 10 previous errors +warning: unused attribute + --> $DIR/splat-non-fn-arg.rs:61:5 + | +LL | #[splat] + | ^^^^^^^^ help: remove this attribute + | +note: attribute also specified here + --> $DIR/splat-non-fn-arg.rs:60:5 + | +LL | #[splat] + | ^^^^^^^^ + = note: requested on the command line with `-W unused-attributes` + +error: aborting due to 11 previous errors; 1 warning emitted diff --git a/tests/ui/splat/splat-non-tuple.rs b/tests/ui/splat/splat-non-tuple.rs new file mode 100644 index 0000000000000..9f9973320006c --- /dev/null +++ b/tests/ui/splat/splat-non-tuple.rs @@ -0,0 +1,78 @@ +//! Test that using `#[splat]` on non-tuple function arguments is an error. + +#![allow(incomplete_features)] +#![feature(splat)] +#![expect(unused)] + +// FIXME(splat): ERROR `#[splat]` attribute must be used on a tuple +fn primitive_arg(#[splat] x: u32) {} + +enum NotATuple { + A(u32), + B(i8), +} + +// FIXME(splat): ERROR `#[splat]` attribute must be used on a tuple +fn enum_arg(#[splat] y: 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) {} + +impl Foo { + // FIXME(splat): this should error except when `self` (or any splatted arg) is a tuple. + fn tuple_2_self( + #[splat] self, // FIXME(splat): ERROR `#[splat]` attribute must be used on a tuple + (a, b): (u32, i8), + ) -> u32 { + a + } +} + +impl FooTrait for Foo { + // FIXME(splat): the expected signature should be `fn( #[splat] (_,))` + fn tuple_1(_: (u32,)) {} + //~^ ERROR method `tuple_1` has an incompatible type for trait + //~| NOTE expected splatted fn, found non-splatted function + //~| NOTE expected signature `fn((_,))` + + fn tuple_4( + #[splat] self, // FIXME(splat): ERROR `#[splat]` attribute must be used on a tuple + _: (u32, i8, (), f32), + ) { + } +} + +struct TupleStruct(u32, i8); + +// FIXME(splat): tuple structs should error until we have a specific use case for them. +// FIXME(splat): ERROR `#[splat]` attribute must be used on a tuple +fn tuple_struct_arg(#[splat] z: TupleStruct) {} + +impl TupleStruct { + fn tuple_2( + #[splat] self, // FIXME(splat): ERROR `#[splat]` attribute must be used on a tuple + (a, b): (u32, i8), + ) -> u32 { + 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() {} 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..02980a263a6b0 --- /dev/null +++ b/tests/ui/splat/splat-overload-at-home.rs @@ -0,0 +1,54 @@ +//@ dont-check-compiler-stderr +//@ dont-check-failure-status +//@ dont-require-annotations: ERROR +// FIXME(splat): ^change the actual types during typeck so MIR doesn't ICE. + +// 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 { + // FIXME(splat): make this work with impl MethodArgs + fn method(&self, #[splat] args: T) { + args.call_method(self) + } +} + +fn main() { + let foo = Foo; + + foo.method::<()>(); //~ ERROR broken MIR + + // FIXME(splat): + // - generic tuple trait implementers should work without explicit tuple type parameters. + // - actually modify the argument list during typeck, to avoid "broken MIR" errors. + foo.method::<(i32,)>(42i32); //~ ERROR broken MIR + // 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((42i32,)); + + foo.method::<(i32,)>(42); //~ ERROR broken MIR + + foo.method::<(i32, String)>(42i32, "asdf".to_owned()); //~ ERROR broken MIR + + foo.method::<(i32, String)>(42, "asdf".to_owned()); //~ ERROR broken MIR +} diff --git a/tests/ui/splat/splat-tuple.rs b/tests/ui/splat/splat-tuple.rs new file mode 100644 index 0000000000000..9826f1c3cdc63 --- /dev/null +++ b/tests/ui/splat/splat-tuple.rs @@ -0,0 +1,101 @@ +//@ dont-check-compiler-stderr +//@ dont-check-failure-status +//@ dont-require-annotations: ERROR +// FIXME(splat): ^change the actual types during typeck so MIR doesn't ICE. + +//! Test using `#[splat]` on tuple function arguments. + +#![allow(incomplete_features)] +#![feature(splat)] +#![feature(tuple_trait)] + +fn tuple_args(#[splat] (a, b): (u32, i8)) {} + +trait FooTrait { + fn tuple_1_trait(#[splat] _: (u32,)); + + // Ambiguous case, self could be a tuple or a non-tuple. + fn tuple_4_trait(#[splat] &self, _: (u32, i8, (), f32)) {} +} + +struct Foo; + +impl Foo { + fn tuple_1(#[splat] (a,): (u32,)) {} + + fn tuple_2(&self, #[splat] (a, b): (u32, i8)) -> u32 { + a + } + + fn tuple_3(#[splat] (a, b, c): (u32, i32, i8)) {} + + fn tuple_4(&self, #[splat] (a, b, c, d): (u32, i8, (), f32)) -> u32 { + a + } +} + +impl FooTrait for Foo { + // FIXME(splat): should splat attributes be inherited from traits? + // Can splat attributes be added on impls? + fn tuple_1_trait(#[splat] _: (u32,)) {} +} + +struct TupleStruct(u32, i8); + +impl TupleStruct { + // FIXME(splat): tuple structs should error until we have a specific use case for them. + fn tuple_2(#[splat] &self, (a, b): (u32, i8)) -> u32 { + a + } +} + +impl FooTrait for TupleStruct { + fn tuple_1_trait(#[splat] _: (u32,)) {} +} + +extern "C" { + // FIXME(splat): tuple layouts are unspecified, so this should error. + #[expect(improper_ctypes)] + fn bar_2(#[splat] _: (u32, i8)); +} + +// FIXME(splat): multiple splats in a fn should error. +fn multisplat_bad_2(#[splat] (a, b): (u32, i8), #[splat] (c, d): (u32, i8)) {} + +// FIXME(splat): non-terminal splat attributes should error, until we have a specific use case for +// them. +fn splat_non_terminal_bad(#[splat] (a, b): (u32, i8), (c, d): (u32, i8)) {} + +fn splat_generic_tuple(#[splat] t: T) {} + +fn main() { + // FIXME(splat): actually modify the argument list during typeck, to avoid "broken MIR" errors + tuple_args(1, 2); //~ ERROR: broken MIR in + // 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(1u32, 2i8); //~ ERROR: broken MIR in + + let foo = Foo; + Foo::tuple_1(1u32); //~ ERROR: broken MIR in + foo.tuple_2(1u32, 2i8); //~ ERROR: broken MIR in + Foo::tuple_3(1u32, 2i32, 3i8); //~ ERROR broken MIR + foo.tuple_4(1u32, 2i8, (), 3f32); + + Foo::tuple_1_trait(1u32); + // FIXME(splat): this should error because `self` is splatted, but `Foo` is not a tuple. + foo.tuple_4_trait((1u32, 2i8, (), 3f32)); + + let tuple_struct = TupleStruct(1u32, 2i8); + tuple_struct.tuple_2((1u32, 2i8)); + + TupleStruct::tuple_1_trait(1u32); + // FIXME(splat): this should error because `self` is splatted, but `TupleStruct` is not a tuple. + tuple_struct.tuple_4_trait((1u32, 2i8, (), 3f32)); + + // FIXME(splat): generic tuple trait implementers should work without explicit tuple type + // parameters. + splat_generic_tuple::<(u32, i8)>(1u32, 2i8); + + splat_generic_tuple::<()>(); +} From 9bbd30324f072cbf8b1c5e40f1cc5863c946f8e2 Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 24 Mar 2026 13:49:31 +1000 Subject: [PATCH 4/5] Fix type inference for splatted arguments --- compiler/rustc_hir_typeck/src/callee.rs | 4 +- .../rustc_hir_typeck/src/fn_ctxt/checks.rs | 53 ++++++++++++++++--- tests/ui/splat/splat-overload-at-home.rs | 15 +++--- tests/ui/splat/splat-tuple.rs | 7 ++- 4 files changed, 61 insertions(+), 18 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/callee.rs b/compiler/rustc_hir_typeck/src/callee.rs index fdeeb933e47c5..eb3810c2f47f0 100644 --- a/compiler/rustc_hir_typeck/src/callee.rs +++ b/compiler/rustc_hir_typeck/src/callee.rs @@ -244,7 +244,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Check whether this is a call to a closure where we // haven't yet decided on whether the closure is fn vs // fnmut vs fnonce. If so, we have to defer further processing. - // FIXME(splat): handle splatting of closure arguments + // FIXME(splat): does it make sense to splat closure arguments? ty::Closure(def_id, args) if self.closure_kind(adjusted_ty).is_none() => { let def_id = def_id.expect_local(); let closure_sig = args.as_closure().sig(); @@ -272,7 +272,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // signature with an infer var for the `tupled_upvars_ty` of the coroutine, // and record a deferred call resolution which will constrain that var // as part of `AsyncFn*` trait confirmation. - // FIXME(splat): handle splatting of coroutine closure arguments + // FIXME(splat): does it make sense to splat coroutine closure arguments? ty::CoroutineClosure(def_id, args) if self.closure_kind(adjusted_ty).is_none() => { let def_id = def_id.expect_local(); let closure_args = args.as_coroutine_closure(); diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index 38eb4558fe9ad..80a1c3fa70d2f 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -293,28 +293,65 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { formal_input_tys.len().checked_sub(1).expect("must have a tuple argument"); // 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 tuple_type = self + let calee_tuple_type = self .try_structurally_resolve_type(call_span, formal_input_tys[tupled_arg_index]); - if tuple_type.is_ty_var() { + if calee_tuple_type.is_ty_var() { let tuple_len = provided_args.len().checked_sub(tupled_arg_index); if let Some(tuple_len) = tuple_len { - // FIXME(splat before merging): how do we force the type variable to resolve to (a supertype) of the caller's tupled argument types? - Ty::new_tup_from_iter( + // 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(tuple_len), - ) + ); + + // FIXME(splat): should this be a sub/super type relationship? + let ocx_error = + ocx.eq(&origin, self.param_env, calee_tuple_type, new_tupled_type); + if let Err(ocx_error) = ocx_error { + struct_span_code_err!( + self.dcx(), + call_span, + // FIXME(splat): add a new error code before stabilization + E0277, + "cannot resolve splatted arguments; the last type parameter \ + for the function must be a tuple or unit: {:?}", + ocx_error, + ) + .emit(); + } + + let type_errors = ocx.try_evaluate_obligations(); + if type_errors.is_empty() { + assert_matches!(new_tupled_type.kind(), ty::Tuple(_)); + new_tupled_type + } else { + let guar = struct_span_code_err!( + self.dcx(), + call_span, + // FIXME(splat): add a new error code before stabilization + E0277, + "cannot resolve splatted arguments; the last type parameter \ + for the function must be a tuple or unit: {:?}", + type_errors, + ) + .emit(); + Ty::new_error(self.tcx, guar) + } } else { // Just let it likely fail later - tuple_type + calee_tuple_type } } else { - tuple_type + calee_tuple_type } } else { self.structurally_resolve_type(call_span, formal_input_tys[tupled_arg_index]) }; match tuple_type.kind() { - // We expected a tuple and got a tuple + // We expected a tuple and got a tuple (or made one ourselves) ty::Tuple(arg_types) => { // Argument length differs // FIXME(splat): update the error code E0057 docs when splat is stabilized diff --git a/tests/ui/splat/splat-overload-at-home.rs b/tests/ui/splat/splat-overload-at-home.rs index 02980a263a6b0..9e68698977f86 100644 --- a/tests/ui/splat/splat-overload-at-home.rs +++ b/tests/ui/splat/splat-overload-at-home.rs @@ -36,19 +36,22 @@ impl Foo { fn main() { let foo = Foo; - foo.method::<()>(); //~ ERROR broken MIR - // FIXME(splat): // - generic tuple trait implementers should work without explicit tuple type parameters. // - actually modify the argument list during typeck, to avoid "broken MIR" errors. + foo.method::<()>(); //~ ERROR broken MIR + foo.method(); //~ ERROR broken MIR + foo.method::<(i32,)>(42i32); //~ ERROR broken MIR + foo.method::<(i32,)>(42); //~ ERROR broken MIR + foo.method(42i32); //~ ERROR broken MIR + foo.method(42); //~ ERROR broken MIR // 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((42i32,)); - foo.method::<(i32,)>(42); //~ ERROR broken MIR - foo.method::<(i32, String)>(42i32, "asdf".to_owned()); //~ ERROR broken MIR - - foo.method::<(i32, String)>(42, "asdf".to_owned()); //~ ERROR broken MIR + 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-tuple.rs b/tests/ui/splat/splat-tuple.rs index 9826f1c3cdc63..77af11f814644 100644 --- a/tests/ui/splat/splat-tuple.rs +++ b/tests/ui/splat/splat-tuple.rs @@ -82,20 +82,23 @@ fn main() { Foo::tuple_3(1u32, 2i32, 3i8); //~ ERROR broken MIR foo.tuple_4(1u32, 2i8, (), 3f32); - Foo::tuple_1_trait(1u32); // FIXME(splat): this should error because `self` is splatted, but `Foo` is not a tuple. + Foo::tuple_1_trait(1u32); foo.tuple_4_trait((1u32, 2i8, (), 3f32)); let tuple_struct = TupleStruct(1u32, 2i8); + // FIXME(splat): this should error because `self` is splatted, but `TupleStruct` is not a tuple. tuple_struct.tuple_2((1u32, 2i8)); TupleStruct::tuple_1_trait(1u32); - // FIXME(splat): this should error because `self` is splatted, but `TupleStruct` is not a tuple. tuple_struct.tuple_4_trait((1u32, 2i8, (), 3f32)); // FIXME(splat): generic tuple trait implementers should work without explicit tuple type // parameters. splat_generic_tuple::<(u32, i8)>(1u32, 2i8); + splat_generic_tuple(1u32, 2i8); + splat_generic_tuple(1, 2); splat_generic_tuple::<()>(); + splat_generic_tuple(); } From 8a0a4c6047981c7dfdd72870ab7d809e59efb04d Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 27 Mar 2026 16:44:00 +1000 Subject: [PATCH 5/5] Implement splatted MIR lowering and split tests --- compiler/rustc_hir_typeck/src/callee.rs | 36 ++- compiler/rustc_hir_typeck/src/expr.rs | 7 + .../rustc_hir_typeck/src/fn_ctxt/_impl.rs | 30 +++ .../rustc_hir_typeck/src/fn_ctxt/checks.rs | 32 ++- compiler/rustc_hir_typeck/src/writeback.rs | 5 + .../rustc_middle/src/ty/typeck_results.rs | 46 ++++ compiler/rustc_mir_build/src/thir/cx/expr.rs | 225 +++++++++++++++++- tests/ui/splat/splat-assoc-fn-tuple-simple.rs | 22 ++ tests/ui/splat/splat-fn-tuple-generic.rs | 22 ++ tests/ui/splat/splat-fn-tuple-simple.rs | 15 ++ tests/ui/splat/splat-generics-everywhere.rs | 49 ++++ tests/ui/splat/splat-inconsistent.rs | 36 +++ tests/ui/splat/splat-inconsistent.stderr | 31 +++ tests/ui/splat/splat-method-tuple-simple.rs | 38 +++ .../ui/splat/splat-method-tuple-simple.stderr | 13 + tests/ui/splat/splat-non-tuple.stderr | 17 ++ tests/ui/splat/splat-overload-at-home.rs | 37 ++- tests/ui/splat/splat-trait-tuple.rs | 42 ++++ tests/ui/splat/splat-trait-tuple.stderr | 13 + tests/ui/splat/splat-tuple.rs | 104 -------- 20 files changed, 669 insertions(+), 151 deletions(-) create mode 100644 tests/ui/splat/splat-assoc-fn-tuple-simple.rs create mode 100644 tests/ui/splat/splat-fn-tuple-generic.rs create mode 100644 tests/ui/splat/splat-fn-tuple-simple.rs create mode 100644 tests/ui/splat/splat-generics-everywhere.rs create mode 100644 tests/ui/splat/splat-inconsistent.rs create mode 100644 tests/ui/splat/splat-inconsistent.stderr create mode 100644 tests/ui/splat/splat-method-tuple-simple.rs create mode 100644 tests/ui/splat/splat-method-tuple-simple.stderr create mode 100644 tests/ui/splat/splat-non-tuple.stderr create mode 100644 tests/ui/splat/splat-trait-tuple.rs create mode 100644 tests/ui/splat/splat-trait-tuple.stderr delete mode 100644 tests/ui/splat/splat-tuple.rs diff --git a/compiler/rustc_hir_typeck/src/callee.rs b/compiler/rustc_hir_typeck/src/callee.rs index eb3810c2f47f0..f9f7da74d4365 100644 --- a/compiler/rustc_hir_typeck/src/callee.rs +++ b/compiler/rustc_hir_typeck/src/callee.rs @@ -63,7 +63,8 @@ enum CallStep<'tcx> { /// Call overloading when callee implements one of the Fn* traits. Overloaded(MethodCallee<'tcx>), /// Caller argument tupling, when callee uses `#[splat]`. - Splatted(Ty<'tcx>), + /// Contains the adjusted type of the callee, and its kind. + Splatted(Ty<'tcx>, def::DefKind), } impl<'a, 'tcx> FnCtxt<'a, 'tcx> { @@ -153,9 +154,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.confirm_overloaded_call(call_expr, arg_exprs, expected, method_callee) } - Some(CallStep::Splatted(callee_ty)) => { - self.confirm_splatted_call(call_expr, callee_ty, arg_exprs, expected) - } + Some(CallStep::Splatted(callee_ty, callee_def_kind)) => self.confirm_splatted_call( + call_expr, + callee_ty, + callee_def_kind, + arg_exprs, + expected, + ), }; // we must check that return type of called functions is WF: @@ -231,11 +236,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // If the callee has `#[splat]` on an argument if let hir::ExprKind::Path(ref qpath) = callee_expr.kind - && let Res::Def(_def_kind, def_id) = + && let Res::Def(def_kind, def_id) = self.typeck_results.borrow().qpath_res(qpath, callee_expr.hir_id) && self.tcx.fn_sig(def_id).skip_binder().skip_binder().splatted { - return Some(CallStep::Splatted(adjusted_ty)); + return Some(CallStep::Splatted(adjusted_ty, def_kind)); } return Some(CallStep::Builtin(adjusted_ty)); @@ -620,6 +625,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fn_sig.c_variadic, TupleArgumentsFlag::DontTupleArguments, def_id, + false, + None, + None, ); if fn_sig.abi == rustc_abi::ExternAbi::RustCall { @@ -930,6 +938,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fn_sig.c_variadic, TupleArgumentsFlag::TupleAllArguments, Some(closure_def_id.to_def_id()), + false, + None, + None, ); fn_sig.output() @@ -1001,6 +1012,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { method.sig.c_variadic, TupleArgumentsFlag::TupleAllArguments, Some(method.def_id), + false, + None, + None, ); self.write_method_call_and_enforce_effects(call_expr.hir_id, call_expr.span, method); @@ -1012,18 +1026,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { &self, call_expr: &'tcx hir::Expr<'tcx>, callee_ty: Ty<'tcx>, + callee_def_kind: def::DefKind, 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); - (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!(), }; @@ -1050,6 +1065,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fn_sig.c_variadic, TupleArgumentsFlag::TupleSplattedArguments, def_id, + false, + Some(callee_def_kind), + callee_generic_args, ); // FIXME(splat): is splatting incompatible with RustCall? diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index f46c84a652f61..e1cf85b588ddf 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -1500,6 +1500,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { method.sig.c_variadic, tuple_arguments, Some(method.def_id), + true, + // Methods have to be associated functions + Some(DefKind::AssocFn), + Some(method.args), ); self.check_call_abi(method.sig.abi, expr.span); @@ -1522,6 +1526,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { false, TupleArgumentsFlag::DontTupleArguments, None, + true, + Some(DefKind::AssocFn), + 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 f817ca8421473..aa61f3a533e10 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs @@ -230,6 +230,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<(bool, DefKind, DefId, u16), ErrorGuaranteed>, + ) { + 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, @@ -242,6 +251,27 @@ 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, + is_method_call: bool, + callee_def_kind: DefKind, + callee_def_id: DefId, + callee_generic_args: GenericArgsRef<'tcx>, + tupled_arg_index: 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((is_method_call, callee_def_kind, callee_def_id, tupled_arg_index)), + ); + 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 80a1c3fa70d2f..7dee05ccc200c 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -1,5 +1,5 @@ use std::ops::Deref; -use std::{fmt, iter}; +use std::{assert_matches, fmt, iter}; use itertools::Itertools; use rustc_ast as ast; @@ -188,6 +188,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { tuple_arguments: TupleArgumentsFlag, // The DefId for the function being called, for better error messages fn_def_id: Option, + // The kind of function being called, with its generics. Only used for splatting. Closures aren't supported. + is_method_call: bool, + callee_def_kind: Option, + callee_generic_args: Option>, ) { let tcx = self.tcx; @@ -317,7 +321,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // FIXME(splat): add a new error code before stabilization E0277, "cannot resolve splatted arguments; the last type parameter \ - for the function must be a tuple or unit: {:?}", + for the function {:?} must be a tuple or unit: {:?}", + callee_def_kind, ocx_error, ) .emit(); @@ -334,7 +339,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // FIXME(splat): add a new error code before stabilization E0277, "cannot resolve splatted arguments; the last type parameter \ - for the function must be a tuple or unit: {:?}", + for the function {:?} must be a tuple or unit: {:?}", + callee_def_kind, type_errors, ) .emit(); @@ -376,7 +382,25 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } None => None, }; - // FIXME(splat before merging): if splatting, update the caller's arguments to be a tuple, so MIR typecheck passes + // 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, + is_method_call, + // FIXME(splat): there's probably a nicer way to do this + callee_def_kind.expect( + "splatting is not implemented for closures or Fn* trait calls", + ), + fn_def_id.expect("splatting is not implemented for FnPtrs"), + callee_generic_args.expect( + "splatting is not implemented for FnPtrs, closures, or Fn* trait calls", + ), + tupled_arg_index.try_into().unwrap(), + ); + } + ( formal_input_tys[..tupled_arg_index] .into_iter() diff --git a/compiler/rustc_hir_typeck/src/writeback.rs b/compiler/rustc_hir_typeck/src/writeback.rs index afb20b3c7cd0a..93aeec6f51486 100644 --- a/compiler/rustc_hir_typeck/src/writeback.rs +++ b/compiler/rustc_hir_typeck/src/writeback.rs @@ -661,6 +661,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_middle/src/ty/typeck_results.rs b/compiler/rustc_middle/src/ty/typeck_results.rs index b8399215cf810..87b6df7624114 100644 --- a/compiler/rustc_middle/src/ty/typeck_results.rs +++ b/compiler/rustc_middle/src/ty/typeck_results.rs @@ -36,6 +36,10 @@ pub struct TypeckResults<'tcx> { /// method calls, including those of overloaded operators. type_dependent_defs: ItemLocalMap>, + /// Resolved definitions for splatted function calls, including the splatted argument index. + 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 +233,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 +292,32 @@ impl<'tcx> TypeckResults<'tcx> { LocalTableInContextMut { hir_owner: self.hir_owner, data: &mut self.type_dependent_defs } } + pub fn splatted_defs( + &self, + ) -> LocalTableInContext< + '_, + Result<(bool /* is_method_call */, DefKind, DefId, u16), ErrorGuaranteed>, + > { + LocalTableInContext { hir_owner: self.hir_owner, data: &self.splatted_defs } + } + + pub fn splatted_def( + &self, + id: HirId, + ) -> Option<(bool /* is_method_call */, DefKind, DefId, u16)> { + 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<(bool /* is_method_call */, DefKind, DefId, u16), ErrorGuaranteed>, + > { + 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 +438,21 @@ impl<'tcx> TypeckResults<'tcx> { matches!(self.type_dependent_defs().get(expr.hir_id), Some(Ok((DefKind::AssocFn, _)))) } + pub fn is_splatted_function_call(&self, expr: &hir::Expr<'_>) -> bool { + // FIXME(splat): does it make sense to support splatted closure definitions? + matches!( + self.splatted_defs().get(expr.hir_id), + Some(Ok((false /* is_method_call */, _, _, _))) + ) + } + + pub fn is_splatted_method_call(&self, expr: &hir::Expr<'_>) -> bool { + matches!( + self.splatted_defs().get(expr.hir_id), + Some(Ok((true /* is_method_call */, DefKind::AssocFn, _, _))) + ) + } + /// 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 { diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index 4256ee45f8501..13a724386af70 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -385,19 +385,85 @@ 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_method_call(expr) { + // The callee has a splatted tuple argument. + let (method, tupled_arg_index) = self.splatted_method_callee(expr, fn_span); + let tupled_arg_index = usize::from(tupled_arg_index); + + // 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 > args.len() { + span_bug!( + expr.span, + "splatted arg index out of bounds of method args: {:?} >= {:?} for method call {:?}, receiver {:?}, args {:?}", + tupled_arg_index, + args.len(), + segment, + receiver, + args, + ); + } + + // rewrite `a.f(b, c)` into `Trait::f(a, #[splat] (b, c))` + let expr = self.method_callee(expr, segment.ident.span, None); + info!("Using splatted method span: {:?}", expr.span); + + // Split into non-tupled and tupled arguments + let 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() { + // Splatting an empty tuple + Default::default() + } else { + &args[tupled_arg_index..] + }; + + let tupled_arg_tys = args + .iter() + .skip(tupled_arg_index) + .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.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(non_tupled_args); + args.push(tupled_args); + + // FIXME(splat): codegen should de-tuple the caller and calee + // This is only done for performance, we need the tupled arguments in HIR/MIR for type checking + ExprKind::Call { + ty: method.ty, + fun: self.thir.exprs.push(method), + args: args.into_boxed_slice(), + from_hir_call: true, + fn_span: expr.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, + } } } @@ -428,6 +494,65 @@ impl<'tcx> ThirBuildCx<'tcx> { from_hir_call: true, fn_span: expr.span, } + } else if self.typeck_results.is_splatted_function_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) = + self.splatted_function_callee(expr, fun.span); + let tupled_arg_index = usize::from(tupled_arg_index); + + // 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 > args.len() { + span_bug!( + expr.span, + "splatted arg index out of bounds of function args: {:?} >= {:?} for function {:?}, args {:?}", + tupled_arg_index, + args.len(), + fun, + args, + ); + } + + // Split into non-tupled and tupled arguments + let 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() { + // Splatting an empty tuple + Default::default() + } else { + &args[tupled_arg_index..] + }; + + let tupled_arg_tys = args + .iter() + .skip(tupled_arg_index) + .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 = non_tupled_args; + args.push(tupled_args); + + // FIXME(splat): codegen should de-tuple the caller and calee + // This is only done for performance, we need the tupled arguments in HIR/MIR for type checking + 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 @@ -1221,6 +1346,80 @@ impl<'tcx> ThirBuildCx<'tcx> { } } + fn splatted_function_callee( + &mut self, + expr: &hir::Expr<'_>, + span: Span, + ) -> (Expr<'tcx>, u16 /* arg_index */) { + let (is_method_call, def_kind, def_id, arg_index) = self + .typeck_results + .splatted_def(expr.hir_id) + .unwrap_or_else(|| span_bug!(expr.span, "no splatted def for function callee")); + if is_method_call { + span_bug!( + expr.span, + "wanted function def for function callee, got method {:?}", + def_kind + ); + } + let user_ty = self.user_args_applied_to_res(expr.hir_id, Res::Def(def_kind, def_id)); + debug!( + "splatted_function_callee: {:?} user_ty={:?} def_kind={:?} def_id={:?} arg_index={:?}", + if is_method_call { "method" } else { "free or assoc fn" }, + user_ty, + def_kind, + def_id, + arg_index + ); + + ( + 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, + ) + } + + fn splatted_method_callee( + &mut self, + expr: &hir::Expr<'_>, + span: Span, + ) -> (Expr<'tcx>, u16 /* arg_index */) { + let (is_method_call, def_kind, def_id, arg_index) = self + .typeck_results + .splatted_def(expr.hir_id) + .unwrap_or_else(|| span_bug!(expr.span, "no splatted def for method callee")); + if !is_method_call || def_kind != DefKind::AssocFn { + span_bug!( + expr.span, + "wanted method def for function callee, got free or assoc fn {:?}", + def_kind + ); + } + let user_ty = self.user_args_applied_to_res(expr.hir_id, Res::Def(def_kind, def_id)); + debug!( + "splatted_method_callee: {:?} user_ty={:?} def_kind={:?} def_id={:?} arg_index={:?}", + if is_method_call { "method" } else { "free or assoc fn" }, + user_ty, + def_kind, + def_id, + arg_index + ); + + ( + 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, + ) + } + fn convert_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) -> ArmId { let arm = Arm { pattern: self.pattern_from_hir(&arm.pat), diff --git a/tests/ui/splat/splat-assoc-fn-tuple-simple.rs b/tests/ui/splat/splat-assoc-fn-tuple-simple.rs new file mode 100644 index 0000000000000..d2681c0d2574c --- /dev/null +++ b/tests/ui/splat/splat-assoc-fn-tuple-simple.rs @@ -0,0 +1,22 @@ +//@ run-pass +//! Test using `#[splat]` on associated function tuple arguments (no receivers). + +#![allow(incomplete_features)] +#![feature(splat)] + +struct Foo; + +impl Foo { + fn tuple_1(#[splat] (_a,): (u32,)) {} + + fn tuple_3(#[splat] (_a, _b, _c): (u32, i32, i8)) {} +} + +fn main() { + // FIXME(splat): should splatted functions be callable with tupled and un-tupled arguments? + // Add a tupled test for each call if they are. + //Foo::tuple_1((1u32,)); + + Foo::tuple_1(1u32); + Foo::tuple_3(1u32, 2i32, 3i8); +} diff --git a/tests/ui/splat/splat-fn-tuple-generic.rs b/tests/ui/splat/splat-fn-tuple-generic.rs new file mode 100644 index 0000000000000..e452937f5b7ee --- /dev/null +++ b/tests/ui/splat/splat-fn-tuple-generic.rs @@ -0,0 +1,22 @@ +//@ 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() { + // FIXME(splat): should splatted functions be callable with tupled and un-tupled arguments? + // Add a tupled test for each call if they are. + //splat_generic_tuple((1, 2)); + + // 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..f1f7cba1580f7 --- /dev/null +++ b/tests/ui/splat/splat-fn-tuple-simple.rs @@ -0,0 +1,15 @@ +//@ 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 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(1u32, 2i8); +} diff --git a/tests/ui/splat/splat-generics-everywhere.rs b/tests/ui/splat/splat-generics-everywhere.rs new file mode 100644 index 0000000000000..0d7b50c26beeb --- /dev/null +++ b/tests/ui/splat/splat-generics-everywhere.rs @@ -0,0 +1,49 @@ +//@ 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)) {} +} + +// 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)); +} + +impl BarTrait for Foo { + fn trait_assoc(_w: W, #[splat] _s: ()) {} + + fn trait_method(&self, _x: X, #[splat] _s: (u32, f64)) {} +} + +// FIXME(splat): also add `T: Tuple` 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.trait_method("x", 42u32, 9.8); +} diff --git a/tests/ui/splat/splat-inconsistent.rs b/tests/ui/splat/splat-inconsistent.rs new file mode 100644 index 0000000000000..0de505a810ed9 --- /dev/null +++ b/tests/ui/splat/splat-inconsistent.rs @@ -0,0 +1,36 @@ +//! Test using `#[splat]` incorrectly, in ways not covered by other tests. + +#![allow(incomplete_features)] +#![feature(splat)] + +// FIXME(splat): multiple splats in a fn should error. +// For now, the attribute is incorrectly assumed to be on the last argument. +fn multisplat_bad_2(#[splat] (_a, _b): (u32, i8), #[splat] (_c, _d): (u32, i8)) {} + +// FIXME(splat): non-terminal splat attributes should error, until we have a specific use case for +// them. +// For now, the attribute is incorrectly assumed to be on the last argument. +fn splat_non_terminal_bad(#[splat] (_a, _b): (u32, i8), (_c, _d): (u32, i8)) {} + +extern "C" { + // 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-inconsistent.stderr b/tests/ui/splat/splat-inconsistent.stderr new file mode 100644 index 0000000000000..91b2e57372732 --- /dev/null +++ b/tests/ui/splat/splat-inconsistent.stderr @@ -0,0 +1,31 @@ +error[E0053]: method `has_splat` has an incompatible type for trait + --> $DIR/splat-inconsistent.rs:30:5 + | +LL | fn has_splat(_: ()) {} + | ^^^^^^^^^^^^^^^^^^^ expected splatted fn, found non-splatted function + | +note: type in trait + --> $DIR/splat-inconsistent.rs:22:5 + | +LL | fn has_splat(#[splat] _: ()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: expected signature `fn(())` + found signature `fn(())` + +error[E0053]: method `no_splat` has an incompatible type for trait + --> $DIR/splat-inconsistent.rs:32:5 + | +LL | fn no_splat(#[splat] _: (u32, f64)) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected non-splatted fn, found splatted function + | +note: type in trait + --> $DIR/splat-inconsistent.rs:24:5 + | +LL | fn no_splat(_: (u32, f64)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: expected signature `fn((_, _))` + found signature `fn((_, _))` + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0053`. 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..31c05d3a1028d --- /dev/null +++ b/tests/ui/splat/splat-method-tuple-simple.rs @@ -0,0 +1,38 @@ +//@ 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 + } +} + +struct TupleStruct(u32, i8); //~ WARN: fields `0` and `1` are never read + +impl TupleStruct { + fn tuple_2(&self, #[splat] (a, _b): (u32, i8)) -> u32 { + a + } +} + +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_2((1, 2)); + + let foo = Foo; + 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-method-tuple-simple.stderr b/tests/ui/splat/splat-method-tuple-simple.stderr new file mode 100644 index 0000000000000..5dac3b89504de --- /dev/null +++ b/tests/ui/splat/splat-method-tuple-simple.stderr @@ -0,0 +1,13 @@ +warning: fields `0` and `1` are never read + --> $DIR/splat-method-tuple-simple.rs:19:20 + | +LL | struct TupleStruct(u32, i8); + | ----------- ^^^ ^^ + | | + | fields in this struct + | + = help: consider removing these fields + = note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default + +warning: 1 warning emitted + diff --git a/tests/ui/splat/splat-non-tuple.stderr b/tests/ui/splat/splat-non-tuple.stderr new file mode 100644 index 0000000000000..109bfb0ecb1d9 --- /dev/null +++ b/tests/ui/splat/splat-non-tuple.stderr @@ -0,0 +1,17 @@ +error[E0053]: method `tuple_1` has an incompatible type for trait + --> $DIR/splat-non-tuple.rs:41:5 + | +LL | fn tuple_1(_: (u32,)) {} + | ^^^^^^^^^^^^^^^^^^^^^ expected splatted fn, found non-splatted function + | +note: type in trait + --> $DIR/splat-non-tuple.rs:19:5 + | +LL | fn tuple_1(#[splat] _: (u32,)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: expected signature `fn((_,))` + found signature `fn((_,))` + +error: aborting due to 1 previous error + +For more information about this 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 index 9e68698977f86..621f0e04f67e1 100644 --- a/tests/ui/splat/splat-overload-at-home.rs +++ b/tests/ui/splat/splat-overload-at-home.rs @@ -1,8 +1,4 @@ -//@ dont-check-compiler-stderr -//@ dont-check-failure-status -//@ dont-require-annotations: ERROR -// FIXME(splat): ^change the actual types during typeck so MIR doesn't ICE. - +//@ run-pass // ignore-tidy-linelength //! Test using `#[splat]` on some "overloading at home" example code. //! @@ -14,20 +10,19 @@ struct Foo; trait MethodArgs: std::marker::Tuple { - fn call_method(self, this: &Foo); + fn call_method(self, _this: &Foo); } impl MethodArgs for () { - fn call_method(self, this: &Foo) {} + fn call_method(self, _this: &Foo) {} } impl MethodArgs for (i32,) { - fn call_method(self, this: &Foo) {} + fn call_method(self, _this: &Foo) {} } impl MethodArgs for (i32, String) { - fn call_method(self, this: &Foo) {} + fn call_method(self, _this: &Foo) {} } impl Foo { - // FIXME(splat): make this work with impl MethodArgs fn method(&self, #[splat] args: T) { args.call_method(self) } @@ -36,21 +31,21 @@ impl Foo { fn main() { let foo = Foo; - // FIXME(splat): - // - generic tuple trait implementers should work without explicit tuple type parameters. - // - actually modify the argument list during typeck, to avoid "broken MIR" errors. - foo.method::<()>(); //~ ERROR broken MIR - foo.method(); //~ ERROR broken MIR - - foo.method::<(i32,)>(42i32); //~ ERROR broken MIR - foo.method::<(i32,)>(42); //~ ERROR broken MIR - foo.method(42i32); //~ ERROR broken MIR - foo.method(42); //~ ERROR broken MIR // 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,)); - foo.method::<(i32, String)>(42i32, "asdf".to_owned()); //~ ERROR broken MIR + // 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..2798140a63351 --- /dev/null +++ b/tests/ui/splat/splat-trait-tuple.rs @@ -0,0 +1,42 @@ +//@ 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)) {} +} + +struct TupleStruct(u32, i8); //~ WARN fields `0` and `1` are never read + +impl FooTrait for TupleStruct { + fn tuple_1_trait(#[splat] _: (u32,)) {} + + fn tuple_2_trait(&self, #[splat] _: (u32, f32)) {} +} + +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_trait((1u32,)); + + let foo = Foo; + 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/splat/splat-trait-tuple.stderr b/tests/ui/splat/splat-trait-tuple.stderr new file mode 100644 index 0000000000000..8d0e1c772d318 --- /dev/null +++ b/tests/ui/splat/splat-trait-tuple.stderr @@ -0,0 +1,13 @@ +warning: fields `0` and `1` are never read + --> $DIR/splat-trait-tuple.rs:22:20 + | +LL | struct TupleStruct(u32, i8); + | ----------- ^^^ ^^ + | | + | fields in this struct + | + = help: consider removing these fields + = note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default + +warning: 1 warning emitted + diff --git a/tests/ui/splat/splat-tuple.rs b/tests/ui/splat/splat-tuple.rs deleted file mode 100644 index 77af11f814644..0000000000000 --- a/tests/ui/splat/splat-tuple.rs +++ /dev/null @@ -1,104 +0,0 @@ -//@ dont-check-compiler-stderr -//@ dont-check-failure-status -//@ dont-require-annotations: ERROR -// FIXME(splat): ^change the actual types during typeck so MIR doesn't ICE. - -//! Test using `#[splat]` on tuple function arguments. - -#![allow(incomplete_features)] -#![feature(splat)] -#![feature(tuple_trait)] - -fn tuple_args(#[splat] (a, b): (u32, i8)) {} - -trait FooTrait { - fn tuple_1_trait(#[splat] _: (u32,)); - - // Ambiguous case, self could be a tuple or a non-tuple. - fn tuple_4_trait(#[splat] &self, _: (u32, i8, (), f32)) {} -} - -struct Foo; - -impl Foo { - fn tuple_1(#[splat] (a,): (u32,)) {} - - fn tuple_2(&self, #[splat] (a, b): (u32, i8)) -> u32 { - a - } - - fn tuple_3(#[splat] (a, b, c): (u32, i32, i8)) {} - - fn tuple_4(&self, #[splat] (a, b, c, d): (u32, i8, (), f32)) -> u32 { - a - } -} - -impl FooTrait for Foo { - // FIXME(splat): should splat attributes be inherited from traits? - // Can splat attributes be added on impls? - fn tuple_1_trait(#[splat] _: (u32,)) {} -} - -struct TupleStruct(u32, i8); - -impl TupleStruct { - // FIXME(splat): tuple structs should error until we have a specific use case for them. - fn tuple_2(#[splat] &self, (a, b): (u32, i8)) -> u32 { - a - } -} - -impl FooTrait for TupleStruct { - fn tuple_1_trait(#[splat] _: (u32,)) {} -} - -extern "C" { - // FIXME(splat): tuple layouts are unspecified, so this should error. - #[expect(improper_ctypes)] - fn bar_2(#[splat] _: (u32, i8)); -} - -// FIXME(splat): multiple splats in a fn should error. -fn multisplat_bad_2(#[splat] (a, b): (u32, i8), #[splat] (c, d): (u32, i8)) {} - -// FIXME(splat): non-terminal splat attributes should error, until we have a specific use case for -// them. -fn splat_non_terminal_bad(#[splat] (a, b): (u32, i8), (c, d): (u32, i8)) {} - -fn splat_generic_tuple(#[splat] t: T) {} - -fn main() { - // FIXME(splat): actually modify the argument list during typeck, to avoid "broken MIR" errors - tuple_args(1, 2); //~ ERROR: broken MIR in - // 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(1u32, 2i8); //~ ERROR: broken MIR in - - let foo = Foo; - Foo::tuple_1(1u32); //~ ERROR: broken MIR in - foo.tuple_2(1u32, 2i8); //~ ERROR: broken MIR in - Foo::tuple_3(1u32, 2i32, 3i8); //~ ERROR broken MIR - foo.tuple_4(1u32, 2i8, (), 3f32); - - // FIXME(splat): this should error because `self` is splatted, but `Foo` is not a tuple. - Foo::tuple_1_trait(1u32); - foo.tuple_4_trait((1u32, 2i8, (), 3f32)); - - let tuple_struct = TupleStruct(1u32, 2i8); - // FIXME(splat): this should error because `self` is splatted, but `TupleStruct` is not a tuple. - tuple_struct.tuple_2((1u32, 2i8)); - - TupleStruct::tuple_1_trait(1u32); - tuple_struct.tuple_4_trait((1u32, 2i8, (), 3f32)); - - // FIXME(splat): generic tuple trait implementers should work without explicit tuple type - // parameters. - splat_generic_tuple::<(u32, i8)>(1u32, 2i8); - splat_generic_tuple(1u32, 2i8); - splat_generic_tuple(1, 2); - - splat_generic_tuple::<()>(); - splat_generic_tuple(); -}