Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
21 changes: 21 additions & 0 deletions compiler/rustc_ast/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u16> {
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?
Expand Down
23 changes: 16 additions & 7 deletions compiler/rustc_ast_lowering/src/delegation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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);
Expand Down Expand Up @@ -268,17 +274,18 @@ 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<u16> /*splatted*/) {
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.

This return type should be a struct I think 😆 it's getting a little out of hand, and usually is just passed along to another function anyway, so we could just keep passing these modifiers along together. Also the function name doesn't really represent the return value anymore... maybe param_info?

View changes since the review

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(
&mut self,
sig_id: DefId,
param_count: usize,
c_variadic: bool,
splatted: Option<u16>,
span: Span,
generics: &GenericsGenerationResults<'hir>,
) -> &'hir hir::FnDecl<'hir> {
Expand Down Expand Up @@ -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(),
})
}

Expand Down
7 changes: 6 additions & 1 deletion compiler/rustc_ast_lowering/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1844,12 +1844,15 @@ impl<'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'_, 'hir, R> {
coro: Option<CoroutineKind>,
) -> &'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| {
Expand Down Expand Up @@ -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 })
}
Expand Down
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
7 changes: 3 additions & 4 deletions compiler/rustc_borrowck/src/diagnostics/region_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_const_eval/src/const_eval/type_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
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
Loading