From 1f1f5d52b457e8a5a0f5508311b4f3a276b30db0 Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 21 Apr 2026 14:48:44 +1000 Subject: [PATCH 1/6] Add rust-analyzer test cache dirs to .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 91a2647ca98f4..a3e9bafcb2e3d 100644 --- a/.gitignore +++ b/.gitignore @@ -56,6 +56,9 @@ no_llvm_build /src/bootstrap/target /src/ci/citool/target /src/tools/x/target +# Created by `x rust-analyzer[-clippy]` tests +/library/core/src/iter/traits/target +/library/core/src/slice/target # Created by `x vendor` /vendor # Created by default with `src/ci/docker/run.sh` From 8401844b867b4ac418c5fe0daabde23558619ee5 Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 22 Apr 2026 16:09:06 +1000 Subject: [PATCH 2/6] Fix some comment typos in tests --- tests/coverage/assert.coverage | 2 +- tests/coverage/assert.rs | 2 +- tests/ui/argument-suggestions/exotic-calls.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/coverage/assert.coverage b/tests/coverage/assert.coverage index 8b0d337980bcb..29a5b48c0566a 100644 --- a/tests/coverage/assert.coverage +++ b/tests/coverage/assert.coverage @@ -27,7 +27,7 @@ LL| |// 3. Notably, the `assert` macros *do not* generate `TerminatorKind::Assert`. The macros produce LL| |// conditional expressions, `TerminatorKind::SwitchInt` branches, and a possible call to LL| |// `begin_panic_fmt()` (that begins a panic unwind, if the assertion test fails). - LL| |// 4. `TerminatoKind::Assert` is, however, also present in the MIR generated for this test + LL| |// 4. `TerminatorKind::Assert` is, however, also present in the MIR generated for this test LL| |// (and in many other coverage tests). The `Assert` terminator is typically generated by the LL| |// Rust compiler to check for runtime failures, such as numeric overflows. diff --git a/tests/coverage/assert.rs b/tests/coverage/assert.rs index bb7741bedbb5c..30d511f8f7a89 100644 --- a/tests/coverage/assert.rs +++ b/tests/coverage/assert.rs @@ -27,6 +27,6 @@ fn main() -> Result<(), u8> { // 3. Notably, the `assert` macros *do not* generate `TerminatorKind::Assert`. The macros produce // conditional expressions, `TerminatorKind::SwitchInt` branches, and a possible call to // `begin_panic_fmt()` (that begins a panic unwind, if the assertion test fails). -// 4. `TerminatoKind::Assert` is, however, also present in the MIR generated for this test +// 4. `TerminatorKind::Assert` is, however, also present in the MIR generated for this test // (and in many other coverage tests). The `Assert` terminator is typically generated by the // Rust compiler to check for runtime failures, such as numeric overflows. diff --git a/tests/ui/argument-suggestions/exotic-calls.rs b/tests/ui/argument-suggestions/exotic-calls.rs index 765b4bc536c3a..782e6c4e4777c 100644 --- a/tests/ui/argument-suggestions/exotic-calls.rs +++ b/tests/ui/argument-suggestions/exotic-calls.rs @@ -1,4 +1,4 @@ -//! Checks variations of E0057, which is the incorrect number of agruments passed into a closure +//! Checks variations of E0057, which is the incorrect number of arguments passed into a closure //@ check-fail From 125d25dd7c08e1da3a6960237bf901c0123948c0 Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 11 Mar 2026 17:34:32 +1000 Subject: [PATCH 3/6] 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 | 8 ++ 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, 244 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 0541d39318f47..3a088ef66b0f8 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -394,6 +394,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 5831636a81b2c..57738f807d106 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -506,6 +506,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) { gate_all!(pin_ergonomics, "pinned reference syntax is experimental"); gate_all!(postfix_match, "postfix match is experimental"); gate_all!(return_type_notation, "return type notation is experimental"); + gate_all!(splat, "`fn(#[splat] (a, ...))` is incomplete", "call as func((a, ...)) instead"); gate_all!(super_let, "`super let` is experimental"); gate_all!(try_blocks_heterogeneous, "`try bikeshed` expression is experimental"); gate_all!(unsafe_binders, "unsafe binder types are experimental"); diff --git a/compiler/rustc_attr_parsing/src/attributes/mod.rs b/compiler/rustc_attr_parsing/src/attributes/mod.rs index ba2e2b54e25b5..8ff69591e7c56 100644 --- a/compiler/rustc_attr_parsing/src/attributes/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/mod.rs @@ -64,6 +64,7 @@ pub(crate) mod rustc_allocator; pub(crate) mod rustc_dump; pub(crate) mod rustc_internal; pub(crate) mod semantics; +pub(crate) mod splat; pub(crate) mod stability; pub(crate) mod test_attrs; pub(crate) mod traits; diff --git a/compiler/rustc_attr_parsing/src/attributes/splat.rs b/compiler/rustc_attr_parsing/src/attributes/splat.rs new file mode 100644 index 0000000000000..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 becdaee0f3d3a..30d56dd618f41 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -55,6 +55,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::*; @@ -336,6 +337,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 1b96be12f71da..11b1d0a7b766f 100644 --- a/compiler/rustc_codegen_ssa/src/mir/block.rs +++ b/compiler/rustc_codegen_ssa/src/mir/block.rs @@ -1137,6 +1137,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 93da12107bab0..c7c976b8b138d 100644 --- a/compiler/rustc_codegen_ssa/src/mir/mod.rs +++ b/compiler/rustc_codegen_ssa/src/mir/mod.rs @@ -402,6 +402,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 cc4f1c300f391..012125766c68c 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -653,6 +653,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 053caee258f7b..60def28b9b0e5 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -392,6 +392,14 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ // - https://github.com/rust-lang/rust/issues/130494 gated!(pin_v2, pin_ergonomics, experimental!(pin_v2)), + // The `#[splat]` attribute is part of the `splat` experiment + // that improves the ergonomics of function overloading, tracked in: + // + // - https://github.com/rust-lang/rust/issues/153629 + gated!( + splat, experimental!(splat), + ), + // ========================================================================== // Internal attributes: Stability, deprecation, and unsafe: // ========================================================================== diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 82402b3c7e31c..ec403f9804446 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -679,6 +679,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 05f398058ecaa..65fb27edf3cca 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -1620,6 +1620,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 ad4d0728888bf..3f9eda6ca00a6 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -192,6 +192,7 @@ impl AttributeKind { RustcUnsafeSpecializationMarker(..) => No, Sanitize { .. } => No, ShouldPanic { .. } => No, + Splat(..) => Yes, Stability { .. } => Yes, TargetFeature { .. } => No, TestRunner(..) => Yes, diff --git a/compiler/rustc_hir_analysis/src/check/entry.rs b/compiler/rustc_hir_analysis/src/check/entry.rs index 9c9ac5b75afe6..79d59f5ecc13a 100644 --- a/compiler/rustc_hir_analysis/src/check/entry.rs +++ b/compiler/rustc_hir_analysis/src/check/entry.rs @@ -97,6 +97,7 @@ fn check_main_fn_ty(tcx: TyCtxt<'_>, main_def_id: DefId) -> Result<(), ErrorGuar .emit_err(errors::MainFunctionAsync { span: main_span, asyncness: asyncness_span })); } + // FIXME(splat): also reject `#[splat]` on main function arguments if let Some(attr_span) = find_attr!(tcx, main_def_id, TrackCaller(span) => *span) { return Err(tcx .dcx() diff --git a/compiler/rustc_mir_build/src/builder/mod.rs b/compiler/rustc_mir_build/src/builder/mod.rs index 8e51ab7d4edb1..1717e5b4b716b 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 10a805a159b06..ff180f07447df 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -377,6 +377,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 981bfed363dcc..8d0db8770fbed 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1943,6 +1943,7 @@ symbols! { specialization, speed, spirv, + splat, spotlight, sqrtf16, sqrtf32, diff --git a/tests/ui/README.md b/tests/ui/README.md index 9ef331698d2b6..226c07d31c604 100644 --- a/tests/ui/README.md +++ b/tests/ui/README.md @@ -1292,6 +1292,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 fbecb233a4e0226ab07ef3f81d6472a280225dd0 Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 18 Mar 2026 18:09:57 +1000 Subject: [PATCH 4/6] Impl typeck & MIR lowering for argument splatting --- compiler/rustc_ast/src/ast.rs | 21 ++ compiler/rustc_ast_lowering/src/delegation.rs | 23 +- compiler/rustc_ast_lowering/src/lib.rs | 7 +- .../rustc_ast_passes/src/ast_validation.rs | 56 +++- compiler/rustc_ast_passes/src/errors.rs | 27 ++ .../src/diagnostics/region_errors.rs | 7 +- .../rustc_codegen_cranelift/src/abi/mod.rs | 1 + .../src/debuginfo/type_names.rs | 1 + .../src/const_eval/type_info.rs | 11 +- compiler/rustc_hir/src/hir.rs | 79 ++++- .../rustc_hir_analysis/src/check/entry.rs | 1 - compiler/rustc_hir_analysis/src/check/mod.rs | 7 +- .../rustc_hir_analysis/src/check/wfcheck.rs | 12 +- .../src/hir_ty_lowering/mod.rs | 5 +- compiler/rustc_hir_pretty/src/lib.rs | 3 + compiler/rustc_hir_typeck/src/callee.rs | 37 ++- compiler/rustc_hir_typeck/src/closure.rs | 1 + compiler/rustc_hir_typeck/src/errors.rs | 1 + compiler/rustc_hir_typeck/src/expr.rs | 32 +- .../rustc_hir_typeck/src/fn_ctxt/_impl.rs | 39 ++- .../rustc_hir_typeck/src/fn_ctxt/checks.rs | 277 +++++++++++++++--- compiler/rustc_hir_typeck/src/lib.rs | 18 +- compiler/rustc_hir_typeck/src/writeback.rs | 5 + compiler/rustc_lint/src/foreign_modules.rs | 8 + compiler/rustc_middle/src/ty/context.rs | 39 ++- compiler/rustc_middle/src/ty/error.rs | 15 + compiler/rustc_middle/src/ty/mod.rs | 3 +- compiler/rustc_middle/src/ty/print/pretty.rs | 15 +- .../rustc_middle/src/ty/typeck_results.rs | 35 +++ compiler/rustc_mir_build/src/builder/mod.rs | 2 +- compiler/rustc_mir_build/src/thir/cx/expr.rs | 201 ++++++++++++- .../src/unstable/convert/internal.rs | 1 + .../src/unstable/convert/stable/ty.rs | 1 + compiler/rustc_symbol_mangling/src/v0.rs | 1 + .../src/error_reporting/infer/mod.rs | 10 + .../src/error_reporting/traits/suggestions.rs | 1 + compiler/rustc_type_ir/src/error.rs | 3 +- compiler/rustc_type_ir/src/inherent.rs | 36 ++- compiler/rustc_type_ir/src/relate.rs | 4 + compiler/rustc_type_ir/src/ty_kind.rs | 155 +++++++++- compiler/rustc_type_ir/src/ty_kind/closure.rs | 2 +- src/tools/miri/src/helpers.rs | 2 + src/tools/miri/src/shims/sig.rs | 2 +- .../crates/hir-ty/src/infer/callee.rs | 2 + .../rust-analyzer/crates/hir-ty/src/lib.rs | 1 + .../rust-analyzer/crates/hir-ty/src/lower.rs | 1 + .../crates/hir-ty/src/next_solver/interner.rs | 1 + .../crates/hir-ty/src/next_solver/ty.rs | 1 + tests/ui/splat/splat-assoc-fn-tuple-simple.rs | 22 ++ tests/ui/splat/splat-fn-ptr-tuple.rs | 51 ++++ tests/ui/splat/splat-fn-ptr-tuple.stderr | 24 ++ tests/ui/splat/splat-fn-tuple-generic-fail.rs | 27 ++ .../splat/splat-fn-tuple-generic-fail.stderr | 39 +++ tests/ui/splat/splat-fn-tuple-generic.rs | 23 ++ tests/ui/splat/splat-fn-tuple-simple.rs | 32 ++ tests/ui/splat/splat-generics-everywhere.rs | 68 +++++ tests/ui/splat/splat-invalid.rs | 47 +++ tests/ui/splat/splat-invalid.stderr | 106 +++++++ tests/ui/splat/splat-method-tuple-simple.rs | 40 +++ ...at-non-function.rs => splat-non-fn-arg.rs} | 41 +-- ...unction.stderr => splat-non-fn-arg.stderr} | 30 +- tests/ui/splat/splat-non-tuple.rs | 78 +++++ tests/ui/splat/splat-non-tuple.stderr | 17 ++ tests/ui/splat/splat-overload-at-home.rs | 52 ++++ tests/ui/splat/splat-trait-tuple.rs | 45 +++ tests/ui/symbol-names/basic.legacy.stderr | 4 +- .../ui/symbol-names/issue-60925.legacy.stderr | 4 +- 67 files changed, 1771 insertions(+), 192 deletions(-) create mode 100644 tests/ui/splat/splat-assoc-fn-tuple-simple.rs create mode 100644 tests/ui/splat/splat-fn-ptr-tuple.rs create mode 100644 tests/ui/splat/splat-fn-ptr-tuple.stderr create mode 100644 tests/ui/splat/splat-fn-tuple-generic-fail.rs create mode 100644 tests/ui/splat/splat-fn-tuple-generic-fail.stderr create mode 100644 tests/ui/splat/splat-fn-tuple-generic.rs create mode 100644 tests/ui/splat/splat-fn-tuple-simple.rs create mode 100644 tests/ui/splat/splat-generics-everywhere.rs create mode 100644 tests/ui/splat/splat-invalid.rs create mode 100644 tests/ui/splat/splat-invalid.stderr create mode 100644 tests/ui/splat/splat-method-tuple-simple.rs 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} (73%) create mode 100644 tests/ui/splat/splat-non-tuple.rs create mode 100644 tests/ui/splat/splat-non-tuple.stderr create mode 100644 tests/ui/splat/splat-overload-at-home.rs create mode 100644 tests/ui/splat/splat-trait-tuple.rs diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index b2c573c23f891..b4bc77b3a8d5c 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -3055,9 +3055,30 @@ impl FnDecl { pub fn has_self(&self) -> bool { self.inputs.get(0).is_some_and(Param::is_self) } + pub fn c_variadic(&self) -> bool { self.inputs.last().is_some_and(|arg| matches!(arg.ty.kind, TyKind::CVarArgs)) } + + /// The marker index for "no splatted arguments". + /// Must have the same value as `FnSigKind::NO_SPLATTED_ARG_INDEX` and `FnDeclFlags::NO_SPLATTED_ARG_INDEX`. + pub const NO_SPLATTED_ARG_INDEX: u16 = u16::MAX; + + /// Returns a splatted argument index, if any are present. + pub fn splatted(&self) -> Option { + self.inputs.iter().enumerate().find_map(|(index, arg)| { + if index == Self::NO_SPLATTED_ARG_INDEX as usize { + // AST validation has already checked the splatted argument index is valid, so just + // ignore invalid indexes here. + None + } else { + arg.attrs + .iter() + .any(|attr| attr.has_name(sym::splat)) + .then_some(u16::try_from(index).unwrap()) + } + }) + } } /// Is the trait definition an auto trait? diff --git a/compiler/rustc_ast_lowering/src/delegation.rs b/compiler/rustc_ast_lowering/src/delegation.rs index 01382a69f2ec0..30ae8e2615f5d 100644 --- a/compiler/rustc_ast_lowering/src/delegation.rs +++ b/compiler/rustc_ast_lowering/src/delegation.rs @@ -141,7 +141,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); @@ -153,8 +153,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); @@ -268,10 +274,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*/, Option /*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( @@ -279,6 +285,7 @@ impl<'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'_, 'hir, R> { sig_id: DefId, param_count: usize, c_variadic: bool, + splatted: Option, span: Span, generics: &GenericsGenerationResults<'hir>, ) -> &'hir hir::FnDecl<'hir> { @@ -311,7 +318,9 @@ impl<'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'_, 'hir, R> { output: hir::FnRetTy::Return(output), fn_decl_kind: FnDeclFlags::default() .set_lifetime_elision_allowed(true) - .set_c_variadic(c_variadic), + .set_c_variadic(c_variadic) + .set_splatted(splatted, inputs.len()) + .unwrap(), }) } diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 2ffb9c123b5f8..377211f999de7 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -1844,12 +1844,15 @@ impl<'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'_, 'hir, R> { coro: Option, ) -> &'hir hir::FnDecl<'hir> { let c_variadic = decl.c_variadic(); + let mut splatted = decl.splatted(); // Skip the `...` (`CVarArgs`) trailing arguments from the AST, // as they are not explicit in HIR/Ty function signatures. // (instead, the `c_variadic` flag is set to `true`) let mut inputs = &decl.inputs[..]; if decl.c_variadic() { + // Splat + variadic errors in AST validation, so just ignore one of them here. + splatted = None; inputs = &inputs[..inputs.len() - 1]; } let inputs = self.arena.alloc_from_iter(inputs.iter().map(|param| { @@ -1937,7 +1940,9 @@ impl<'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'_, 'hir, R> { } })) .set_lifetime_elision_allowed(self.resolver.lifetime_elision_allowed(fn_node_id)) - .set_c_variadic(c_variadic); + .set_c_variadic(c_variadic) + .set_splatted(splatted, inputs.len()) + .unwrap(); self.arena.alloc(hir::FnDecl { inputs, output, fn_decl_kind }) } diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index 3a088ef66b0f8..a85c0e6ec8a3c 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -350,7 +350,8 @@ impl<'a> AstValidator<'a> { fn check_fn_decl(&self, fn_decl: &FnDecl, self_semantic: SelfSemantic) { self.check_decl_num_args(fn_decl); - self.check_decl_cvariadic_pos(fn_decl); + let c_variadic_span = self.check_decl_cvariadic_pos(fn_decl); + self.check_decl_splatting(fn_decl, c_variadic_span); self.check_decl_attrs(fn_decl); self.check_decl_self_param(fn_decl, self_semantic); } @@ -368,17 +369,68 @@ impl<'a> AstValidator<'a> { /// Emits an error if a function declaration has a variadic parameter in the /// beginning or middle of parameter list. /// Example: `fn foo(..., x: i32)` will emit an error. - fn check_decl_cvariadic_pos(&self, fn_decl: &FnDecl) { + /// Returns true if a C-variadic parameter is found. + fn check_decl_cvariadic_pos(&self, fn_decl: &FnDecl) -> Option { + let mut c_variadic_span = None; + match &*fn_decl.inputs { [ps @ .., _] => { for Param { ty, span, .. } in ps { if let TyKind::CVarArgs = ty.kind { + c_variadic_span = Some(*span); self.dcx().emit_err(errors::FnParamCVarArgsNotLast { span: *span }); } } } _ => {} } + + if let Some(Param { ty, span, .. }) = &fn_decl.inputs.last() { + if let TyKind::CVarArgs = ty.kind { + c_variadic_span = Some(*span); + } + } + + c_variadic_span + } + + /// Emits an error if a function declaration has more than one splatted argument, with a + /// C-variadic parameter, or a splat at an unsupported index (for performance). + /// Example: `fn foo(#[splat] x: (), #[splat] y: ())` will emit an error. + fn check_decl_splatting(&self, fn_decl: &FnDecl, c_variadic_span: Option) { + let (splatted_arg_indexes, mut splatted_spans): (Vec, Vec) = fn_decl + .inputs + .iter() + .enumerate() + .filter_map(|(index, arg)| { + arg.attrs + .iter() + .any(|attr| attr.has_name(sym::splat)) + .then_some((u16::try_from(index).unwrap(), arg.span)) + }) + .unzip(); + + // A splatted argument at the "no splatted" marker index is not supported (this is an + // unlikely edge case). + if let (Some(&splatted_arg_index), Some(&splatted_span)) = + (splatted_arg_indexes.last(), splatted_spans.last()) + && splatted_arg_index == FnDecl::NO_SPLATTED_ARG_INDEX + { + self.dcx() + .emit_err(errors::InvalidSplattedArg { splatted_arg_index, span: splatted_span }); + } + + // Multiple splatted arguments are invalid: we can't know which arguments go in each splat. + if splatted_arg_indexes.len() > 1 { + self.dcx().emit_err(errors::DuplicateSplattedArgs { spans: splatted_spans.clone() }); + } + + if let Some(c_variadic_span) = c_variadic_span + && !splatted_spans.is_empty() + { + splatted_spans.push(c_variadic_span); + self.dcx().emit_err(errors::CVarArgsAndSplat { spans: splatted_spans }); + } } fn check_decl_attrs(&self, fn_decl: &FnDecl) { diff --git a/compiler/rustc_ast_passes/src/errors.rs b/compiler/rustc_ast_passes/src/errors.rs index 7cc0043e9e33c..f67652a9fa250 100644 --- a/compiler/rustc_ast_passes/src/errors.rs +++ b/compiler/rustc_ast_passes/src/errors.rs @@ -124,6 +124,33 @@ pub(crate) struct FnParamCVarArgsNotLast { pub span: Span, } +#[derive(Diagnostic)] +#[diag("`#[splat]` is not supported on argument index {$splatted_arg_index}")] +#[help("remove `#[splat]`, or use it on an argument closer to the start of the argument list")] +pub(crate) struct InvalidSplattedArg { + pub splatted_arg_index: u16, + + #[primary_span] + #[label("`#[splat]` is not supported here")] + pub span: Span, +} + +#[derive(Diagnostic)] +#[diag("multiple `#[splat]`s are not allowed in the same function")] +#[help("remove `#[splat]` from all but one argument")] +pub(crate) struct DuplicateSplattedArgs { + #[primary_span] + pub spans: Vec, +} + +#[derive(Diagnostic)] +#[diag("`...` and `#[splat]` are not allowed in the same function")] +#[help("remove `#[splat]` or remove `...`")] +pub(crate) struct CVarArgsAndSplat { + #[primary_span] + pub spans: Vec, +} + #[derive(Diagnostic)] #[diag("documentation comments cannot be applied to function parameters")] pub(crate) struct FnParamDocComment { diff --git a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs index 4e5f4408ca31a..8c7cf1f09a0f0 100644 --- a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs @@ -15,8 +15,7 @@ use rustc_middle::bug; use rustc_middle::hir::place::PlaceBase; use rustc_middle::mir::{AnnotationSource, ConstraintCategory, ReturnConstraint}; use rustc_middle::ty::{ - self, FnSigKind, GenericArgs, Region, RegionVid, Ty, TyCtxt, TypeFoldable, TypeVisitor, - fold_regions, + self, GenericArgs, Region, RegionVid, Ty, TyCtxt, TypeFoldable, TypeVisitor, fold_regions, }; use rustc_span::{Ident, Span, kw}; use rustc_trait_selection::error_reporting::InferCtxtErrorExt; @@ -1085,8 +1084,8 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { } // Build a new closure where the return type is an owned value, instead of a ref. - let fn_sig_kind = - FnSigKind::default().set_safe(true).set_c_variadic(liberated_sig.c_variadic()); + // The new closure is safe, but otherwise has the same ABI, splat, and c-variadic. + let fn_sig_kind = liberated_sig.fn_sig_kind.set_safe(true); let closure_sig_as_fn_ptr_ty = Ty::new_fn_ptr( tcx, ty::Binder::dummy(tcx.mk_fn_sig( diff --git a/compiler/rustc_codegen_cranelift/src/abi/mod.rs b/compiler/rustc_codegen_cranelift/src/abi/mod.rs index 13f5ad5157cef..755ee0aa4bd20 100644 --- a/compiler/rustc_codegen_cranelift/src/abi/mod.rs +++ b/compiler/rustc_codegen_cranelift/src/abi/mod.rs @@ -265,6 +265,7 @@ pub(crate) fn codegen_fn_prelude<'tcx>(fx: &mut FunctionCx<'_, '_, 'tcx>, start_ .map(|local| { let arg_ty = fx.monomorphize(fx.mir.local_decls[local].ty); + // FIXME(splat): un-tuple splatted arguments in codegen, for performance // Adapted from https://github.com/rust-lang/rust/blob/145155dc96757002c7b2e9de8489416e2fdbbd57/src/librustc_codegen_llvm/mir/mod.rs#L442-L482 if Some(local) == fx.mir.spread_arg { // This argument (e.g. the last argument in the "rust-call" ABI) diff --git a/compiler/rustc_codegen_ssa/src/debuginfo/type_names.rs b/compiler/rustc_codegen_ssa/src/debuginfo/type_names.rs index b4c6ecd069f82..6a3a1dff43a8d 100644 --- a/compiler/rustc_codegen_ssa/src/debuginfo/type_names.rs +++ b/compiler/rustc_codegen_ssa/src/debuginfo/type_names.rs @@ -375,6 +375,7 @@ fn push_debuginfo_type_name<'tcx>( output.push_str("fn("); } + // FIXME(splat): should debuginfo be de-tupled in the callee (and caller)? if !sig.inputs().is_empty() { for ¶meter_type in sig.inputs() { push_debuginfo_type_name(tcx, parameter_type, true, output, visited); diff --git a/compiler/rustc_const_eval/src/const_eval/type_info.rs b/compiler/rustc_const_eval/src/const_eval/type_info.rs index 7b63ab5bb02e8..be940b3e5e980 100644 --- a/compiler/rustc_const_eval/src/const_eval/type_info.rs +++ b/compiler/rustc_const_eval/src/const_eval/type_info.rs @@ -7,7 +7,7 @@ use rustc_ast::Mutability; use rustc_hir::LangItem; use rustc_middle::span_bug; use rustc_middle::ty::layout::TyAndLayout; -use rustc_middle::ty::{self, Const, FnHeader, FnSigTys, ScalarInt, Ty, TyCtxt}; +use rustc_middle::ty::{self, Const, FnHeader, FnSigKind, FnSigTys, ScalarInt, Ty, TyCtxt}; use rustc_span::{Symbol, sym}; use crate::const_eval::CompileTimeMachine; @@ -465,6 +465,15 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> { sym::variadic => { self.write_scalar(Scalar::from_bool(fn_sig_kind.c_variadic()), &field_place)?; } + sym::splat => { + self.write_scalar( + // Use the same encoding as FnSigKind.splatted + Scalar::from_u16( + fn_sig_kind.splatted().unwrap_or(FnSigKind::NO_SPLATTED_ARG_INDEX), + ), + &field_place, + )?; + } other => span_bug!(self.tcx.def_span(field.did), "unimplemented field {other}"), } } diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index d8149b4e30a59..1832f9768c159 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -3921,6 +3921,18 @@ pub struct Param<'hir> { pub span: Span, } +/// Error type for splatted argument index errors. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SplattedArgIndexError { + /// The splatted argument index is invalid. + /// Currently the only unsupported index is `u16::MAX`, which is used to indicate that no argument + /// is splatted. + InvalidIndex { splatted_arg_index: u16 }, + + /// The splatted argument index is outside the bounds of the function arguments. + OutOfBounds { splatted_arg_index: u16, args_len: u16 }, +} + /// Contains the packed non-type fields of a function declaration. // FIXME(splat): add the splatted argument index as a u16 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -3928,6 +3940,11 @@ pub struct Param<'hir> { pub struct FnDeclFlags { /// Holds the c_variadic and lifetime_elision_allowed bitflags, and 3 bits for the `ImplicitSelfKind`. flags: u8, + + /// Which function argument is splatted into multiple arguments in callers, if any? + /// Splatting functions with `u16::MAX` arguments is not supported, see `FnSigKind` for + /// details. + splatted: u16, } impl fmt::Debug for FnDeclFlags { @@ -3939,11 +3956,15 @@ impl fmt::Debug for FnDeclFlags { f.field(&"LifetimeElisionAllowed"); } else { f.field(&"NoLifetimeElision"); - }; + } if self.c_variadic() { f.field(&"CVariadic"); - }; + } + + if let Some(index) = self.splatted() { + f.field(&format!("Splatted({})", index)); + } f.finish() } @@ -3959,14 +3980,20 @@ impl FnDeclFlags { /// Bitflag for lifetime elision. const LIFETIME_ELISION_ALLOWED_FLAG: u8 = 1 << 4; - /// Create a new FnDeclKind with no implicit self, no lifetime elision, and no C-style variadic argument. + /// Marker index for "no splatted argument". + /// Must have the same value as `FnSigKind::NO_SPLATTED_ARG_INDEX` and `rustc_ast::FnDecl::NO_SPLATTED_ARG_INDEX`. + const NO_SPLATTED_ARG_INDEX: u16 = u16::MAX; + + /// Create a new FnDeclKind with no implicit self, no lifetime elision, no C-style variadic + /// argument, and no splatting. /// To modify these flags, use the `set_*` methods, for readability. // FIXME: use Default instead when that trait is const stable. pub const fn default() -> Self { - Self { flags: 0 } + Self { flags: 0, splatted: 0 } .set_implicit_self(ImplicitSelfKind::None) .set_lifetime_elision_allowed(false) .set_c_variadic(false) + .set_no_splatted_args() } /// Set the implicit self kind. @@ -4009,6 +4036,41 @@ impl FnDeclFlags { self } + /// Set the splatted argument index. + /// The number of function arguments is used for error checking. + #[must_use = "this method does not modify the receiver"] + pub const fn set_splatted( + mut self, + splatted: Option, + args_len: usize, + ) -> Result { + if let Some(splatted_arg_index) = splatted { + if splatted_arg_index == Self::NO_SPLATTED_ARG_INDEX { + // This index value is used as a marker for "no splatting", so it is unsupported. + return Err(SplattedArgIndexError::InvalidIndex { splatted_arg_index }); + } else if splatted_arg_index as usize >= args_len { + return Err(SplattedArgIndexError::OutOfBounds { + splatted_arg_index, + args_len: args_len as u16, + }); + } + + self.splatted = splatted_arg_index; + } else { + self.splatted = Self::NO_SPLATTED_ARG_INDEX; + } + + Ok(self) + } + + /// Set "no splatted arguments" for the function declaration. + #[must_use = "this method does not modify the receiver"] + pub const fn set_no_splatted_args(mut self) -> Self { + self.splatted = Self::NO_SPLATTED_ARG_INDEX; + + self + } + /// Get the implicit self kind. pub const fn implicit_self(self) -> ImplicitSelfKind { match self.flags & Self::IMPLICIT_SELF_MASK { @@ -4030,6 +4092,11 @@ impl FnDeclFlags { pub const fn lifetime_elision_allowed(self) -> bool { self.flags & Self::LIFETIME_ELISION_ALLOWED_FLAG != 0 } + + /// Get the splatted argument index, if any. + pub const fn splatted(self) -> Option { + if self.splatted == Self::NO_SPLATTED_ARG_INDEX { None } else { Some(self.splatted) } + } } /// Represents the header (not the body) of a function declaration. @@ -4077,6 +4144,10 @@ impl<'hir> FnDecl<'hir> { self.fn_decl_kind.lifetime_elision_allowed() } + pub fn splatted(&self) -> Option { + self.fn_decl_kind.splatted() + } + pub fn dummy(span: Span) -> Self { Self { inputs: &[], diff --git a/compiler/rustc_hir_analysis/src/check/entry.rs b/compiler/rustc_hir_analysis/src/check/entry.rs index 79d59f5ecc13a..9c9ac5b75afe6 100644 --- a/compiler/rustc_hir_analysis/src/check/entry.rs +++ b/compiler/rustc_hir_analysis/src/check/entry.rs @@ -97,7 +97,6 @@ fn check_main_fn_ty(tcx: TyCtxt<'_>, main_def_id: DefId) -> Result<(), ErrorGuar .emit_err(errors::MainFunctionAsync { span: main_span, asyncness: asyncness_span })); } - // FIXME(splat): also reject `#[splat]` on main function arguments if let Some(attr_span) = find_attr!(tcx, main_def_id, TrackCaller(span) => *span) { return Err(tcx .dcx() diff --git a/compiler/rustc_hir_analysis/src/check/mod.rs b/compiler/rustc_hir_analysis/src/check/mod.rs index 6862b6fe863f8..e23b4e5713024 100644 --- a/compiler/rustc_hir_analysis/src/check/mod.rs +++ b/compiler/rustc_hir_analysis/src/check/mod.rs @@ -445,12 +445,14 @@ fn fn_sig_suggestion<'tcx>( predicates: impl IntoIterator, Span)>, assoc: ty::AssocItem, ) -> String { + let splatted_arg_index = sig.splatted().map(usize::from); let args = sig .inputs() .iter() .enumerate() .map(|(i, ty)| { - Some(match ty.kind() { + let splat = if splatted_arg_index == Some(i) { "#[splat] " } else { "" }; + let arg_ty = match ty.kind() { ty::Param(_) if assoc.is_method() && i == 0 => "self".to_string(), ty::Ref(reg, ref_ty, mutability) if i == 0 => { let reg = format!("{reg} "); @@ -477,7 +479,8 @@ fn fn_sig_suggestion<'tcx>( format!("_: {ty}") } } - }) + }; + Some(format!("{splat}{arg_ty}")) }) .chain(std::iter::once(if sig.c_variadic() { Some("...".to_string()) } else { None })) .flatten() diff --git a/compiler/rustc_hir_analysis/src/check/wfcheck.rs b/compiler/rustc_hir_analysis/src/check/wfcheck.rs index 07ad2a79dc7ad..e7ee9843a5ba9 100644 --- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs +++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs @@ -1732,8 +1732,18 @@ fn check_fn_or_method<'tcx>( if sig.abi() == ExternAbi::RustCall { let span = tcx.def_span(def_id); - let has_implicit_self = hir_decl.implicit_self() != hir::ImplicitSelfKind::None; + let has_implicit_self = hir_decl.implicit_self().has_implicit_self(); let mut inputs = sig.inputs().iter().skip(if has_implicit_self { 1 } else { 0 }); + // FIXME(splat): support the rest of closure splatting, or replace this code with an error + if let Some(mut splatted_arg_index) = sig.splatted() { + let mut inputs_count = sig.inputs().len(); + if has_implicit_self { + splatted_arg_index = splatted_arg_index.strict_sub(1); + inputs_count = inputs_count.strict_sub(1); + } + debug!(?splatted_arg_index, ?inputs_count, ?has_implicit_self, ?sig); + sig = sig.set_splatted(Some(splatted_arg_index), inputs_count).unwrap(); + } // Check that the argument is a tuple and is sized if let Some(ty) = inputs.next() { wfcx.register_bound( diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs index a027c552d92b9..e3e6f4ad743b6 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs @@ -3588,10 +3588,13 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { debug!(?output_ty); + debug!(?abi, ?safety, ?decl.fn_decl_kind, input_tys_len = ?input_tys.len()); let fn_sig_kind = FnSigKind::default() .set_abi(abi) .set_safe(safety.is_safe()) - .set_c_variadic(decl.fn_decl_kind.c_variadic()); + .set_c_variadic(decl.fn_decl_kind.c_variadic()) + .set_splatted(decl.fn_decl_kind.splatted(), input_tys.len()) + .unwrap(); let fn_ty = tcx.mk_fn_sig(input_tys, output_ty, fn_sig_kind); let fn_ptr_ty = ty::Binder::bind_with_vars(fn_ty, bound_vars); diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs index 46aed3c5cd084..37a9f42ebdf8a 100644 --- a/compiler/rustc_hir_pretty/src/lib.rs +++ b/compiler/rustc_hir_pretty/src/lib.rs @@ -2264,6 +2264,9 @@ impl<'a> State<'a> { assert!(arg_idents.is_empty() || body_id.is_none()); let mut i = 0; let mut print_arg = |s: &mut Self, ty: Option<&hir::Ty<'_>>| { + if Some(i) == decl.splatted().map(usize::from) { + s.word("#[splat]"); + } if i == 0 && decl.implicit_self().has_implicit_self() { s.print_implicit_self(&decl.implicit_self()); } else { diff --git a/compiler/rustc_hir_typeck/src/callee.rs b/compiler/rustc_hir_typeck/src/callee.rs index e5fcc68dad23a..c5045611f94a9 100644 --- a/compiler/rustc_hir_typeck/src/callee.rs +++ b/compiler/rustc_hir_typeck/src/callee.rs @@ -53,9 +53,13 @@ pub(crate) fn check_legal_trait_for_method_call( tcx.ensure_result().coherent_trait(trait_id) } +/// State machine for typechecking a call, based on the callee type. #[derive(Debug)] enum CallStep<'tcx> { + /// Typecheck a call to a function definition or pointer. + /// Includes functions with splatted arguments. Builtin(Ty<'tcx>), + /// Deferred closure Fn* trait typechecking, when the callee is a closure. DeferredClosure(LocalDefId, ty::FnSig<'tcx>), /// Call overloading when callee implements one of the Fn* traits. Overloaded(MethodCallee<'tcx>), @@ -537,7 +541,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { arg_exprs: &'tcx [hir::Expr<'tcx>], expected: Expectation<'tcx>, ) -> Ty<'tcx> { - let (fn_sig, def_id) = match *callee_ty.kind() { + let (fn_sig, def_id, callee_generic_args) = match *callee_ty.kind() { ty::FnDef(def_id, args) => { self.enforce_context_effects(Some(call_expr.hir_id), call_expr.span, def_id, args); let fn_sig = self.tcx.fn_sig(def_id).instantiate(self.tcx, args).skip_norm_wip(); @@ -566,11 +570,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .emit(); } } - (fn_sig, Some(def_id)) + (fn_sig, Some(def_id), Some(args)) } // FIXME(const_trait_impl): these arms should error because we can't enforce them - ty::FnPtr(sig_tys, hdr) => (sig_tys.with(hdr), None), + ty::FnPtr(sig_tys, hdr) => (sig_tys.with(hdr), None, None), _ => unreachable!(), }; @@ -594,14 +598,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fn_sig.output(), expected, arg_exprs, - fn_sig.c_variadic(), - TupleArgumentsFlag::DontTupleArguments, + fn_sig.fn_sig_kind, + TupleArgumentsFlag::NotCallOper, def_id, + callee_generic_args, ); + // Splatting is currently incompatible with RustCall. if fn_sig.abi() == rustc_abi::ExternAbi::RustCall { let sp = arg_exprs.last().map_or(call_expr.span, |expr| expr.span); - if let Some(ty) = fn_sig.inputs().last().copied() { + if let Some(ty) = fn_sig.inputs().last().copied() + && fn_sig.splatted().is_none() + { self.register_bound( ty, self.tcx.require_lang_item(hir::LangItem::Tuple, sp), @@ -904,9 +912,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fn_sig.output(), expected, arg_exprs, - fn_sig.c_variadic(), - TupleArgumentsFlag::TupleArguments, + fn_sig.fn_sig_kind, + TupleArgumentsFlag::TupleAllCallArgs, Some(closure_def_id.to_def_id()), + None, ); fn_sig.output() @@ -976,6 +985,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { expected: Expectation<'tcx>, method: MethodCallee<'tcx>, ) -> Ty<'tcx> { + // FIXME(splat): if we ever support splatting here, decrement the splatted index, because + // the receiver argument is removed below. + assert_eq!( + method.sig.fn_sig_kind.splatted(), + None, + "splatting is not supported on RustCall tuples", + ); self.check_argument_types( call_expr.span, call_expr, @@ -983,9 +999,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { method.sig.output(), expected, arg_exprs, - method.sig.c_variadic(), - TupleArgumentsFlag::TupleArguments, + method.sig.fn_sig_kind, + TupleArgumentsFlag::TupleAllCallArgs, Some(method.def_id), + None, ); self.write_method_call_and_enforce_effects(call_expr.hir_id, call_expr.span, method); diff --git a/compiler/rustc_hir_typeck/src/closure.rs b/compiler/rustc_hir_typeck/src/closure.rs index d90cbaf935a71..593d793ed7e07 100644 --- a/compiler/rustc_hir_typeck/src/closure.rs +++ b/compiler/rustc_hir_typeck/src/closure.rs @@ -721,6 +721,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // in this binder we are creating. assert!(!expected_sig.sig.skip_binder().has_vars_bound_above(ty::INNERMOST)); let bound_sig = expected_sig.sig.map_bound(|sig| { + // Ignore splatting, it is unsupported on closures. let fn_sig_kind = FnSigKind::default() .set_abi(ExternAbi::RustCall) .set_safe(true) diff --git a/compiler/rustc_hir_typeck/src/errors.rs b/compiler/rustc_hir_typeck/src/errors.rs index b55e7933cc1e9..afdac5b66f01c 100644 --- a/compiler/rustc_hir_typeck/src/errors.rs +++ b/compiler/rustc_hir_typeck/src/errors.rs @@ -100,6 +100,7 @@ impl IntoDiagArg for ReturnLikeStatementKind { } } +// FIXME(splat): add "non-splatted" to all 4 instances of this error message #[derive(Diagnostic)] #[diag("functions with the \"rust-call\" ABI must take a single non-self tuple argument")] pub(crate) struct RustCallIncorrectArgs { diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index ac77354f2a794..cc1364115a227 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -28,7 +28,9 @@ use rustc_infer::infer::{self, DefineOpaqueTypes, InferOk, RegionVariableOrigin} use rustc_infer::traits::query::NoSolution; use rustc_middle::ty::adjustment::{Adjust, Adjustment, AllowTwoPhase}; use rustc_middle::ty::error::{ExpectedFound, TypeError}; -use rustc_middle::ty::{self, AdtKind, GenericArgsRef, Ty, TypeVisitableExt, Unnormalized}; +use rustc_middle::ty::{ + self, AdtKind, FnSigKind, GenericArgsRef, Ty, TypeVisitableExt, Unnormalized, +}; use rustc_middle::{bug, span_bug}; use rustc_session::errors::ExprParenthesesNeeded; use rustc_session::parse::feature_err; @@ -1482,16 +1484,32 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { Ok(method) => { self.write_method_call_and_enforce_effects(expr.hir_id, expr.span, method); + // Handle splatted method arguments + // self is already handled as `rcvr`, so it's never splatted here + let method_inputs = &method.sig.inputs()[1..]; + let method_fn_sig_kind = if let Some(splatted_arg_index) = + method.sig.fn_sig_kind.splatted() + { + method + .sig + .fn_sig_kind + .set_splatted(Some(splatted_arg_index.strict_sub(1)), method_inputs.len()) + .unwrap() + } else { + method.sig.fn_sig_kind + }; + self.check_argument_types( segment.ident.span, expr, - &method.sig.inputs()[1..], + method_inputs, method.sig.output(), expected, args, - method.sig.c_variadic(), - TupleArgumentsFlag::DontTupleArguments, + method_fn_sig_kind, + TupleArgumentsFlag::NotCallOper, Some(method.def_id), + Some(method.args), ); self.check_call_abi(method.sig.abi(), expr.span); @@ -1511,9 +1529,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { err_output, NoExpectation, args, - false, - TupleArgumentsFlag::DontTupleArguments, + // Avoid spurious unsafe errors by using a safe dummy sig + FnSigKind::dummy(), + TupleArgumentsFlag::NotCallOper, None, + Some(GenericArgsRef::default()), ); err_output diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs index 9a54d651fe73a..b0d40707d2785 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs @@ -27,8 +27,8 @@ use rustc_middle::ty::adjustment::{ }; use rustc_middle::ty::{ self, AdtKind, CanonicalUserType, GenericArgsRef, GenericParamDefKind, IsIdentity, - SizedTraitKind, Ty, TyCtxt, TypeFoldable, TypeVisitable, TypeVisitableExt, Unnormalized, - UserArgs, UserSelfTy, + SizedTraitKind, SplattedDef, Ty, TyCtxt, TypeFoldable, TypeVisitable, TypeVisitableExt, + Unnormalized, UserArgs, UserSelfTy, }; use rustc_middle::{bug, span_bug}; use rustc_session::lint; @@ -236,6 +236,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.typeck_results.borrow_mut().type_dependent_defs_mut().insert(hir_id, r); } + #[instrument(level = "debug", skip(self))] + pub(crate) fn write_splatted_resolution( + &self, + hir_id: HirId, + r: Result, + ) { + self.typeck_results.borrow_mut().splatted_defs_mut().insert(hir_id, r); + } + #[instrument(level = "debug", skip(self))] pub(crate) fn write_method_call_and_enforce_effects( &self, @@ -248,6 +257,32 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.write_args(hir_id, method.args); } + #[instrument(level = "debug", skip(self))] + pub(crate) fn write_splatted_call( + &self, + hir_id: HirId, + span: Span, + callee_def_id: Option, + callee_generic_args: Option>, + first_tupled_arg_index: u16, + tupled_args_count: u16, + ) { + // FIXME(const_trait_impl): enforce constness using enforce_context_effects() and add + // _and_enforce_effects to this method's name + + self.write_splatted_resolution( + hir_id, + Ok(SplattedDef { + def_id: callee_def_id, + arg_index: first_tupled_arg_index, + arg_count: tupled_args_count, + }), + ); + if let Some(callee_generic_args) = callee_generic_args { + self.write_args(hir_id, callee_generic_args); + } + } + fn write_args(&self, node_id: HirId, args: GenericArgsRef<'tcx>) { if !args.is_empty() { debug!("write_args({:?}, {:?}) in fcx {}", node_id, args, self.tag()); diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index 6a14ab9683bf1..40126b6f06c47 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; @@ -18,7 +18,9 @@ use rustc_index::IndexVec; use rustc_infer::infer::{BoundRegionConversionTime, DefineOpaqueTypes, InferOk, TypeTrace}; use rustc_middle::ty::adjustment::AllowTwoPhase; use rustc_middle::ty::error::TypeError; -use rustc_middle::ty::{self, IsSuggestable, Ty, TyCtxt, TypeVisitableExt, Unnormalized}; +use rustc_middle::ty::{ + self, FnSigKind, IsSuggestable, Ty, TyCtxt, TypeVisitableExt, Unnormalized, +}; use rustc_middle::{bug, span_bug}; use rustc_session::Session; use rustc_span::{DUMMY_SP, Ident, Span, kw, sym}; @@ -184,13 +186,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { expectation: Expectation<'tcx>, // The expressions for each provided argument provided_args: &'tcx [hir::Expr<'tcx>], - // Whether the function is variadic, for example when imported from C - // FIXME(splat): maybe change this to FnSigKind? - c_variadic: bool, - // Whether the arguments have been bundled in a tuple (ex: closures) + // Whether the function is variadic (e.g. from C), or has a splatted argument + fn_sig_kind: FnSigKind, + // Whether all the arguments have been bundled in a tuple (ex: closures). + // Splatting is handled separately. tuple_arguments: TupleArgumentsFlag, // The DefId for the function being called, for better error messages fn_def_id: Option, + // The generics of the function being called. Only used for splatting + callee_generic_args: Option>, ) { let tcx = self.tcx; @@ -242,11 +246,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }; // First, let's unify the formal method signature with the expectation eagerly. - // We use this to guide coercion inference; it's output is "fudged" which means + // We use this to guide coercion inference; its output is "fudged" which means // any remaining type variables are assigned to new, unrelated variables. This // is because the inference guidance here is only speculative. + // FIXME(splat): do we need to splat arguments before this type inference? let formal_output = self.resolve_vars_with_obligations(formal_output); - let expected_input_tys: Option> = expectation + let mut expected_input_tys: Option> = expectation .only_has_type(self) .and_then(|expected_output| { // FIXME(#149379): This operation results in expected input @@ -294,45 +299,217 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let mut err_code = E0061; - // If the arguments should be wrapped in a tuple (ex: closures), unwrap them here - let (formal_input_tys, expected_input_tys) = if tuple_arguments == TupleArguments { - let tuple_type = self.structurally_resolve_type(call_span, formal_input_tys[0]); - match tuple_type.kind() { - // We expected a tuple and got a tuple - ty::Tuple(arg_types) => { - // Argument length differs - if arg_types.len() != provided_args.len() { - err_code = E0057; + let mut formal_input_tys = formal_input_tys.to_vec(); + + // Caller arguments are tupled before typechecking, starting at the given index. + // Tupling makes the callee and caller argument counts match. + let first_tupled_arg_index = if let Some(splatted_arg_index) = fn_sig_kind.splatted() { + Some(usize::from(splatted_arg_index)) + } else if tuple_arguments == TupleAllCallArgs { + Some(0) + } else { + None + }; + + // If the arguments should be wrapped in a tuple (ex: closures, splats), unwrap them here + if let Some(first_tupled_arg_index) = first_tupled_arg_index { + // The argument difference can range from -1 to u16::MAX - 1, so we count the number + // of tupled arguments instead. + // (An empty argument list becomes a unit tuple in the callee.) + // 0: f() -> f(#[splat] _: ()) + // 1: f(a) -> f(#[splat] _: (A,)) + // 2: f(a, b) -> f(#[splat] _: (A, B)) + // The Fn* traits ensure this by construction, and `#[splat]` can only be applied to + // an actual argument. + let tupled_args_count = (1 + provided_args.len()).checked_sub(formal_input_tys.len()); + debug!( + ?first_tupled_arg_index, ?tupled_args_count, + ?tuple_arguments, ?fn_sig_kind, + provided_args_len = ?provided_args.len(), formal_input_tys_len = ?formal_input_tys.len() + ); + + // If earlier code has modified the FnSig argument list without adjusting the splatted + // argument, indexing into the formal input types will panic. + if usize::from(first_tupled_arg_index) >= formal_input_tys.len() { + span_bug!( + call_span, + "splatted argument index is out of bounds: {first_tupled_arg_index} >= {}, \ + tupled_args_count = {tupled_args_count:?}, {tuple_arguments:?}, \ + {fn_sig_kind:?}, provided_args: {}", + formal_input_tys.len(), + provided_args.len(), + ); + } + + // Keep the type variable if the argument is splatted, so we can force it to be a tuple later. + let tuple_type = if fn_sig_kind.splatted().is_some() { + let calee_tuple_type = self.try_structurally_resolve_type( + call_span, + formal_input_tys[first_tupled_arg_index], + ); + if calee_tuple_type.is_ty_var() + && let Some(tupled_args_count) = tupled_args_count + { + // Make the original type variable resolve to a tuple containing new type variables + let ocx = ObligationCtxt::new(self); + let origin = self.misc(call_span); + + let new_tupled_type = Ty::new_tup_from_iter( + self.tcx, + iter::repeat_with(|| self.next_ty_var(call_span)).take(tupled_args_count), + ); + + // FIXME(splat): should this be a sub/super type relationship? + let ocx_error = + ocx.eq(&origin, self.param_env, 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 expected_input_tys = match expected_input_tys { - Some(expected_input_tys) => match expected_input_tys.get(0) { - Some(ty) => match ty.kind() { - ty::Tuple(tys) => Some(tys.iter().collect()), - _ => None, - }, - None => None, - }, - None => None, + + 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 { + // Otherwise, just let the argument type checker make a suggestion + calee_tuple_type + } + } else { + self.structurally_resolve_type(call_span, formal_input_tys[first_tupled_arg_index]) + }; + + // We expected a tuple and got a tuple (or made one ourselves) + if let ty::Tuple(detup_formal_arg_tys) = tuple_type.kind() { + // Argument length differs + // FIXME(splat): update the error code E0057 docs when splat is stabilized + if Some(detup_formal_arg_tys.len()) != tupled_args_count { + err_code = E0057; + } + if let Some(ref mut expected_input_tys) = expected_input_tys + && let Some(ty) = expected_input_tys.get(first_tupled_arg_index) + && let ty::Tuple(detup_expected_arg_tys) = ty.kind() + { + let substitute_tys = if Some(detup_expected_arg_tys.len()) == tupled_args_count + { + detup_expected_arg_tys.iter() + } else { + // Just fall back to the formal argument types + detup_formal_arg_tys.iter() }; - (arg_types.iter().collect(), expected_input_tys) + + expected_input_tys + .splice(first_tupled_arg_index..=first_tupled_arg_index, substitute_tys) + .for_each(|_| {}); + } else { + expected_input_tys = None; } - _ => { - // 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 + // If splatting, record this call in a side-table, so MIR lowering can tuple the caller's arguments + if fn_sig_kind.splatted().is_some() { + // FIXME(const_trait_impl): does not enforce constness yet + self.write_splatted_call( + call_expr.hir_id, + call_span, + fn_def_id, + callee_generic_args, + first_tupled_arg_index.try_into().unwrap(), + tupled_args_count.unwrap().try_into().unwrap(), + ); + } + + formal_input_tys + .splice( + first_tupled_arg_index..=first_tupled_arg_index, + detup_formal_arg_tys.iter(), + ) + .for_each(|_| {}); + if let Some(ref expected_input_tys) = expected_input_tys { + assert_eq!( + formal_input_tys.len(), + expected_input_tys.len(), + "incorrectly constructed input type tuples, argument counts must match: \ + tuple_arguments: {tuple_arguments:?}", + ) + } + } + + // Otherwise, there's a mismatch during splatting or a rust-call. + // So clear out what we're expecting, and set our input types to err_args so we don't + // blow up the error messages. + if tuple_arguments == TupleAllCallArgs && !matches!(tuple_type.kind(), ty::Tuple(_)) { + let guar = struct_span_code_err!( + self.dcx(), + call_span, + E0059, + "cannot use call notation; the first type parameter \ + for the function trait is neither a tuple nor unit" + ) + .emit(); + + formal_input_tys = self.err_args(provided_args.len(), guar); + expected_input_tys = None; + } else if fn_sig_kind.splatted().is_some() { + // If we don't check argument counts here, and there's a subtle bug in the code above, + // later compilation stages can fail in unrelated places with confusing errors. + if !matches!(tuple_type.kind(), ty::Tuple(_)) { let 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" + // FIXME(splat): add a new error code before stabilization + E0277, + "cannot use splat attribute; the splatted type parameter {} \ + for the function must be a tuple or unit, not a {:?} ({:?})", + first_tupled_arg_index, + tuple_type.kind(), + self.structurally_resolve_type( + call_span, + formal_input_tys[first_tupled_arg_index] + ) + .kind(), ) .emit(); - (self.err_args(provided_args.len(), guar), None) + + formal_input_tys = self.err_args(provided_args.len(), guar); + expected_input_tys = None; + } else if formal_input_tys.len() != provided_args.len() { + // FIXME(splat): suggest alternative argument counts, if there are any + let guar = struct_span_code_err!( + self.dcx(), + call_span, + E0057, + "this splatted function takes {} arguments, but {} {} provided", + formal_input_tys.len(), + provided_args.len(), + if provided_args.len() == 1 { "was" } else { "were" }, + ) + .emit(); + + formal_input_tys = self.err_args(provided_args.len(), guar); + expected_input_tys = None; } } - } else { - (formal_input_tys.to_vec(), expected_input_tys) - }; + } // If there are no external expectations at the call site, just use the types from the function defn let expected_input_tys = if let Some(expected_input_tys) = expected_input_tys { @@ -405,7 +582,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // if the wrong number of arguments were supplied, we CAN'T be satisfied, // and if we're c_variadic, the supplied arguments must be >= the minimum count from the function // otherwise, they need to be identical, because rust doesn't currently support variadic functions - let mut call_appears_satisfied = if c_variadic { + let mut call_appears_satisfied = if fn_sig_kind.c_variadic() { provided_arg_count >= minimum_input_count } else { provided_arg_count == minimum_input_count @@ -467,7 +644,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } - if c_variadic && provided_arg_count < minimum_input_count { + if fn_sig_kind.c_variadic() && provided_arg_count < minimum_input_count { err_code = E0060; } @@ -479,7 +656,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // so we need to check those, and write out the types // Ideally this would be folded into the above, for uniform style // but c-variadic is already a corner case - if c_variadic { + if fn_sig_kind.c_variadic() { fn variadic_error<'tcx>( sess: &'tcx Session, span: Span, @@ -540,11 +717,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if !call_appears_satisfied { let compatibility_diagonal = IndexVec::from_raw(compatibility_diagonal); - let provided_args = IndexVec::from_iter(provided_args.iter().take(if c_variadic { - minimum_input_count - } else { - provided_arg_count - })); + let provided_args = + IndexVec::from_iter(provided_args.iter().take(if fn_sig_kind.c_variadic() { + minimum_input_count + } else { + provided_arg_count + })); debug_assert_eq!( formal_input_tys.len(), expected_input_tys.len(), @@ -562,7 +740,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { compatibility_diagonal, formal_and_expected_inputs, provided_args, - c_variadic, + fn_sig_kind.c_variadic(), err_code, fn_def_id, call_span, @@ -592,6 +770,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { compatibility_diagonal: IndexVec>, formal_and_expected_inputs: IndexVec, Ty<'tcx>)>, provided_args: IndexVec>, + // FIXME(splat): when the feature design is settled, replace this with FnSigKind, and + // improve the errors here c_variadic: bool, err_code: ErrCode, fn_def_id: Option, @@ -1377,8 +1557,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // If we're calling a method of a Fn/FnMut/FnOnce trait object implicitly // (eg invoking a closure) we want to point at the underlying callable, // not the method implicitly invoked (eg call_once). - // TupleArguments is set only when this is an implicit call (my_closure(...)) rather than explicit (my_closure.call(...)) - if tuple_arguments == TupleArguments + // TupleAllCallArgs is set only when this is an implicit call `my_closure(...)` rather + // than explicit `my_closure.call(...)`. + if tuple_arguments == TupleAllCallArgs && let Some(assoc_item) = self.tcx.opt_associated_item(def_id) // Since this is an associated item, it might point at either an impl or a trait item. // We want it to always point to the trait item. diff --git a/compiler/rustc_hir_typeck/src/lib.rs b/compiler/rustc_hir_typeck/src/lib.rs index 15729bc311e57..4847105a7d31a 100644 --- a/compiler/rustc_hir_typeck/src/lib.rs +++ b/compiler/rustc_hir_typeck/src/lib.rs @@ -613,12 +613,10 @@ fn report_unexpected_variant_res( .emit() } -/// Controls whether the arguments are tupled. This is used for the call -/// operator. +/// Controls whether all arguments are tupled. This is used for the call operator only. /// -/// Tupling means that all call-side arguments are packed into a tuple and -/// passed as a single parameter. For example, if tupling is enabled, this -/// function: +/// Tupling means that all call-side arguments are packed into a tuple and passed as a single +/// parameter. For example, if tupling is enabled, this function: /// ``` /// fn f(x: (isize, isize)) {} /// ``` @@ -632,10 +630,14 @@ fn report_unexpected_variant_res( /// # fn f(x: (isize, isize)) {} /// f((1, 2)); /// ``` -#[derive(Copy, Clone, Eq, PartialEq)] +/// +/// Note: splatted arguments are handled separately. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] enum TupleArgumentsFlag { - DontTupleArguments, - TupleArguments, + /// Arguments are typechecked unchanged. + NotCallOper, + /// This is a call operator: all caller arguments are tupled before typechecking. + TupleAllCallArgs, } fn fatally_break_rust(tcx: TyCtxt<'_>, span: Span) -> ! { diff --git a/compiler/rustc_hir_typeck/src/writeback.rs b/compiler/rustc_hir_typeck/src/writeback.rs index e0d9970a5a16d..e59c5cb6ed828 100644 --- a/compiler/rustc_hir_typeck/src/writeback.rs +++ b/compiler/rustc_hir_typeck/src/writeback.rs @@ -663,6 +663,11 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> { self.typeck_results.type_dependent_defs_mut().insert(hir_id, def); } + // Export splatted function call resolutions. + if let Some(def) = self.fcx.typeck_results.borrow_mut().splatted_defs_mut().remove(hir_id) { + self.typeck_results.splatted_defs_mut().insert(hir_id, def); + } + // Resolve any borrowings for the node with id `node_id` self.visit_adjustments(span, hir_id); diff --git a/compiler/rustc_lint/src/foreign_modules.rs b/compiler/rustc_lint/src/foreign_modules.rs index c194626a1cb21..22e2af782dc96 100644 --- a/compiler/rustc_lint/src/foreign_modules.rs +++ b/compiler/rustc_lint/src/foreign_modules.rs @@ -329,6 +329,14 @@ fn structurally_same_type_impl<'tcx>( let a_sig = tcx.instantiate_bound_regions_with_erased(a_poly_sig); let b_sig = tcx.instantiate_bound_regions_with_erased(b_poly_sig); + // FIXME(splat): Is splatting ever repr(C)? + // Can two splatted functions to have the same structure? + // Can a splatted and non-splatted function have the same structure? + // For now, we require splatting to match exactly. + if a_sig.splatted() != b_sig.splatted() { + return false; + } + (a_sig.abi(), a_sig.safety(), a_sig.c_variadic()) == (b_sig.abi(), b_sig.safety(), b_sig.c_variadic()) && a_sig.inputs().iter().eq_by(b_sig.inputs().iter(), |a, b| { diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index 44cd6499fb088..48adf59d16f7d 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -48,7 +48,8 @@ use rustc_span::{DUMMY_SP, Ident, Span, Symbol, kw, sym}; use rustc_type_ir::TyKind::*; pub use rustc_type_ir::lift::Lift; use rustc_type_ir::{ - CollectAndApply, FnSigKind, TypeFlags, WithCachedTypeInfo, elaborate, search_graph, + CollectAndApply, FnSigKind, SplattedArgIndexError, TypeFlags, WithCachedTypeInfo, elaborate, + search_graph, }; use tracing::{debug, instrument}; @@ -91,8 +92,34 @@ impl<'tcx> rustc_type_ir::inherent::FSigKind> for FnSigKind { self } - fn new(abi: ExternAbi, safety: hir::Safety, c_variadic: bool) -> Self { - FnSigKind::default().set_abi(abi).set_safe(safety.is_safe()).set_c_variadic(c_variadic) + fn new( + abi: ExternAbi, + safety: hir::Safety, + c_variadic: bool, + splatted: Option, + args_len: usize, + ) -> Result { + FnSigKind::default() + .set_abi(abi) + .set_safe(safety.is_safe()) + .set_c_variadic(c_variadic) + .set_splatted(splatted, args_len) + } + + fn dummy() -> Self { + FnSigKind::dummy() + } + + fn set_safe(self, is_safe: bool) -> Self { + self.set_safe(is_safe) + } + + fn set_splatted( + self, + splatted: Option, + args_len: usize, + ) -> Result { + self.set_splatted(splatted, args_len) } fn abi(self) -> ExternAbi { @@ -106,6 +133,10 @@ impl<'tcx> rustc_type_ir::inherent::FSigKind> for FnSigKind { fn c_variadic(self) -> bool { self.c_variadic() } + + fn splatted(self) -> Option { + self.splatted() + } } impl<'tcx> rustc_type_ir::inherent::Abi> for ExternAbi { @@ -2193,6 +2224,8 @@ impl<'tcx> TyCtxt<'tcx> { ty::Tuple(params) => *params, _ => bug!(), }; + // Ignore splatting, it is unsupported on closures. + assert!(s.splatted().is_none()); self.mk_fn_sig( params, s.output(), diff --git a/compiler/rustc_middle/src/ty/error.rs b/compiler/rustc_middle/src/ty/error.rs index a4c30f1f88434..1ff88b13e00d5 100644 --- a/compiler/rustc_middle/src/ty/error.rs +++ b/compiler/rustc_middle/src/ty/error.rs @@ -96,6 +96,21 @@ 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 with {}, found fn with {}", + if let Some(index) = values.expected { + format!("arg {index} splatted") + } else { + "no splatted arg".to_string() + }, + if let Some(index) = values.found { + format!("arg {index} splatted") + } else { + "no splatted arg".to_string() + } + ) + .into(), TypeError::ProjectionMismatched(ref values) => format!( "expected `{}`, found `{}`", tcx.def_path_str(values.expected), diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index 56a7abfac6635..3565cc06fc348 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -113,7 +113,8 @@ pub use self::sty::{ pub use self::trait_def::TraitDef; pub use self::typeck_results::{ CanonicalUserType, CanonicalUserTypeAnnotation, CanonicalUserTypeAnnotations, IsIdentity, - Rust2024IncompatiblePatInfo, TypeckResults, UserType, UserTypeAnnotationIndex, UserTypeKind, + Rust2024IncompatiblePatInfo, SplattedDef, TypeckResults, UserType, UserTypeAnnotationIndex, + UserTypeKind, }; use crate::error::{OpaqueHiddenTypeMismatch, TypeMismatchReason}; use crate::ich::StableHashingContext; diff --git a/compiler/rustc_middle/src/ty/print/pretty.rs b/compiler/rustc_middle/src/ty/print/pretty.rs index d3132d3f65780..defa3943cb604 100644 --- a/compiler/rustc_middle/src/ty/print/pretty.rs +++ b/compiler/rustc_middle/src/ty/print/pretty.rs @@ -1428,6 +1428,8 @@ pub trait PrettyPrinter<'tcx>: Printer<'tcx> + fmt::Write { p.pretty_print_fn_sig( tys, false, + // FIXME(splat): support splatted arguments here? + None, proj.skip_binder().term.as_type().expect("Return type was a const"), )?; resugared = true; @@ -1531,10 +1533,19 @@ pub trait PrettyPrinter<'tcx>: Printer<'tcx> + fmt::Write { &mut self, inputs: &[Ty<'tcx>], c_variadic: bool, + splatted: Option, output: Ty<'tcx>, ) -> Result<(), PrintError> { write!(self, "(")?; - self.comma_sep(inputs.iter().copied())?; + let splatted_arg_index = splatted.map(usize::from); + let mut input_iter = inputs.iter().copied(); + if let Some(index) = splatted_arg_index { + self.comma_sep((&mut input_iter).take(usize::from(index)))?; + write!(self, ", #[splat]")?; + self.comma_sep(input_iter)?; + } else { + self.comma_sep(input_iter)?; + } if c_variadic { if !inputs.is_empty() { write!(self, ", ")?; @@ -3144,7 +3155,7 @@ define_print! { } write!(p, "fn")?; - p.pretty_print_fn_sig(self.inputs(), self.c_variadic(), self.output())?; + p.pretty_print_fn_sig(self.inputs(), self.c_variadic(), self.splatted(), self.output())?; } ty::TraitRef<'tcx> { diff --git a/compiler/rustc_middle/src/ty/typeck_results.rs b/compiler/rustc_middle/src/ty/typeck_results.rs index b8399215cf810..cf73bc1f582eb 100644 --- a/compiler/rustc_middle/src/ty/typeck_results.rs +++ b/compiler/rustc_middle/src/ty/typeck_results.rs @@ -36,6 +36,9 @@ pub struct TypeckResults<'tcx> { /// method calls, including those of overloaded operators. type_dependent_defs: ItemLocalMap>, + /// Resolved definitions for splatted function calls. + splatted_defs: ItemLocalMap>, + /// Resolved field indices for field accesses in expressions (`S { field }`, `obj.field`) /// or patterns (`S { field }`). The index is often useful by itself, but to learn more /// about the field you also need definition of the variant to which the field @@ -229,6 +232,7 @@ impl<'tcx> TypeckResults<'tcx> { TypeckResults { hir_owner, type_dependent_defs: Default::default(), + splatted_defs: Default::default(), field_indices: Default::default(), user_provided_types: Default::default(), user_provided_sigs: Default::default(), @@ -287,6 +291,21 @@ impl<'tcx> TypeckResults<'tcx> { LocalTableInContextMut { hir_owner: self.hir_owner, data: &mut self.type_dependent_defs } } + pub fn splatted_defs(&self) -> LocalTableInContext<'_, Result> { + LocalTableInContext { hir_owner: self.hir_owner, data: &self.splatted_defs } + } + + pub fn splatted_def(&self, id: HirId) -> Option { + validate_hir_id_for_typeck_results(self.hir_owner, id); + self.splatted_defs.get(&id.local_id).cloned().and_then(|r| r.ok()) + } + + pub fn splatted_defs_mut( + &mut self, + ) -> LocalTableInContextMut<'_, Result> { + LocalTableInContextMut { hir_owner: self.hir_owner, data: &mut self.splatted_defs } + } + pub fn field_indices(&self) -> LocalTableInContext<'_, FieldIdx> { LocalTableInContext { hir_owner: self.hir_owner, data: &self.field_indices } } @@ -407,6 +426,10 @@ impl<'tcx> TypeckResults<'tcx> { matches!(self.type_dependent_defs().get(expr.hir_id), Some(Ok((DefKind::AssocFn, _)))) } + pub fn is_splatted_call(&self, expr: &hir::Expr<'_>) -> bool { + matches!(self.splatted_defs().get(expr.hir_id), Some(Ok(SplattedDef { .. }))) + } + /// Returns the computed binding mode for a `PatKind::Binding` pattern /// (after match ergonomics adjustments). pub fn extract_binding_mode(&self, s: &Session, id: HirId, sp: Span) -> BindingMode { @@ -569,6 +592,18 @@ impl<'tcx> TypeckResults<'tcx> { } } +/// A resolved splatted function call. +#[derive(Debug, Copy, Clone, PartialEq, Eq, HashStable, TyEncodable, TyDecodable)] +pub struct SplattedDef { + /// The function DefId, if available (FnPtrs don't have DefIds) + pub def_id: Option, + /// The index of the first argument in the callee's splatted tuple, and the index of the + /// splatted tuple argument in the caller. + pub arg_index: u16, + /// The number of arguments in the splatted tuple. + pub arg_count: u16, +} + /// Validate that the given HirId (respectively its `local_id` part) can be /// safely used as a key in the maps of a TypeckResults. For that to be /// the case, the HirId must have the same `owner` as all the other IDs in diff --git a/compiler/rustc_mir_build/src/builder/mod.rs b/compiler/rustc_mir_build/src/builder/mod.rs index 1717e5b4b716b..89e4f3a48054c 100644 --- a/compiler/rustc_mir_build/src/builder/mod.rs +++ b/compiler/rustc_mir_build/src/builder/mod.rs @@ -551,7 +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 + // FIXME(splat): splat can untuple any argument, set spread_arg here Some(Local::new(arguments.len())) } else { None diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index 69260792a95d2..c40a69f52dc88 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -16,7 +16,8 @@ use rustc_middle::ty::adjustment::{ Adjust, Adjustment, AutoBorrow, AutoBorrowMutability, DerefAdjustKind, PointerCoercion, }; use rustc_middle::ty::{ - self, AdtKind, GenericArgs, InlineConstArgs, InlineConstArgsParts, ScalarInt, Ty, UpvarArgs, + self, AdtKind, GenericArgs, InlineConstArgs, InlineConstArgsParts, ScalarInt, SplattedDef, Ty, + UpvarArgs, }; use rustc_middle::{bug, span_bug}; use rustc_span::Span; @@ -323,19 +324,94 @@ impl<'tcx> ThirBuildCx<'tcx> { let kind = match expr.kind { // Here comes the interesting stuff: hir::ExprKind::MethodCall(segment, receiver, args, fn_span) => { - // Rewrite a.b(c) into UFCS form like Trait::b(a, c) - let expr = self.method_callee(expr, segment.ident.span, None); - info!("Using method span: {:?}", expr.span); - let args = std::iter::once(receiver) - .chain(args.iter()) - .map(|expr| self.mirror_expr(expr)) - .collect(); - ExprKind::Call { - ty: expr.ty, - fun: self.thir.exprs.push(expr), - args, - from_hir_call: true, - fn_span, + // FIXME(splat): abstract this into a helper function that handles both method and function calls + if self.typeck_results.is_splatted_call(expr) { + // The callee has a splatted tuple argument. + let (method, tupled_arg_index, tupled_args_count) = + self.splatted_callee(expr, fn_span); + let tupled_arg_index = usize::from(tupled_arg_index); + let tupled_args_count = usize::from(tupled_args_count); + + // Splatting an empty tuple is permitted: `a.f() -> Trait::f(a, #[splat] ())`. + // In that case, the tupled arg index is one past the end of the args. + if tupled_arg_index + tupled_args_count > args.len() { + span_bug!( + expr.span, + "splatted arg index out of bounds of method args: {:?} + {:?} > {:?} for method call {:?}, receiver {:?}, args {:?}", + tupled_arg_index, + tupled_args_count, + args.len(), + segment, + receiver, + args, + ); + } + + // FIXME(splat): do we need to rewrite `a.f(b, c)` into `Trait::f(a, #[splat] (b, c))`? + info!("Using splatted method span: {:?}", method.span); + + // Split into non-tupled and tupled arguments + let initial_non_tupled_args = args + .iter() + .take(tupled_arg_index) + .map(|e| self.mirror_expr(e)) + .collect_vec(); + let tupled_args = if tupled_arg_index == args.len() || tupled_args_count == 0 { + // Splatting an empty tuple, in the ABI this gets ignored + Default::default() + } else { + &args[tupled_arg_index..(tupled_arg_index + tupled_args_count)] + }; + let final_non_tupled_args = args + .iter() + .skip(tupled_arg_index + tupled_args_count) + .map(|e| self.mirror_expr(e)) + .collect_vec(); + + let tupled_arg_tys = + tupled_args.iter().map(|e| self.typeck_results.expr_ty_adjusted(e)); + + let tupled_args = Expr { + ty: Ty::new_tup_from_iter(tcx, tupled_arg_tys), + temp_scope_id: method.temp_scope_id, + span: expr.span, + kind: ExprKind::Tuple { fields: self.mirror_exprs(tupled_args) }, + }; + + let tupled_args = self.thir.exprs.push(tupled_args); + + let mut args = vec![self.mirror_expr(receiver)]; + args.extend(initial_non_tupled_args); + args.push(tupled_args); + args.extend(final_non_tupled_args); + + // 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 + let method_span = method.span; + ExprKind::Call { + // FIXME(splat): should this be method.ty, or be the same as it?? + ty: method.ty, + fun: self.thir.exprs.push(method), + args: args.into_boxed_slice(), + from_hir_call: true, + fn_span: method_span, + } + } else { + // Rewrite a.b(c) into UFCS form like Trait::b(a, c) + let expr = self.method_callee(expr, segment.ident.span, None); + info!("Using method span: {:?}", expr.span); + + let args = std::iter::once(receiver) + .chain(args.iter()) + .map(|expr| self.mirror_expr(expr)) + .collect(); + ExprKind::Call { + ty: expr.ty, + fun: self.thir.exprs.push(expr), + args, + from_hir_call: true, + fn_span, + } } } @@ -366,6 +442,72 @@ impl<'tcx> ThirBuildCx<'tcx> { from_hir_call: true, fn_span: expr.span, } + } else if self.typeck_results.is_splatted_call(expr) { + // The callee has a splatted tuple argument. + // rewrite `f(a, u, v)` into `f(a, #[splat] (u, v))` + + let (function, tupled_arg_index, tupled_args_count) = + self.splatted_callee(expr, fun.span); + let tupled_arg_index = usize::from(tupled_arg_index); + let tupled_args_count = usize::from(tupled_args_count); + + // Splatting an empty tuple is permitted: `f() -> f(#[splat] ())`. + // In that case, the tupled arg index is one past the end of the args. + if tupled_arg_index + tupled_args_count > args.len() { + span_bug!( + expr.span, + "splatted arg index out of bounds of function args: {:?} + {:?} > {:?} for function {:?}, args {:?}", + tupled_arg_index, + tupled_args_count, + args.len(), + fun, + args, + ); + } + + // Split into non-tupled and tupled arguments + let initial_non_tupled_args = args + .iter() + .take(tupled_arg_index) + .map(|e| self.mirror_expr(e)) + .collect_vec(); + let tupled_args = if tupled_arg_index == args.len() || tupled_args_count == 0 { + // Splatting an empty tuple, in the ABI this gets ignored + Default::default() + } else { + &args[tupled_arg_index..(tupled_arg_index + tupled_args_count)] + }; + let final_non_tupled_args = args + .iter() + .skip(tupled_arg_index + tupled_args_count) + .map(|e| self.mirror_expr(e)) + .collect_vec(); + + let tupled_arg_tys = + tupled_args.iter().map(|e| self.typeck_results.expr_ty_adjusted(e)); + + let tupled_args = Expr { + ty: Ty::new_tup_from_iter(tcx, tupled_arg_tys), + temp_scope_id: expr.hir_id.local_id, + span: expr.span, + kind: ExprKind::Tuple { fields: self.mirror_exprs(tupled_args) }, + }; + + let tupled_args = self.thir.exprs.push(tupled_args); + + let mut args = initial_non_tupled_args; + args.push(tupled_args); + args.extend(final_non_tupled_args); + + // 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 @@ -1159,6 +1301,37 @@ impl<'tcx> ThirBuildCx<'tcx> { } } + fn splatted_callee( + &mut self, + expr: &hir::Expr<'_>, + span: Span, + ) -> (Expr<'tcx>, u16 /* arg_index */, u16 /* arg_count */) { + let SplattedDef { def_id, arg_index, arg_count } = + self.typeck_results.splatted_def(expr.hir_id).unwrap_or_else(|| { + span_bug!(expr.span, "no splatted def for function or method callee") + }); + let def_id = def_id.unwrap_or_else(|| { + span_bug!(expr.span, "no splatted def for function or method callee") + }); + let def_kind = self.tcx.def_kind(def_id); + let user_ty = self.user_args_applied_to_res(expr.hir_id, Res::Def(def_kind, def_id)); + debug!( + "splatted_callee: user_ty={:?} def_kind={:?} def_id={:?} arg_index={:?} arg_count={:?}", + user_ty, def_kind, def_id, arg_index, arg_count + ); + + ( + Expr { + temp_scope_id: expr.hir_id.local_id, + ty: Ty::new_fn_def(self.tcx, def_id, self.typeck_results.node_args(expr.hir_id)), + span, + kind: ExprKind::ZstLiteral { user_ty }, + }, + arg_index, + arg_count, + ) + } + fn convert_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) -> ArmId { let arm = Arm { pattern: self.pattern_from_hir(&arm.pat), diff --git a/compiler/rustc_public/src/unstable/convert/internal.rs b/compiler/rustc_public/src/unstable/convert/internal.rs index 4f3e9f94c599b..f98277611ed60 100644 --- a/compiler/rustc_public/src/unstable/convert/internal.rs +++ b/compiler/rustc_public/src/unstable/convert/internal.rs @@ -308,6 +308,7 @@ impl RustcInternal for FnSig { tables: &mut Tables<'_, BridgeTys>, tcx: impl InternalCx<'tcx>, ) -> Self::T<'tcx> { + // FIXME(splat): When splatted is complete (or stable), add splatted to the public FnSig let fn_sig_kind = rustc_ty::FnSigKind::default() .set_abi(self.abi.internal(tables, tcx)) .set_safe(self.safety == Safety::Safe) diff --git a/compiler/rustc_public/src/unstable/convert/stable/ty.rs b/compiler/rustc_public/src/unstable/convert/stable/ty.rs index 9a9576d47efd2..c22bec88b71de 100644 --- a/compiler/rustc_public/src/unstable/convert/stable/ty.rs +++ b/compiler/rustc_public/src/unstable/convert/stable/ty.rs @@ -270,6 +270,7 @@ impl<'tcx> Stable<'tcx> for ty::FnSigKind { } impl<'tcx> Stable<'tcx> for ty::FnSig<'tcx> { + // FIXME(splat): When splat is complete (or stable), add a field to FnSig type T = crate::ty::FnSig; fn stable<'cx>( &self, diff --git a/compiler/rustc_symbol_mangling/src/v0.rs b/compiler/rustc_symbol_mangling/src/v0.rs index 46a7e092e6bc9..9a981ce9685f3 100644 --- a/compiler/rustc_symbol_mangling/src/v0.rs +++ b/compiler/rustc_symbol_mangling/src/v0.rs @@ -576,6 +576,7 @@ impl<'tcx> Printer<'tcx> for V0SymbolMangler<'tcx> { } } } + // FIXME(splat): should splatted arguments be part of symbol mangling? for &ty in sig.inputs() { ty.print(p)?; } diff --git a/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs b/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs index 86fd705e68aea..0468e187a9058 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs @@ -824,9 +824,19 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { // ^^^^^ let len1 = sig1.inputs().len(); let len2 = sig2.inputs().len(); + let splatted_arg_index1 = sig1.splatted().map(usize::from); + let splatted_arg_index2 = sig2.splatted().map(usize::from); if len1 == len2 { for (i, (l, r)) in iter::zip(sig1.inputs(), sig2.inputs()).enumerate() { self.push_comma(&mut values.0, &mut values.1, i); + if Some(i) == splatted_arg_index1 { + values.0.push("#[splat]", splatted_arg_index1 != splatted_arg_index2); + values.0.push_normal(" "); + } + if Some(i) == splatted_arg_index2 { + values.1.push("#[splat]", splatted_arg_index1 != splatted_arg_index2); + values.1.push_normal(" "); + } let (x1, x2) = self.cmp(*l, *r); (values.0).0.extend(x1.0); (values.1).0.extend(x2.0); diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs index 9e45023144def..ed3aadcbb5665 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -4721,6 +4721,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { && let fn_sig @ ty::FnSig { .. } = fn_ty.fn_sig(tcx).skip_binder() + // FIXME(splat): this might need to change if the Fn* traits start using/supporting splat && fn_sig.abi() == ExternAbi::Rust && !fn_sig.c_variadic() && fn_sig.safety() == hir::Safety::Safe diff --git a/compiler/rustc_type_ir/src/error.rs b/compiler/rustc_type_ir/src/error.rs index eba4c7c6644ac..7c57bfe3e8047 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/inherent.rs b/compiler/rustc_type_ir/src/inherent.rs index a336313e90b8f..f3c2a5b44d746 100644 --- a/compiler/rustc_type_ir/src/inherent.rs +++ b/compiler/rustc_type_ir/src/inherent.rs @@ -14,7 +14,8 @@ use crate::relate::Relate; use crate::solve::{AdtDestructorKind, SizedTraitKind}; use crate::visit::{Flags, TypeSuperVisitable, TypeVisitable}; use crate::{ - self as ty, ClauseKind, CollectAndApply, FieldInfo, Interner, PredicateKind, UpcastFrom, + self as ty, ClauseKind, CollectAndApply, FieldInfo, Interner, PredicateKind, + SplattedArgIndexError, UpcastFrom, }; #[rust_analyzer::prefer_underscore_import] @@ -210,8 +211,34 @@ pub trait FSigKind>: Copy + Debug + Hash + Eq { /// The identity function. fn fn_sig_kind(self) -> Self; - /// Create a new FnSigKind with the given ABI, safety, and C-style variadic flag. - fn new(abi: I::Abi, safety: I::Safety, c_variadic: bool) -> Self; + /// Create a new FnSigKind with the given ABI, safety, C-style variadic flag, and splatted + /// argument index. + /// + /// Returns an error if the splatted argument index is invalid (e.g. `u16::MAX`) or beyond + /// the bounds of the function arguments `args_len`. + fn new( + abi: I::Abi, + safety: I::Safety, + c_variadic: bool, + splatted: Option, + args_len: usize, + ) -> Result; + + /// Create a new safe FnSigKind with the `extern "Rust"` ABI, that isn't C-style variadic or splatted. + fn dummy() -> Self; + + /// Returns a modified FnSigKind with the safety mode set to `is_safe`. + #[must_use = "this method does not modify the receiver"] + fn set_safe(self, is_safe: bool) -> Self; + + /// Returns a modified FnSigKind with the splatted argument index set to `splatted`. + /// The number of function arguments is used for error checking. + #[must_use = "this method does not modify the receiver"] + fn set_splatted( + self, + splatted: Option, + args_len: usize, + ) -> Result; /// Returns the ABI. fn abi(self) -> I::Abi; @@ -221,6 +248,9 @@ pub trait FSigKind>: Copy + Debug + Hash + Eq { /// Do the function arguments end with a C-style variadic argument? fn c_variadic(self) -> bool; + + /// Get the index of the splatted argument, if any. + fn splatted(self) -> Option; } #[rust_analyzer::prefer_underscore_import] diff --git a/compiler/rustc_type_ir/src/relate.rs b/compiler/rustc_type_ir/src/relate.rs index 2ae8d52db6f5e..7357bde663204 100644 --- a/compiler/rustc_type_ir/src/relate.rs +++ b/compiler/rustc_type_ir/src/relate.rs @@ -169,6 +169,10 @@ impl Relate for ty::FnSig { return Err(TypeError::AbiMismatch(ExpectedFound::new(a.abi(), b.abi()))); }; + if a.splatted() != b.splatted() { + return Err(TypeError::SplatMismatch(ExpectedFound::new(a.splatted(), b.splatted()))); + } + let a_inputs = a.inputs(); let b_inputs = b.inputs(); if a_inputs.len() != b_inputs.len() { diff --git a/compiler/rustc_type_ir/src/ty_kind.rs b/compiler/rustc_type_ir/src/ty_kind.rs index 61ac5acca7405..4e60bcfc74a9d 100644 --- a/compiler/rustc_type_ir/src/ty_kind.rs +++ b/compiler/rustc_type_ir/src/ty_kind.rs @@ -320,6 +320,40 @@ impl TyKind { } } + pub fn def_id(self) -> Option { + match self { + ty::Adt(adt, ..) => Some(adt.def_id().into()), + ty::Foreign(def_id) => Some(def_id.into()), + ty::FnDef(def_id, ..) => Some(def_id.into()), + ty::Closure(def_id, ..) => Some(def_id.into()), + ty::CoroutineClosure(def_id, ..) => Some(def_id.into()), + ty::Coroutine(def_id, ..) => Some(def_id.into()), + ty::CoroutineWitness(def_id, ..) => Some(def_id.into()), + ty::Alias(alias) => Some(alias.kind.def_id().into()), + ty::Bool + | ty::Char + | ty::Int(_) + | ty::Uint(_) + | ty::Float(_) + | ty::Str + | ty::Array(_, _) + | ty::Pat(_, _) + | ty::Slice(_) + | ty::RawPtr(_, _) + | ty::Ref(_, _, _) + | ty::FnPtr(..) + | ty::UnsafeBinder(_) + | ty::Dynamic(_, _) + | ty::Never + | ty::Tuple(_) + | ty::Param(_) + | ty::Bound(_, _) + | ty::Placeholder(_) + | ty::Infer(_) + | ty::Error(..) => None, + } + } + /// Returns `true` when the outermost type cannot be further normalized, /// resolved, or instantiated. /// @@ -760,6 +794,18 @@ pub struct TypeAndMut { impl Eq for TypeAndMut {} +/// Error type for splatted argument index errors. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SplattedArgIndexError { + /// The splatted argument index is invalid. + /// Currently the only unsupported index is `u16::MAX`, which is used to indicate that no argument + /// is splatted. + InvalidIndex { splatted_arg_index: u16 }, + + /// The splatted argument index is outside the bounds of the function arguments. + OutOfBounds { splatted_arg_index: u16, args_len: u16 }, +} + /// Contains the packed non-type fields of a function signature. // FIXME(splat): add the splatted argument index as a u16 #[derive(Copy, Clone, PartialEq, Eq, Hash)] @@ -771,6 +817,11 @@ pub struct FnSigKind { /// Holds the c_variadic and safety bitflags, and 6 bits for the `ExternAbi` variant and unwind /// flag. flags: u8, + + /// Which function argument is splatted into multiple arguments in callers, if any? + /// Splatting the `u16::MAX`th argument is not supported, because it likely pushes the caller + /// over the fn args limit. (And spending an extra byte on an edge case is not worth the perf.) + splatted: u16, } impl fmt::Debug for FnSigKind { @@ -787,7 +838,11 @@ impl fmt::Debug for FnSigKind { if self.c_variadic() { f.field(&"CVariadic"); - }; + } + + if let Some(index) = self.splatted() { + f.field(&format!("Splatted({})", index)); + } f.finish() } @@ -803,11 +858,26 @@ impl FnSigKind { /// Bitflag for a trailing C-style variadic argument. const C_VARIADIC_FLAG: u8 = 1 << 7; - /// Create a new FnSigKind with the "Rust" ABI, "Unsafe" safety, and no C-style variadic argument. + /// The marker index for "no splatted arguments". + /// Must have the same value as `FnDeclFlags::NO_SPLATTED_ARG_INDEX` and `rustc_ast::FnDecl::NO_SPLATTED_ARG_INDEX`. + /// + /// This is an implementation detail, which should only be used in low-level encoding. + pub const NO_SPLATTED_ARG_INDEX: u16 = u16::MAX; + + /// Create a new FnSigKind with the "Rust" ABI, "Unsafe" safety, and no C-style variadic or splatted arguments. /// To modify these flags, use the `set_*` methods, for readability. // FIXME: use Default instead when that trait is const stable. pub const fn default() -> Self { - Self { flags: 0 }.set_abi(ExternAbi::Rust).set_safe(false).set_c_variadic(false) + Self { flags: 0, splatted: 0 } + .set_abi(ExternAbi::Rust) + .set_safe(false) + .set_c_variadic(false) + .set_no_splatted_args() + } + + /// Create a new safe FnSigKind with the `extern "Rust"` ABI, that isn't C-style variadic or splatted. + pub const fn dummy() -> Self { + Self::default().set_safe(true) } /// Set the ABI, including the unwind flag. @@ -846,6 +916,40 @@ impl FnSigKind { self } + /// Set the splatted argument index. + /// The number of function arguments is used for error checking. + #[must_use = "this method does not modify the receiver"] + pub const fn set_splatted( + mut self, + splatted: Option, + args_len: usize, + ) -> Result { + if let Some(splatted_arg_index) = splatted { + if splatted_arg_index == Self::NO_SPLATTED_ARG_INDEX { + // This index value is used as a marker for "no splatting", so it is unsupported. + return Err(SplattedArgIndexError::InvalidIndex { splatted_arg_index }); + } else if splatted_arg_index as usize >= args_len { + return Err(SplattedArgIndexError::OutOfBounds { + splatted_arg_index, + args_len: args_len as u16, + }); + } + + self.splatted = splatted_arg_index; + } else { + self.splatted = Self::NO_SPLATTED_ARG_INDEX; + } + + Ok(self) + } + + /// Set the splatted argument index to "no splatted arguments". + #[must_use = "this method does not modify the receiver"] + pub const fn set_no_splatted_args(mut self) -> Self { + self.splatted = Self::NO_SPLATTED_ARG_INDEX; + self + } + /// Get the ABI, including the unwind flag. pub const fn abi(self) -> ExternAbi { let abi_index = self.flags & Self::EXTERN_ABI_MASK; @@ -861,6 +965,11 @@ impl FnSigKind { pub const fn c_variadic(self) -> bool { self.flags & Self::C_VARIADIC_FLAG != 0 } + + /// Get the index of the splatted argument, if any. + pub const fn splatted(self) -> Option { + if self.splatted == Self::NO_SPLATTED_ARG_INDEX { None } else { Some(self.splatted) } + } } #[derive_where(Clone, Copy, PartialEq, Hash; I: Interner)] @@ -891,15 +1000,21 @@ impl FnSig { !self.c_variadic() && self.safety().is_safe() && self.abi().is_rust() } + /// Set the safety flag. + #[must_use = "this method does not modify the receiver"] pub fn set_safe(self, is_safe: bool) -> Self { - Self { - fn_sig_kind: I::FSigKind::new( - self.abi(), - if is_safe { I::Safety::safe() } else { I::Safety::unsafe_mode() }, - self.c_variadic(), - ), - ..self - } + Self { fn_sig_kind: self.fn_sig_kind.set_safe(is_safe), ..self } + } + + /// Set the splatted argument index. + /// The number of function arguments is used for error checking. + #[must_use = "this method does not modify the receiver"] + pub fn set_splatted( + self, + splatted: Option, + args_len: usize, + ) -> Result { + Ok(Self { fn_sig_kind: self.fn_sig_kind.set_splatted(splatted, args_len)?, ..self }) } pub fn safety(self) -> I::Safety { @@ -914,11 +1029,12 @@ impl FnSig { self.fn_sig_kind.c_variadic() } + pub fn splatted(self) -> Option { + self.fn_sig_kind.splatted() + } + pub fn dummy() -> Self { - Self { - inputs_and_output: Default::default(), - fn_sig_kind: I::FSigKind::new(I::Abi::rust(), I::Safety::safe(), false), - } + Self { inputs_and_output: Default::default(), fn_sig_kind: I::FSigKind::dummy() } } } @@ -951,6 +1067,10 @@ impl ty::Binder> { self.skip_binder().c_variadic() } + pub fn splatted(self) -> Option { + self.skip_binder().splatted() + } + pub fn safety(self) -> I::Safety { self.skip_binder().safety() } @@ -986,6 +1106,9 @@ impl fmt::Debug for FnSig { if i > 0 { write!(f, ", ")?; } + if Some(i) == fn_sig_kind.splatted().map(usize::from) { + write!(f, "#[splat] ")?; + } write!(f, "{ty:?}")?; } if fn_sig_kind.c_variadic() { @@ -1148,7 +1271,7 @@ impl FnHeader { } pub fn dummy() -> Self { - Self { fn_sig_kind: I::FSigKind::new(I::Abi::rust(), I::Safety::safe(), false) } + Self { fn_sig_kind: I::FSigKind::dummy() } } } diff --git a/compiler/rustc_type_ir/src/ty_kind/closure.rs b/compiler/rustc_type_ir/src/ty_kind/closure.rs index 1ad5ed45e8b10..f4cdfa11735c0 100644 --- a/compiler/rustc_type_ir/src/ty_kind/closure.rs +++ b/compiler/rustc_type_ir/src/ty_kind/closure.rs @@ -364,7 +364,7 @@ pub struct CoroutineClosureSignature { // Like the `fn_sig_as_fn_ptr_ty` of a regular closure, these types // never actually differ. But we save them rather than recreating them // from scratch just for good measure. - /// Always safe, RustCall, non-c-variadic + /// Always safe, RustCall, non-c-variadic, non-splatted #[type_visitable(ignore)] #[type_foldable(identity)] pub fn_sig_kind: I::FSigKind, diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index 2a6520ffc2ba5..5a35df0574638 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -407,6 +407,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let sig = this.tcx.mk_fn_sig( args.iter().map(|a| a.layout.ty), dest.layout.ty, + // FIXME(splat): Do we need to set splatted here? + // (Currently this also ignores c_variadic) FnSigKind::default().set_abi(caller_abi).set_safe(true), ); let caller_fn_abi = this.fn_abi_of_fn_ptr(ty::Binder::dummy(sig), ty::List::empty())?; diff --git a/src/tools/miri/src/shims/sig.rs b/src/tools/miri/src/shims/sig.rs index ddfde35f47c4d..8b7c313c936bf 100644 --- a/src/tools/miri/src/shims/sig.rs +++ b/src/tools/miri/src/shims/sig.rs @@ -274,7 +274,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { inputs_and_output.push(shim_sig.ret); let fn_sig_binder = Binder::dummy(FnSig { inputs_and_output: this.machine.tcx.mk_type_list(&inputs_and_output), - // Safety does not matter for the ABI. + // Safety and splatted do not matter for the ABI. fn_sig_kind: FnSigKind::default().set_abi(shim_sig.abi).set_safe(true), }); let callee_fn_abi = this.fn_abi_of_fn_ptr(fn_sig_binder, Default::default())?; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/callee.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/callee.rs index 3d478912a3db2..d06f8de85e8c5 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/callee.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/callee.rs @@ -159,6 +159,8 @@ impl<'db> InferenceContext<'_, 'db> { // impl forces the closure kind to `FnOnce` i.e. `u8`. let kind_ty = autoderef.ctx().table.next_ty_var(); let interner = autoderef.ctx().interner(); + + // Ignore splatting, it is unsupported on closures. let call_sig = interner.mk_fn_sig( [coroutine_closure_sig.tupled_inputs_ty], coroutine_closure_sig.to_coroutine( diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs index d004b5e3ef1d6..00276f5ade6c5 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs @@ -577,6 +577,7 @@ pub fn callable_sig_from_fn_trait<'db>( Binder::dummy(FnSig { inputs_and_output, c_variadic: false, + // FIXME(splat): handle splatted arguments safety: abi::Safety::Safe, abi: FnAbi::RustCall, }), diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs index 335aff2c1df16..e80a2249365e5 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs @@ -574,6 +574,7 @@ impl<'db, 'a> TyLoweringContext<'db, 'a> { abi: fn_.abi.as_ref().map_or(FnAbi::Rust, FnAbi::from_symbol), safety: if fn_.is_unsafe { Safety::Unsafe } else { Safety::Safe }, c_variadic: fn_.is_varargs, + // FIXME(splat): handle splatted arguments inputs_and_output: Tys::new_from_slice(&args), }), ) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/interner.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/interner.rs index 4095dbe47d852..72c379aa3d790 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/interner.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/interner.rs @@ -2333,6 +2333,7 @@ impl<'db> DbInterner<'db> { self.replace_escaping_bound_vars_uncached(value.skip_binder(), delegate) } + // FIXME: add splat support when the experiment is complete pub fn mk_fn_sig( self, inputs: I, diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/ty.rs b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/ty.rs index 39abdaf079b63..fbe019a334336 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/ty.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/next_solver/ty.rs @@ -1444,6 +1444,7 @@ impl<'db> DbInterner<'db> { TyKind::Tuple(params) => params, _ => panic!(), }; + // Ignore splatting, it is unsupported on closures. self.mk_fn_sig(params, s.output(), s.c_variadic, safety, FnAbi::Rust) }) } diff --git a/tests/ui/splat/splat-assoc-fn-tuple-simple.rs b/tests/ui/splat/splat-assoc-fn-tuple-simple.rs new file mode 100644 index 0000000000000..d2681c0d2574c --- /dev/null +++ b/tests/ui/splat/splat-assoc-fn-tuple-simple.rs @@ -0,0 +1,22 @@ +//@ run-pass +//! Test using `#[splat]` on associated function tuple arguments (no receivers). + +#![allow(incomplete_features)] +#![feature(splat)] + +struct Foo; + +impl Foo { + fn tuple_1(#[splat] (_a,): (u32,)) {} + + fn tuple_3(#[splat] (_a, _b, _c): (u32, i32, i8)) {} +} + +fn main() { + // FIXME(splat): should splatted functions be callable with tupled and un-tupled arguments? + // Add a tupled test for each call if they are. + //Foo::tuple_1((1u32,)); + + Foo::tuple_1(1u32); + Foo::tuple_3(1u32, 2i32, 3i8); +} diff --git a/tests/ui/splat/splat-fn-ptr-tuple.rs b/tests/ui/splat/splat-fn-ptr-tuple.rs new file mode 100644 index 0000000000000..f5c0a33883ff4 --- /dev/null +++ b/tests/ui/splat/splat-fn-ptr-tuple.rs @@ -0,0 +1,51 @@ +//@ failure-status: 101 + +//@ normalize-stderr: "thread.*panicked at compiler.*" -> "" +//@ normalize-stderr: "note: rustc.*running on.*" -> "note: rustc {version} running on {platform}" +//@ normalize-stderr: "note: compiler flags.*\n\n" -> "" +//@ normalize-stderr: " +\d{1,}: .*\n" -> "" +//@ normalize-stderr: " + at .*\n" -> "" +//@ normalize-stderr: ".*omitted \d{1,} frames?.*\n" -> "" +//@ normalize-stderr: ".*note: Some details are omitted.*\n" -> "" + +//! Test using `#[splat]` on tuple arguments of simple functions. +//! Currently ICEs, but if we fix it, we'll want to know and update this test to pass. + +#![allow(incomplete_features)] +#![feature(splat)] + +fn tuple_args(#[splat] (_a, _b): (u32, i8)) {} + +fn splat_non_terminal_arg(#[splat] (_a, _b): (u32, i8), _c: f64) {} + +fn main() { + // FIXME(splat): not currently supported, can be supported when we no longer require a DefId in + // MIR lowering + // FIXME(rustfmt): the attribute gets deleted by rustfmt + // Functions + #[rustfmt::skip] + let fn_ptr: fn(#[splat] (u32, i8)) = tuple_args; + fn_ptr(1, 2); //~ ERROR no splatted def for function or method callee + fn_ptr(1u32, 2i8); + + // FIXME(splat): should splatted functions be callable with tupled and un-tupled arguments? + // Add a tupled test for each call if they are. + //fn_ptr((1, 2)); // ERROR this splatted function takes 2 arguments, but 1 was provided + + #[rustfmt::skip] + let fn_ptr: fn(#[splat] (u32, i8), f64) = splat_non_terminal_arg; + fn_ptr(1, 2, 3.5); + fn_ptr(1u32, 2i8, 3.5f64); + + // Function pointers + #[rustfmt::skip] + let fn_ptr: *const fn(#[splat] (u32, i8)) = tuple_args as *const fn(#[splat] (u32, i8)); + (*fn_ptr)(1, 2); + (*fn_ptr)(1u32, 2i8); + + #[rustfmt::skip] + let fn_ptr: *const fn(#[splat] (u32, i8), f64) = + splat_non_terminal_arg as *const fn(#[splat] (u32, i8), f64); + (*fn_ptr)(1, 2, 3.5); + (*fn_ptr)(1u32, 2i8, 3.5f64); +} diff --git a/tests/ui/splat/splat-fn-ptr-tuple.stderr b/tests/ui/splat/splat-fn-ptr-tuple.stderr new file mode 100644 index 0000000000000..09438bdf040e7 --- /dev/null +++ b/tests/ui/splat/splat-fn-ptr-tuple.stderr @@ -0,0 +1,24 @@ +error: internal compiler error: compiler/rustc_mir_build/src/thir/cx/expr.rs:1314:13: no splatted def for function or method callee + --> $DIR/splat-fn-ptr-tuple.rs:28:5 + | +LL | fn_ptr(1, 2); + | ^^^^^^^^^^^^ + + + +Box +stack backtrace: + +note: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-compiler&template=ice.md + +note: please make sure that you have updated to the latest nightly + +note: rustc {version} running on {platform} + +query stack during panic: +#0 [thir_body] building THIR for `main` +#1 [check_unsafety] unsafety-checking `main` +#2 [analysis] running analysis passes on crate `splat_fn_ptr_tuple` +end of query stack +error: aborting due to 1 previous error + diff --git a/tests/ui/splat/splat-fn-tuple-generic-fail.rs b/tests/ui/splat/splat-fn-tuple-generic-fail.rs new file mode 100644 index 0000000000000..8a8651e75c9ff --- /dev/null +++ b/tests/ui/splat/splat-fn-tuple-generic-fail.rs @@ -0,0 +1,27 @@ +//! Test failing use of `#[splat]` on tuple trait arguments of generic functions. + +#![allow(incomplete_features)] +#![feature(splat)] +#![feature(tuple_trait)] + +fn splat_generic_tuple(#[splat] _t: T) {} + +fn main() { + // FIXME(splat): should splatted functions be callable with tupled and un-tupled arguments? + + // Calling with un-splatted arguments might look like it works, but the actual generic type is + // a tuple inside another tuple. Aren't generics great? + splat_generic_tuple((1, 2)); + splat_generic_tuple((1u32, 2i8)); + + // FIXME(splat): Make the splat generic handling code handle tuples inside tuples + // (if we want to support tupled calls) + splat_generic_tuple::<(((u32, i8)))>((1, 2)); //~ ERROR this splatted function takes 2 arguments, but 1 was provided + splat_generic_tuple::<(((u32, i8)))>((1u32, 2i8)); //~ ERROR this splatted function takes 2 arguments, but 1 was provided + + splat_generic_tuple::<((u32, i8))>((1, 2)); //~ ERROR this splatted function takes 2 arguments, but 1 was provided + splat_generic_tuple::<((u32, i8))>((1u32, 2i8)); //~ ERROR this splatted function takes 2 arguments, but 1 was provided + + splat_generic_tuple::<(u32, i8)>((1, 2)); //~ ERROR this splatted function takes 2 arguments, but 1 was provided + splat_generic_tuple::<(u32, i8)>((1u32, 2i8)); //~ ERROR this splatted function takes 2 arguments, but 1 was provided +} diff --git a/tests/ui/splat/splat-fn-tuple-generic-fail.stderr b/tests/ui/splat/splat-fn-tuple-generic-fail.stderr new file mode 100644 index 0000000000000..7fd4a0719b493 --- /dev/null +++ b/tests/ui/splat/splat-fn-tuple-generic-fail.stderr @@ -0,0 +1,39 @@ +error[E0057]: this splatted function takes 2 arguments, but 1 was provided + --> $DIR/splat-fn-tuple-generic-fail.rs:19:5 + | +LL | splat_generic_tuple::<(((u32, i8)))>((1, 2)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0057]: this splatted function takes 2 arguments, but 1 was provided + --> $DIR/splat-fn-tuple-generic-fail.rs:20:5 + | +LL | splat_generic_tuple::<(((u32, i8)))>((1u32, 2i8)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0057]: this splatted function takes 2 arguments, but 1 was provided + --> $DIR/splat-fn-tuple-generic-fail.rs:22:5 + | +LL | splat_generic_tuple::<((u32, i8))>((1, 2)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0057]: this splatted function takes 2 arguments, but 1 was provided + --> $DIR/splat-fn-tuple-generic-fail.rs:23:5 + | +LL | splat_generic_tuple::<((u32, i8))>((1u32, 2i8)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0057]: this splatted function takes 2 arguments, but 1 was provided + --> $DIR/splat-fn-tuple-generic-fail.rs:25:5 + | +LL | splat_generic_tuple::<(u32, i8)>((1, 2)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0057]: this splatted function takes 2 arguments, but 1 was provided + --> $DIR/splat-fn-tuple-generic-fail.rs:26:5 + | +LL | splat_generic_tuple::<(u32, i8)>((1u32, 2i8)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 6 previous errors + +For more information about this error, try `rustc --explain E0057`. diff --git a/tests/ui/splat/splat-fn-tuple-generic.rs b/tests/ui/splat/splat-fn-tuple-generic.rs new file mode 100644 index 0000000000000..b7e3615f62c45 --- /dev/null +++ b/tests/ui/splat/splat-fn-tuple-generic.rs @@ -0,0 +1,23 @@ +//@ run-pass +//! Test using `#[splat]` on tuple trait arguments of generic functions. + +#![allow(incomplete_features)] +#![feature(splat)] +#![feature(tuple_trait)] + +fn splat_generic_tuple(#[splat] _t: T) {} + +fn main() { + // Calling with un-splatted arguments might look like it works, but the actual generic type is + // a tuple inside another tuple. Aren't generics great? + splat_generic_tuple((1, 2)); + splat_generic_tuple((1u32, 2i8)); + + // Generic tuple trait implementers are resolved during caller typeck. + splat_generic_tuple::<(u32, i8)>(1u32, 2i8); + splat_generic_tuple(1u32, 2i8); + splat_generic_tuple(1, 2); + + splat_generic_tuple::<()>(); + splat_generic_tuple(); +} diff --git a/tests/ui/splat/splat-fn-tuple-simple.rs b/tests/ui/splat/splat-fn-tuple-simple.rs new file mode 100644 index 0000000000000..c7234a15b9d55 --- /dev/null +++ b/tests/ui/splat/splat-fn-tuple-simple.rs @@ -0,0 +1,32 @@ +//@ run-pass +//! Test using `#[splat]` on tuple arguments of simple functions. + +#![allow(incomplete_features)] +#![feature(splat)] + +fn tuple_args(#[splat] (_a, _b): (u32, i8)) {} + +fn splat_non_terminal_arg(#[splat] (_a, _b): (u32, i8), _c: f64) {} + +fn main() { + tuple_args(1, 2); + // FIXME(splat): should splatted functions be callable with tupled and un-tupled arguments? + // Add a tupled test for each call if they are. + //tuple_args((1, 2)); + + tuple_args(1, 2); + tuple_args(1u32, 2i8); + + splat_non_terminal_arg(1, 2, 3.5); + splat_non_terminal_arg(1u32, 2i8, 3.5f64); + + #[expect(unused_variables, reason = "FIXME(splat or lint): this is obviously used")] + let fn_ptr = tuple_args; + fn_ptr(1, 2); + fn_ptr(1u32, 2i8); + + #[expect(unused_variables, reason = "FIXME(splat or lint): this is obviously used")] + let fn_ptr = splat_non_terminal_arg; + fn_ptr(1, 2, 3.5); + fn_ptr(1u32, 2i8, 3.5f64); +} diff --git a/tests/ui/splat/splat-generics-everywhere.rs b/tests/ui/splat/splat-generics-everywhere.rs new file mode 100644 index 0000000000000..16093fe338357 --- /dev/null +++ b/tests/ui/splat/splat-generics-everywhere.rs @@ -0,0 +1,68 @@ +//@ run-pass +//! Test using `#[splat]` on tuples with generics in various positions. + +#![allow(incomplete_features)] +#![feature(splat)] + +struct Foo(T); + +// FIXME(splat): also add assoc/method with splatted generic tuple traits +// also add generics inside the splatted tuple +impl Foo { + fn new(t: T) -> Self { + Self(t) + } + + fn assoc(_u: U, #[splat] _s: ()) {} + + fn method(&self, _v: V, #[splat] _s: (u32, f64)) {} + + fn lifetime<'a>(&self, #[splat] _s: (u32, f64, &'a str)) {} + + fn const_generic(&self, #[splat] _s: (u32, f64, [u8; N])) {} +} + +// FIXME(splat): also add generics to the trait +// also add assoc/method with splatted generic tuple traits +// also add generics inside the splatted tuple +trait BarTrait { + fn trait_assoc(w: W, #[splat] _s: ()); + + fn trait_method(&self, x: X, #[splat] _s: (u32, f64)); + + fn trait_lifetime<'a>(&self, #[splat] _s: (u32, f64, &'a str)) {} + + fn trait_const_generic(&self, #[splat] _s: (u32, f64, [u8; N])) {} +} + +impl BarTrait for Foo { + fn trait_assoc(_w: W, #[splat] _s: ()) {} + + fn trait_method(&self, _x: X, #[splat] _s: (u32, f64)) {} + + fn trait_lifetime<'a>(&self, #[splat] _s: (u32, f64, &'a str)) {} + + fn trait_const_generic(&self, #[splat] _s: (u32, f64, [u8; N])) {} +} + +// FIXME(splat): +// - add `T: Tuple` generics tests +// - add const fn generics tests + +fn main() { + // FIXME(splat): should splatted functions be callable with tupled and un-tupled arguments? + // Add a tupled test for each call if they are. + //Foo::::assoc(("u",)); + + Foo::::assoc("u"); + Foo::::trait_assoc("w"); + + let foo = Foo::new("t"); + foo.method("v", 1u32, 2.3); + foo.lifetime(1u32, 2.3, "asdf"); + foo.const_generic(1u32, 2.3, [1, 2, 3]); + + foo.trait_method("x", 42u32, 9.8); + foo.trait_lifetime(1u32, 2.3, "asdf"); + foo.trait_const_generic(1u32, 2.3, [1, 2, 3]); +} diff --git a/tests/ui/splat/splat-invalid.rs b/tests/ui/splat/splat-invalid.rs new file mode 100644 index 0000000000000..2c4bf57ef972a --- /dev/null +++ b/tests/ui/splat/splat-invalid.rs @@ -0,0 +1,47 @@ +//! Test using `#[splat]` incorrectly, in ways not covered by other tests. + +#![allow(incomplete_features)] +#![feature(splat)] +#![feature(c_variadic)] + +fn multisplat_bad(#[splat] (_a, _b): (u32, i8), #[splat] (_c, _d): (u32, i8)) {} +//~^ ERROR multiple `#[splat]`s are not allowed in the same function + +unsafe extern "C" fn splat_variadic(#[splat] (_a, _b): (u32, i8), varargs: ...) {} +//~^ ERROR `...` and `#[splat]` are not allowed in the same function + +unsafe extern "C" fn splat_variadic2(varargs: ..., #[splat] (_a, _b): (u32, i8)) {} +//~^ ERROR `...` and `#[splat]` are not allowed in the same function +//~| ERROR `...` must be the last argument of a C-variadic function + +extern "C" { + fn splat_variadic3(#[splat] (_a, _b): (u32, i8), ...) {} + //~^ ERROR incorrect function inside `extern` block + //~| ERROR `...` and `#[splat]` are not allowed in the same function + + fn splat_variadic4(..., #[splat] (_a, _b): (u32, i8)) {} + //~^ ERROR incorrect function inside `extern` block + //~| ERROR `...` and `#[splat]` are not allowed in the same function + //~| ERROR `...` must be the last argument of a C-variadic function + + // FIXME(splat): tuple layouts are unspecified. Should this error in addition to + // the existing `improper_ctypes` lint? + #[expect(improper_ctypes)] + fn bar_2(#[splat] _: (u32, i8)); +} + +trait FooTrait { + fn has_splat(#[splat] _: ()); + + fn no_splat(_: (u32, f64)); +} + +struct Foo; + +impl FooTrait for Foo { + fn has_splat(_: ()) {} //~ ERROR method `has_splat` has an incompatible type for trait + + fn no_splat(#[splat] _: (u32, f64)) {} //~ ERROR method `no_splat` has an incompatible type for trait +} + +fn main() {} diff --git a/tests/ui/splat/splat-invalid.stderr b/tests/ui/splat/splat-invalid.stderr new file mode 100644 index 0000000000000..74c54c00cd285 --- /dev/null +++ b/tests/ui/splat/splat-invalid.stderr @@ -0,0 +1,106 @@ +error: multiple `#[splat]`s are not allowed in the same function + --> $DIR/splat-invalid.rs:7:19 + | +LL | fn multisplat_bad(#[splat] (_a, _b): (u32, i8), #[splat] (_c, _d): (u32, i8)) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: remove `#[splat]` from all but one argument + +error: `...` and `#[splat]` are not allowed in the same function + --> $DIR/splat-invalid.rs:10:37 + | +LL | unsafe extern "C" fn splat_variadic(#[splat] (_a, _b): (u32, i8), varargs: ...) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^ + | + = help: remove `#[splat]` or remove `...` + +error: `...` must be the last argument of a C-variadic function + --> $DIR/splat-invalid.rs:13:38 + | +LL | unsafe extern "C" fn splat_variadic2(varargs: ..., #[splat] (_a, _b): (u32, i8)) {} + | ^^^^^^^^^^^^ + +error: `...` and `#[splat]` are not allowed in the same function + --> $DIR/splat-invalid.rs:13:38 + | +LL | unsafe extern "C" fn splat_variadic2(varargs: ..., #[splat] (_a, _b): (u32, i8)) {} + | ^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: remove `#[splat]` or remove `...` + +error: incorrect function inside `extern` block + --> $DIR/splat-invalid.rs:18:8 + | +LL | extern "C" { + | ---------- `extern` blocks define existing foreign functions and functions inside of them cannot have a body +LL | fn splat_variadic3(#[splat] (_a, _b): (u32, i8), ...) {} + | ^^^^^^^^^^^^^^^ cannot have a body -- help: remove the invalid body: `;` + | + = help: you might have meant to write a function accessible through FFI, which can be done by writing `extern fn` outside of the `extern` block + = note: for more information, visit https://doc.rust-lang.org/std/keyword.extern.html + +error: `...` and `#[splat]` are not allowed in the same function + --> $DIR/splat-invalid.rs:18:24 + | +LL | fn splat_variadic3(#[splat] (_a, _b): (u32, i8), ...) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ + | + = help: remove `#[splat]` or remove `...` + +error: incorrect function inside `extern` block + --> $DIR/splat-invalid.rs:22:8 + | +LL | extern "C" { + | ---------- `extern` blocks define existing foreign functions and functions inside of them cannot have a body +... +LL | fn splat_variadic4(..., #[splat] (_a, _b): (u32, i8)) {} + | ^^^^^^^^^^^^^^^ cannot have a body -- help: remove the invalid body: `;` + | + = help: you might have meant to write a function accessible through FFI, which can be done by writing `extern fn` outside of the `extern` block + = note: for more information, visit https://doc.rust-lang.org/std/keyword.extern.html + +error: `...` must be the last argument of a C-variadic function + --> $DIR/splat-invalid.rs:22:24 + | +LL | fn splat_variadic4(..., #[splat] (_a, _b): (u32, i8)) {} + | ^^^ + +error: `...` and `#[splat]` are not allowed in the same function + --> $DIR/splat-invalid.rs:22:24 + | +LL | fn splat_variadic4(..., #[splat] (_a, _b): (u32, i8)) {} + | ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: remove `#[splat]` or remove `...` + +error[E0053]: method `has_splat` has an incompatible type for trait + --> $DIR/splat-invalid.rs:42:5 + | +LL | fn has_splat(_: ()) {} + | ^^^^^^^^^^^^^^^^^^^ expected fn with arg 0 splatted, found fn with no splatted arg + | +note: type in trait + --> $DIR/splat-invalid.rs:34:5 + | +LL | fn has_splat(#[splat] _: ()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: expected signature `fn(#[splat] ())` + found signature `fn(())` + +error[E0053]: method `no_splat` has an incompatible type for trait + --> $DIR/splat-invalid.rs:44:5 + | +LL | fn no_splat(#[splat] _: (u32, f64)) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected fn with no splatted arg, found fn with arg 0 splatted + | +note: type in trait + --> $DIR/splat-invalid.rs:36:5 + | +LL | fn no_splat(_: (u32, f64)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: expected signature `fn((_, _))` + found signature `fn(#[splat] (_, _))` + +error: aborting due to 11 previous errors + +For more information about this error, try `rustc --explain E0053`. diff --git a/tests/ui/splat/splat-method-tuple-simple.rs b/tests/ui/splat/splat-method-tuple-simple.rs new file mode 100644 index 0000000000000..887c9515bcdae --- /dev/null +++ b/tests/ui/splat/splat-method-tuple-simple.rs @@ -0,0 +1,40 @@ +//@ run-pass +//! Test using `#[splat]` on method tuple arguments (with receivers). + +#![allow(incomplete_features)] +#![feature(splat)] + +struct Foo; + +impl Foo { + fn tuple_2(&self, #[splat] (a, _b): (u32, i8)) -> u32 { + a + } + + fn tuple_4(&self, #[splat] (a, _b, _c, _d): (u32, i8, (), f32)) -> u32 { + a + } +} + +#[expect(dead_code)] +struct TupleStruct(u32, i8); + +impl TupleStruct { + fn tuple_2(&self, #[splat] (a, _b): (u32, i8)) -> u32 { + a + } +} + +fn main() { + let foo = Foo; + + // FIXME(splat): should splatted functions be callable with tupled and un-tupled arguments? + // Add a tupled test for each call if they are. + //foo.tuple_2((1, 2)); + + foo.tuple_2(1u32, 2i8); + foo.tuple_4(1u32, 2i8, (), 3f32); + + let tuple_struct = TupleStruct(1u32, 2i8); + tuple_struct.tuple_2(1u32, 2i8); +} diff --git a/tests/ui/splat/splat-non-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..6f2815e2b6579 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)); } diff --git a/tests/ui/splat/splat-non-function.stderr b/tests/ui/splat/splat-non-fn-arg.stderr similarity index 73% rename from tests/ui/splat/splat-non-function.stderr rename to tests/ui/splat/splat-non-fn-arg.stderr index bb7fc6bf5c84f..ddd16094fb104 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,12 @@ 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 +error: aborting due to 11 previous errors diff --git a/tests/ui/splat/splat-non-tuple.rs b/tests/ui/splat/splat-non-tuple.rs new file mode 100644 index 0000000000000..e7c0d59764c18 --- /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 { + fn tuple_1(_: (u32,)) {} + //~^ ERROR method `tuple_1` has an incompatible type for trait + //~| NOTE expected fn with arg 0 splatted, found fn with no splatted arg + //~| NOTE expected signature `fn(#[splat] (_,))` + //~| NOTE found signature `fn((_,))` + + fn tuple_4( + #[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-non-tuple.stderr b/tests/ui/splat/splat-non-tuple.stderr new file mode 100644 index 0000000000000..c489d5d56cd09 --- /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:40:5 + | +LL | fn tuple_1(_: (u32,)) {} + | ^^^^^^^^^^^^^^^^^^^^^ expected fn with arg 0 splatted, found fn with no splatted arg + | +note: type in trait + --> $DIR/splat-non-tuple.rs:19:5 + | +LL | fn tuple_1(#[splat] _: (u32,)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: expected signature `fn(#[splat] (_,))` + 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 new file mode 100644 index 0000000000000..621f0e04f67e1 --- /dev/null +++ b/tests/ui/splat/splat-overload-at-home.rs @@ -0,0 +1,52 @@ +//@ run-pass +// ignore-tidy-linelength +//! Test using `#[splat]` on some "overloading at home" example code. +//! + +#![allow(incomplete_features)] +#![feature(splat)] +#![feature(tuple_trait)] + +struct Foo; + +trait MethodArgs: std::marker::Tuple { + fn call_method(self, _this: &Foo); +} +impl MethodArgs for () { + fn call_method(self, _this: &Foo) {} +} +impl MethodArgs for (i32,) { + fn call_method(self, _this: &Foo) {} +} +impl MethodArgs for (i32, String) { + fn call_method(self, _this: &Foo) {} +} + +impl Foo { + fn method(&self, #[splat] args: T) { + args.call_method(self) + } +} + +fn main() { + let foo = Foo; + + // FIXME(splat): should splatted functions be callable with tupled and un-tupled arguments? + // Add a tupled test for each call if they are. + //foo.method(()); + //foo.method((42i32,)); + + // Generic tuple trait implementers work without explicit tuple type parameters. + foo.method::<()>(); + foo.method(); + + foo.method::<(i32,)>(42i32); + foo.method::<(i32,)>(42); + foo.method(42i32); + foo.method(42); + + foo.method::<(i32, String)>(42i32, "asdf".to_owned()); + foo.method::<(i32, String)>(42, "asdf".to_owned()); + foo.method(42i32, "asdf".to_owned()); + foo.method(42, "asdf".to_owned()); +} diff --git a/tests/ui/splat/splat-trait-tuple.rs b/tests/ui/splat/splat-trait-tuple.rs new file mode 100644 index 0000000000000..a5b74a40cffd9 --- /dev/null +++ b/tests/ui/splat/splat-trait-tuple.rs @@ -0,0 +1,45 @@ +//@ run-pass +//! Test using `#[splat]` on trait assoc function/method tuple arguments. + +#![allow(incomplete_features)] +#![feature(splat)] + +trait FooTrait { + fn tuple_1_trait(#[splat] _: (u32,)); + + fn tuple_2_trait(&self, #[splat] _: (u32, f32)); +} + +struct Foo; + +impl FooTrait for Foo { + // Currently, splat attributes on impls must match traits. This provides better UX. + fn tuple_1_trait(#[splat] _: (u32,)) {} + + fn tuple_2_trait(&self, #[splat] _: (u32, f32)) {} +} + +#[expect(dead_code)] +struct TupleStruct(u32, i8); + +impl FooTrait for TupleStruct { + fn tuple_1_trait(#[splat] _: (u32,)) {} + + fn tuple_2_trait(&self, #[splat] _: (u32, f32)) {} +} + +fn main() { + let foo = Foo; + + // FIXME(splat): should splatted functions be callable with tupled and un-tupled arguments? + // Add a tupled test for each call if they are. + //Foo::tuple_1_trait((1u32,)); + //foo.tuple_2_trait((1, 3.5)); + + Foo::tuple_1_trait(1u32); + foo.tuple_2_trait(1, 3.5); + + let tuple_struct = TupleStruct(1u32, 2i8); + TupleStruct::tuple_1_trait(1u32); + tuple_struct.tuple_2_trait(1, 3.5) +} diff --git a/tests/ui/symbol-names/basic.legacy.stderr b/tests/ui/symbol-names/basic.legacy.stderr index 8309b8f957a7a..e205a04af84e1 100644 --- a/tests/ui/symbol-names/basic.legacy.stderr +++ b/tests/ui/symbol-names/basic.legacy.stderr @@ -1,10 +1,10 @@ -error: symbol-name(_ZN5basic4main17h947b7a9ed2b2bf56E) +error: symbol-name(_ZN5basic4main17h84a005f7899ecc4fE) --> $DIR/basic.rs:8:1 | LL | #[rustc_dump_symbol_name] | ^^^^^^^^^^^^^^^^^^^^^^^^^ -error: demangling(basic::main::h947b7a9ed2b2bf56) +error: demangling(basic::main::h84a005f7899ecc4f) --> $DIR/basic.rs:8:1 | LL | #[rustc_dump_symbol_name] diff --git a/tests/ui/symbol-names/issue-60925.legacy.stderr b/tests/ui/symbol-names/issue-60925.legacy.stderr index 359bafdff4692..d794ec1974293 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$3foo17hba5ac046b858f549E) +error: symbol-name(_ZN11issue_609253foo37Foo$LT$issue_60925..llv$u6d$..Foo$GT$3foo17h384d09b4d2a48661E) --> $DIR/issue-60925.rs:21:9 | LL | #[rustc_dump_symbol_name] | ^^^^^^^^^^^^^^^^^^^^^^^^^ -error: demangling(issue_60925::foo::Foo::foo::hba5ac046b858f549) +error: demangling(issue_60925::foo::Foo::foo::h384d09b4d2a48661) --> $DIR/issue-60925.rs:21:9 | LL | #[rustc_dump_symbol_name] From d83e6d969974b92b666adb436aa80649c9af9426 Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 24 Apr 2026 11:31:49 +1000 Subject: [PATCH 5/6] Fix splat ICEs in arg mismatch checks --- compiler/rustc_hir_typeck/src/demand.rs | 17 +++- .../rustc_hir_typeck/src/fn_ctxt/checks.rs | 79 ++++++++++++------- 2 files changed, 64 insertions(+), 32 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/demand.rs b/compiler/rustc_hir_typeck/src/demand.rs index 29fc6729e4b3e..76f55e883e36e 100644 --- a/compiler/rustc_hir_typeck/src/demand.rs +++ b/compiler/rustc_hir_typeck/src/demand.rs @@ -3,11 +3,11 @@ use rustc_hir::def::Res; use rustc_hir::intravisit::Visitor; use rustc_hir::{self as hir, find_attr}; use rustc_infer::infer::DefineOpaqueTypes; -use rustc_middle::bug; use rustc_middle::ty::adjustment::AllowTwoPhase; use rustc_middle::ty::error::{ExpectedFound, TypeError}; use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::{self, AssocItem, BottomUpFolder, Ty, TypeFoldable, TypeVisitableExt}; +use rustc_middle::{bug, span_bug}; use rustc_span::{DUMMY_SP, Ident, Span, sym}; use rustc_trait_selection::infer::InferCtxtExt; use rustc_trait_selection::traits::ObligationCause; @@ -405,9 +405,22 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Unify the method signature with our incompatible arg, to // do inference in the *opposite* direction and to find out // what our ideal rcvr ty would look like. + let Some(input_arg) = method.sig.inputs().get(idx + 1) else { + if method.sig.splatted().is_some() { + // FIXME(splat): when the arg is splatted, adjust its index + return None; + } else { + span_bug!( + self.tcx.def_span(method.def_id), + "arg index {} out of bounds for method with {} inputs", + idx + 1, + method.sig.inputs().len(), + ); + } + }; let _ = self .at(&ObligationCause::dummy(), self.param_env) - .eq(DefineOpaqueTypes::Yes, method.sig.inputs()[idx + 1], arg_ty) + .eq(DefineOpaqueTypes::Yes, *input_arg, arg_ty) .ok()?; self.select_obligations_where_possible(|errs| { // Yeet the errors, we're already reporting errors. diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index 40126b6f06c47..09fbe1da7dee4 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -740,12 +740,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { compatibility_diagonal, formal_and_expected_inputs, provided_args, - fn_sig_kind.c_variadic(), err_code, fn_def_id, call_span, call_expr, tuple_arguments, + fn_sig_kind, ); } } @@ -770,14 +770,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { compatibility_diagonal: IndexVec>, formal_and_expected_inputs: IndexVec, Ty<'tcx>)>, provided_args: IndexVec>, - // FIXME(splat): when the feature design is settled, replace this with FnSigKind, and - // improve the errors here - c_variadic: bool, err_code: ErrCode, fn_def_id: Option, call_span: Span, call_expr: &'tcx hir::Expr<'tcx>, tuple_arguments: TupleArgumentsFlag, + // FIXME(splat): when the feature design is settled, improve the errors here + fn_sig_kind: FnSigKind, ) -> ErrorGuaranteed { // Next, let's construct the error @@ -786,12 +785,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { compatibility_diagonal, formal_and_expected_inputs, provided_args, - c_variadic, err_code, fn_def_id, call_span, call_expr, tuple_arguments, + fn_sig_kind, ); // First, check if we just need to wrap some arguments in a tuple. @@ -871,6 +870,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { &fn_call_diag_ctxt.formal_and_expected_inputs, fn_call_diag_ctxt.call_metadata.is_method, tuple_arguments, + fn_sig_kind, ); // And add a suggestion block for all of the parameters @@ -1549,6 +1549,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { formal_and_expected_inputs: &IndexVec, Ty<'tcx>)>, is_method: bool, tuple_arguments: TupleArgumentsFlag, + fn_sig_kind: FnSigKind, ) { let Some(mut def_id) = callable_def_id else { return; @@ -1649,22 +1650,38 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { debug_assert_eq!(params_with_generics.len(), matched_inputs.len()); // Gather all mismatched parameters with generics. let mut mismatched_params = Vec::>::new(); + let mut use_splat_fallback = false; if let Some(expected_idx) = expected_idx { let expected_idx = ExpectedIdx::from_usize(expected_idx); - let &(expected_generic, ref expected_param) = - ¶ms_with_generics[expected_idx]; - if let Some(expected_generic) = expected_generic { - mismatched_params.push(MismatchedParam { - idx: expected_idx, - generic: expected_generic, - param: expected_param, - deps: SmallVec::new(), - }); - } else { - // Still mark the mismatched parameter - spans.push_span_label(expected_param.span(), ""); - } - } else { + match params_with_generics.get(expected_idx) { + Some(&(Some(expected_generic), ref expected_param)) => mismatched_params + .push(MismatchedParam { + idx: expected_idx, + generic: expected_generic, + param: expected_param, + deps: SmallVec::new(), + }), + Some((None, expected_param)) => { + // Still mark the mismatched parameter + spans.push_span_label(expected_param.span(), ""); + } + None => { + if fn_sig_kind.splatted().is_none() { + // FIXME(splat): when the arg is splatted, adjust its index + use_splat_fallback = true; + } else { + span_bug!( + self.tcx.def_span(def_id), + "arg index {} out of bounds for method with {} inputs", + expected_idx.as_usize(), + params_with_generics.len(), + ); + } + } + }; + } + + if expected_idx.is_none() || use_splat_fallback { mismatched_params.extend( params_with_generics.iter_enumerated().zip(matched_inputs).filter_map( |((idx, &(generic, ref param)), matched_idx)| { @@ -2108,24 +2125,24 @@ impl<'a, 'tcx> FnCallDiagCtxt<'a, 'tcx> { compatibility_diagonal: IndexVec>, formal_and_expected_inputs: IndexVec, Ty<'tcx>)>, provided_args: IndexVec>, - c_variadic: bool, err_code: ErrCode, fn_def_id: Option, call_span: Span, call_expr: &'tcx Expr<'tcx>, tuple_arguments: TupleArgumentsFlag, + fn_sig_kind: FnSigKind, ) -> Self { let arg_matching_ctxt = ArgMatchingCtxt::new( arg, compatibility_diagonal, formal_and_expected_inputs, provided_args, - c_variadic, err_code, fn_def_id, call_span, call_expr, tuple_arguments, + fn_sig_kind, ); // The algorithm here is inspired by levenshtein distance and longest common subsequence. @@ -2208,7 +2225,7 @@ impl<'a, 'tcx> FnCallDiagCtxt<'a, 'tcx> { self.arg_matching_ctxt.args_ctxt.call_metadata.full_call_span, format!( "{call_name} takes {}{} but {} {} supplied", - if self.c_variadic { "at least " } else { "" }, + if self.fn_sig_kind.c_variadic() { "at least " } else { "" }, potentially_plural_count( self.formal_and_expected_inputs.len(), "argument" @@ -2238,6 +2255,7 @@ impl<'a, 'tcx> FnCallDiagCtxt<'a, 'tcx> { &self.formal_and_expected_inputs, self.call_metadata.is_method, self.tuple_arguments, + self.fn_sig_kind, ); self.suggest_confusable(&mut err); Some(err.emit()) @@ -2403,6 +2421,7 @@ impl<'a, 'tcx> FnCallDiagCtxt<'a, 'tcx> { &self.formal_and_expected_inputs, self.call_metadata.is_method, self.tuple_arguments, + self.fn_sig_kind, ); self.arg_matching_ctxt.suggest_confusable(&mut err); self.detect_dotdot(&mut err, provided_ty, self.provided_args[provided_idx]); @@ -2441,7 +2460,7 @@ impl<'a, 'tcx> FnCallDiagCtxt<'a, 'tcx> { format!( "this {} takes {}{} but {} {} supplied", self.call_metadata.call_name, - if self.c_variadic { "at least " } else { "" }, + if self.fn_sig_kind.c_variadic() { "at least " } else { "" }, potentially_plural_count(self.formal_and_expected_inputs.len(), "argument"), potentially_plural_count(self.provided_args.len(), "argument"), pluralize!("was", self.provided_args.len()) @@ -3005,24 +3024,24 @@ impl<'a, 'tcx> ArgMatchingCtxt<'a, 'tcx> { compatibility_diagonal: IndexVec>, formal_and_expected_inputs: IndexVec, Ty<'tcx>)>, provided_args: IndexVec>, - c_variadic: bool, err_code: ErrCode, fn_def_id: Option, call_span: Span, call_expr: &'tcx Expr<'tcx>, tuple_arguments: TupleArgumentsFlag, + fn_sig_kind: FnSigKind, ) -> Self { let args_ctxt = ArgsCtxt::new( arg, compatibility_diagonal, formal_and_expected_inputs, provided_args, - c_variadic, err_code, fn_def_id, call_span, call_expr, tuple_arguments, + fn_sig_kind, ); let provided_arg_tys = args_ctxt.provided_arg_tys(); @@ -3152,24 +3171,24 @@ impl<'a, 'tcx> ArgsCtxt<'a, 'tcx> { compatibility_diagonal: IndexVec>, formal_and_expected_inputs: IndexVec, Ty<'tcx>)>, provided_args: IndexVec>, - c_variadic: bool, err_code: ErrCode, fn_def_id: Option, call_span: Span, call_expr: &'tcx Expr<'tcx>, tuple_arguments: TupleArgumentsFlag, + fn_sig_kind: FnSigKind, ) -> Self { let call_ctxt: CallCtxt<'_, '_> = CallCtxt::new( arg, compatibility_diagonal, formal_and_expected_inputs, provided_args, - c_variadic, err_code, fn_def_id, call_span, call_expr, tuple_arguments, + fn_sig_kind, ); let call_metadata = call_ctxt.call_metadata(); @@ -3271,12 +3290,12 @@ struct CallCtxt<'a, 'tcx> { compatibility_diagonal: IndexVec>, formal_and_expected_inputs: IndexVec, Ty<'tcx>)>, provided_args: IndexVec>, - c_variadic: bool, err_code: ErrCode, fn_def_id: Option, call_span: Span, call_expr: &'tcx hir::Expr<'tcx>, tuple_arguments: TupleArgumentsFlag, + fn_sig_kind: FnSigKind, callee_expr: Option<&'tcx Expr<'tcx>>, callee_ty: Option>, } @@ -3295,12 +3314,12 @@ impl<'a, 'tcx> CallCtxt<'a, 'tcx> { compatibility_diagonal: IndexVec>, formal_and_expected_inputs: IndexVec, Ty<'tcx>)>, provided_args: IndexVec>, - c_variadic: bool, err_code: ErrCode, fn_def_id: Option, call_span: Span, call_expr: &'tcx hir::Expr<'tcx>, tuple_arguments: TupleArgumentsFlag, + fn_sig_kind: FnSigKind, ) -> CallCtxt<'a, 'tcx> { let callee_expr = match &call_expr.peel_blocks().kind { hir::ExprKind::Call(callee, _) => Some(*callee), @@ -3327,12 +3346,12 @@ impl<'a, 'tcx> CallCtxt<'a, 'tcx> { compatibility_diagonal, formal_and_expected_inputs, provided_args, - c_variadic, err_code, fn_def_id, call_span, call_expr, tuple_arguments, + fn_sig_kind, callee_expr, callee_ty, } From 7e8fef2d88ae25057cd8e83b4e54871f342dc235 Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 24 Apr 2026 11:43:08 +1000 Subject: [PATCH 6/6] Reject #[splat] in TypeInfo, for now --- .../src/const_eval/type_info.rs | 11 +- .../ui/splat/splat-fn-ptr-tuple-const-ice.rs | 41 +++++++ .../splat/splat-fn-ptr-tuple-const-ice.stderr | 109 ++++++++++++++++++ ...ptr-tuple.rs => splat-fn-ptr-tuple-ice.rs} | 21 +++- ...e.stderr => splat-fn-ptr-tuple-ice.stderr} | 4 +- tests/ui/splat/splat-fn-tuple-simple.rs | 13 +++ 6 files changed, 188 insertions(+), 11 deletions(-) create mode 100644 tests/ui/splat/splat-fn-ptr-tuple-const-ice.rs create mode 100644 tests/ui/splat/splat-fn-ptr-tuple-const-ice.stderr rename tests/ui/splat/{splat-fn-ptr-tuple.rs => splat-fn-ptr-tuple-ice.rs} (76%) rename tests/ui/splat/{splat-fn-ptr-tuple.stderr => splat-fn-ptr-tuple-ice.stderr} (93%) 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 be940b3e5e980..ab3851326c9e0 100644 --- a/compiler/rustc_const_eval/src/const_eval/type_info.rs +++ b/compiler/rustc_const_eval/src/const_eval/type_info.rs @@ -7,7 +7,7 @@ use rustc_ast::Mutability; use rustc_hir::LangItem; use rustc_middle::span_bug; use rustc_middle::ty::layout::TyAndLayout; -use rustc_middle::ty::{self, Const, FnHeader, FnSigKind, FnSigTys, ScalarInt, Ty, TyCtxt}; +use rustc_middle::ty::{self, Const, FnHeader, FnSigTys, ScalarInt, Ty, TyCtxt}; use rustc_span::{Symbol, sym}; use crate::const_eval::CompileTimeMachine; @@ -466,13 +466,8 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> { self.write_scalar(Scalar::from_bool(fn_sig_kind.c_variadic()), &field_place)?; } sym::splat => { - self.write_scalar( - // Use the same encoding as FnSigKind.splatted - Scalar::from_u16( - fn_sig_kind.splatted().unwrap_or(FnSigKind::NO_SPLATTED_ARG_INDEX), - ), - &field_place, - )?; + // FIXME(splat): use the same encoding as FnSigKind.splatted + span_bug!(self.tcx.def_span(field.did), "const `#[splat]` is not implemented") } other => span_bug!(self.tcx.def_span(field.did), "unimplemented field {other}"), } diff --git a/tests/ui/splat/splat-fn-ptr-tuple-const-ice.rs b/tests/ui/splat/splat-fn-ptr-tuple-const-ice.rs new file mode 100644 index 0000000000000..5603f8294a09c --- /dev/null +++ b/tests/ui/splat/splat-fn-ptr-tuple-const-ice.rs @@ -0,0 +1,41 @@ +//@ failure-status: 101 + +//@ normalize-stderr: "thread.*panicked at compiler.*" -> "" +//@ normalize-stderr: "note: rustc.*running on.*" -> "note: rustc {version} running on {platform}" +//@ normalize-stderr: "note: compiler flags.*\n\n" -> "" +//@ normalize-stderr: " +\d{1,}: .*\n" -> "" +//@ normalize-stderr: " + at .*\n" -> "" +//@ normalize-stderr: ".*omitted \d{1,} frames?.*\n" -> "" +//@ normalize-stderr: ".*note: Some details are omitted.*\n" -> "" + +//! Test using `#[splat]` on tuple arguments of simple function pointers. +//! Currently ICEs, but if we fix it, we'll want to know and update this test to pass. + +#![allow(incomplete_features)] +#![feature(splat)] + +const fn tuple_args_const(#[splat] (_a, _b): (u32, i8)) {} + +fn main() { + // FIXME(splat): not currently supported, can be supported when we no longer require a DefId in + // MIR lowering + // FIXME(rustfmt): the attribute gets deleted by rustfmt + + // Functions + + #[rustfmt::skip] + const FN_PTR: fn(#[splat] (u32, i8)) = tuple_args_const; + const _: () = FN_PTR(1, 2); //~ ERROR no splatted def for function or method callee + const _: () = FN_PTR(1u32, 2i8); //~ ERROR no splatted def for function or method callee + + // FIXME(splat): should splatted functions be callable with tupled and un-tupled arguments? + // Add a tupled test for each call if they are. + const _: () = FN_PTR((1, 2)); //~ ERROR this splatted function takes 2 arguments, but 1 was provided + + // Function pointers + #[rustfmt::skip] + const FN_PTR_PTR: *const fn(#[splat] (u32, i8)) = + tuple_args_const as *const fn(#[splat] (u32, i8)); + const _: () = (*FN_PTR_PTR)(1, 2); //~ ERROR no splatted def for function or method callee + const _: () = (*FN_PTR_PTR)(1u32, 2i8); //~ ERROR no splatted def for function or method callee +} diff --git a/tests/ui/splat/splat-fn-ptr-tuple-const-ice.stderr b/tests/ui/splat/splat-fn-ptr-tuple-const-ice.stderr new file mode 100644 index 0000000000000..0a0c90bba0579 --- /dev/null +++ b/tests/ui/splat/splat-fn-ptr-tuple-const-ice.stderr @@ -0,0 +1,109 @@ +error: internal compiler error: compiler/rustc_mir_build/src/thir/cx/expr.rs:1314:13: no splatted def for function or method callee + --> $DIR/splat-fn-ptr-tuple-const-ice.rs:28:19 + | +LL | const _: () = FN_PTR(1, 2); + | ^^^^^^^^^^^^ + + + +Box +stack backtrace: + +note: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-compiler&template=ice.md + +note: please make sure that you have updated to the latest nightly + +note: rustc {version} running on {platform} + +query stack during panic: +#0 [thir_body] building THIR for `main::_` +#1 [check_match] match-checking `main::_` +#2 [mir_built] building MIR for `main::_` +#3 [trivial_const] checking if `main::_` is a trivial const +#4 [eval_to_const_value_raw] simplifying constant for the type system `main::_` +#5 [analysis] running analysis passes on crate `splat_fn_ptr_tuple_const_ice` +end of query stack +error: internal compiler error: compiler/rustc_mir_build/src/thir/cx/expr.rs:1314:13: no splatted def for function or method callee + --> $DIR/splat-fn-ptr-tuple-const-ice.rs:29:19 + | +LL | const _: () = FN_PTR(1u32, 2i8); + | ^^^^^^^^^^^^^^^^^ + + + +Box +stack backtrace: + +note: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-compiler&template=ice.md + +note: please make sure that you have updated to the latest nightly + +note: rustc {version} running on {platform} + +query stack during panic: +#0 [thir_body] building THIR for `main::_` +#1 [check_match] match-checking `main::_` +#2 [mir_built] building MIR for `main::_` +#3 [trivial_const] checking if `main::_` is a trivial const +#4 [eval_to_const_value_raw] simplifying constant for the type system `main::_` +#5 [analysis] running analysis passes on crate `splat_fn_ptr_tuple_const_ice` +end of query stack +error[E0057]: this splatted function takes 2 arguments, but 1 was provided + --> $DIR/splat-fn-ptr-tuple-const-ice.rs:33:19 + | +LL | const _: () = FN_PTR((1, 2)); + | ^^^^^^^^^^^^^^ + +error: internal compiler error: compiler/rustc_mir_build/src/thir/cx/expr.rs:1314:13: no splatted def for function or method callee + --> $DIR/splat-fn-ptr-tuple-const-ice.rs:39:19 + | +LL | const _: () = (*FN_PTR_PTR)(1, 2); + | ^^^^^^^^^^^^^^^^^^^ + + + +Box +stack backtrace: + +note: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-compiler&template=ice.md + +note: please make sure that you have updated to the latest nightly + +note: rustc {version} running on {platform} + +query stack during panic: +#0 [thir_body] building THIR for `main::_` +#1 [check_match] match-checking `main::_` +#2 [mir_built] building MIR for `main::_` +#3 [trivial_const] checking if `main::_` is a trivial const +#4 [eval_to_const_value_raw] simplifying constant for the type system `main::_` +#5 [analysis] running analysis passes on crate `splat_fn_ptr_tuple_const_ice` +end of query stack +error: internal compiler error: compiler/rustc_mir_build/src/thir/cx/expr.rs:1314:13: no splatted def for function or method callee + --> $DIR/splat-fn-ptr-tuple-const-ice.rs:40:19 + | +LL | const _: () = (*FN_PTR_PTR)(1u32, 2i8); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + + + +Box +stack backtrace: + +note: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-compiler&template=ice.md + +note: please make sure that you have updated to the latest nightly + +note: rustc {version} running on {platform} + +query stack during panic: +#0 [thir_body] building THIR for `main::_` +#1 [check_match] match-checking `main::_` +#2 [mir_built] building MIR for `main::_` +#3 [trivial_const] checking if `main::_` is a trivial const +#4 [eval_to_const_value_raw] simplifying constant for the type system `main::_` +#5 [analysis] running analysis passes on crate `splat_fn_ptr_tuple_const_ice` +end of query stack +error: aborting due to 5 previous errors + +For more information about this error, try `rustc --explain E0057`. diff --git a/tests/ui/splat/splat-fn-ptr-tuple.rs b/tests/ui/splat/splat-fn-ptr-tuple-ice.rs similarity index 76% rename from tests/ui/splat/splat-fn-ptr-tuple.rs rename to tests/ui/splat/splat-fn-ptr-tuple-ice.rs index f5c0a33883ff4..21f6b5e3f1a20 100644 --- a/tests/ui/splat/splat-fn-ptr-tuple.rs +++ b/tests/ui/splat/splat-fn-ptr-tuple-ice.rs @@ -8,7 +8,7 @@ //@ normalize-stderr: ".*omitted \d{1,} frames?.*\n" -> "" //@ normalize-stderr: ".*note: Some details are omitted.*\n" -> "" -//! Test using `#[splat]` on tuple arguments of simple functions. +//! Test using `#[splat]` on tuple arguments of simple function pointers. //! Currently ICEs, but if we fix it, we'll want to know and update this test to pass. #![allow(incomplete_features)] @@ -18,10 +18,13 @@ fn tuple_args(#[splat] (_a, _b): (u32, i8)) {} fn splat_non_terminal_arg(#[splat] (_a, _b): (u32, i8), _c: f64) {} +const fn tuple_args_const(#[splat] (_a, _b): (u32, i8)) {} + fn main() { // FIXME(splat): not currently supported, can be supported when we no longer require a DefId in // MIR lowering // FIXME(rustfmt): the attribute gets deleted by rustfmt + // Functions #[rustfmt::skip] let fn_ptr: fn(#[splat] (u32, i8)) = tuple_args; @@ -37,6 +40,16 @@ fn main() { fn_ptr(1, 2, 3.5); fn_ptr(1u32, 2i8, 3.5f64); + #[rustfmt::skip] + let fn_ptr: fn(#[splat] (u32, i8)) = tuple_args_const; + fn_ptr(1, 2); + fn_ptr(1u32, 2i8); + + #[rustfmt::skip] + const FN_PTR: fn(#[splat] (u32, i8)) = tuple_args_const; + FN_PTR(1, 2); + FN_PTR(1u32, 2i8); + // Function pointers #[rustfmt::skip] let fn_ptr: *const fn(#[splat] (u32, i8)) = tuple_args as *const fn(#[splat] (u32, i8)); @@ -48,4 +61,10 @@ fn main() { splat_non_terminal_arg as *const fn(#[splat] (u32, i8), f64); (*fn_ptr)(1, 2, 3.5); (*fn_ptr)(1u32, 2i8, 3.5f64); + + #[rustfmt::skip] + const FN_PTR_PTR: *const fn(#[splat] (u32, i8)) = + tuple_args_const as *const fn(#[splat] (u32, i8)); + (*FN_PTR_PTR)(1, 2); + (*FN_PTR_PTR)(1u32, 2i8); } diff --git a/tests/ui/splat/splat-fn-ptr-tuple.stderr b/tests/ui/splat/splat-fn-ptr-tuple-ice.stderr similarity index 93% rename from tests/ui/splat/splat-fn-ptr-tuple.stderr rename to tests/ui/splat/splat-fn-ptr-tuple-ice.stderr index 09438bdf040e7..0d4a1ced18712 100644 --- a/tests/ui/splat/splat-fn-ptr-tuple.stderr +++ b/tests/ui/splat/splat-fn-ptr-tuple-ice.stderr @@ -1,5 +1,5 @@ error: internal compiler error: compiler/rustc_mir_build/src/thir/cx/expr.rs:1314:13: no splatted def for function or method callee - --> $DIR/splat-fn-ptr-tuple.rs:28:5 + --> $DIR/splat-fn-ptr-tuple-ice.rs:31:5 | LL | fn_ptr(1, 2); | ^^^^^^^^^^^^ @@ -18,7 +18,7 @@ note: rustc {version} running on {platform} query stack during panic: #0 [thir_body] building THIR for `main` #1 [check_unsafety] unsafety-checking `main` -#2 [analysis] running analysis passes on crate `splat_fn_ptr_tuple` +#2 [analysis] running analysis passes on crate `splat_fn_ptr_tuple_ice` end of query stack error: aborting due to 1 previous error diff --git a/tests/ui/splat/splat-fn-tuple-simple.rs b/tests/ui/splat/splat-fn-tuple-simple.rs index c7234a15b9d55..805bda6e45e3f 100644 --- a/tests/ui/splat/splat-fn-tuple-simple.rs +++ b/tests/ui/splat/splat-fn-tuple-simple.rs @@ -8,6 +8,8 @@ fn tuple_args(#[splat] (_a, _b): (u32, i8)) {} fn splat_non_terminal_arg(#[splat] (_a, _b): (u32, i8), _c: f64) {} +const fn tuple_args_const(#[splat] (_a, _b): (u32, i8)) {} + fn main() { tuple_args(1, 2); // FIXME(splat): should splatted functions be callable with tupled and un-tupled arguments? @@ -20,6 +22,9 @@ fn main() { splat_non_terminal_arg(1, 2, 3.5); splat_non_terminal_arg(1u32, 2i8, 3.5f64); + tuple_args_const(1, 2); + tuple_args_const(1u32, 2i8); + #[expect(unused_variables, reason = "FIXME(splat or lint): this is obviously used")] let fn_ptr = tuple_args; fn_ptr(1, 2); @@ -29,4 +34,12 @@ fn main() { let fn_ptr = splat_non_terminal_arg; fn_ptr(1, 2, 3.5); fn_ptr(1u32, 2i8, 3.5f64); + + #[expect(unused_variables, reason = "FIXME(splat or lint): this is obviously used")] + let fn_ptr = tuple_args_const; + fn_ptr(1, 2); + fn_ptr(1u32, 2i8); + + const _: () = tuple_args_const(1, 2); + const _: () = tuple_args_const(1u32, 2i8); }