Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 55 additions & 2 deletions compiler/rustc_ast_passes/src/ast_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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<Span> {
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<Span>) {
let (splatted_arg_indexes, mut splatted_spans): (Vec<u16>, Vec<Span>) = 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() });
Copy link
Copy Markdown
Contributor

@oli-obk oli-obk Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm somewhat worried that due to these errors being non-blocking for the rest of the compiler, that it's possible to cause weird ICEs, but that's nothing new wrt most of the ast validation checks.

View changes since the review

}

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) {
Expand All @@ -394,6 +446,7 @@ impl<'a> AstValidator<'a> {
sym::deny,
sym::expect,
sym::forbid,
sym::splat,
sym::warn,
];
!attr.has_any_name(&arr) && rustc_attr_parsing::is_builtin_attr(*attr)
Expand Down
28 changes: 28 additions & 0 deletions compiler/rustc_ast_passes/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Span>,
}

#[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<Span>,
}

#[derive(Diagnostic)]
#[diag("documentation comments cannot be applied to function parameters")]
pub(crate) struct FnParamDocComment {
Expand All @@ -132,6 +159,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"
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_ast_passes/src/feature_gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_attr_parsing/src/attributes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
17 changes: 17 additions & 0 deletions compiler/rustc_attr_parsing/src/attributes/splat.rs
Original file line number Diff line number Diff line change
@@ -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<S: Stage> NoArgsAttributeParser<S> for SplatParser {
const PATH: &[Symbol] = &[sym::splat];
const ON_DUPLICATE: OnDuplicate<S> = 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),
Comment on lines +13 to +14
Copy link
Copy Markdown
Contributor

@oli-obk oli-obk Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could also just not allow this for now, or add a test that excercises this code path

View changes since the review

]);
const CREATE: fn(Span) -> AttributeKind = AttributeKind::Splat;
}
2 changes: 2 additions & 0 deletions compiler/rustc_attr_parsing/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down Expand Up @@ -336,6 +337,7 @@ attribute_parsers!(
Single<WithoutArgs<RustcStrictCoherenceParser>>,
Single<WithoutArgs<RustcTrivialFieldReadsParser>>,
Single<WithoutArgs<RustcUnsafeSpecializationMarkerParser>>,
Single<WithoutArgs<SplatParser>>,
Single<WithoutArgs<ThreadLocalParser>>,
Single<WithoutArgs<TrackCallerParser>>,
// tidy-alphabetical-end
Expand Down
8 changes: 8 additions & 0 deletions compiler/rustc_feature/src/builtin_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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:
// ==========================================================================
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_hir/src/attrs/data_structures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_hir/src/attrs/encode_cross_crate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ impl AttributeKind {
RustcUnsafeSpecializationMarker(..) => No,
Sanitize { .. } => No,
ShouldPanic { .. } => No,
Splat(..) => Yes,
Stability { .. } => Yes,
TargetFeature { .. } => No,
TestRunner(..) => Yes,
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_passes/src/check_attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
| AttributeKind::RustcTrivialFieldReads
| AttributeKind::RustcUnsafeSpecializationMarker(..)
| AttributeKind::ShouldPanic { .. }
| AttributeKind::Splat(..)
| AttributeKind::Stability { .. }
| AttributeKind::TestRunner(..)
| AttributeKind::ThreadLocal
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1943,6 +1943,7 @@ symbols! {
specialization,
speed,
spirv,
splat,
spotlight,
sqrtf16,
sqrtf32,
Expand Down
6 changes: 6 additions & 0 deletions tests/ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()]`.
Expand Down
8 changes: 8 additions & 0 deletions tests/ui/feature-gates/feature-gate-splat.rs
Original file line number Diff line number Diff line change
@@ -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() {}
13 changes: 13 additions & 0 deletions tests/ui/feature-gates/feature-gate-splat.stderr
Original file line number Diff line number Diff line change
@@ -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 <https://github.com/rust-lang/rust/issues/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`.
47 changes: 47 additions & 0 deletions tests/ui/splat/splat-invalid.rs
Original file line number Diff line number Diff line change
@@ -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() {}
Loading