Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
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*/) {
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() });
}

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),
]);
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
1 change: 1 addition & 0 deletions compiler/rustc_codegen_cranelift/src/abi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_codegen_ssa/src/debuginfo/type_names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 &parameter_type in sig.inputs() {
push_debuginfo_type_name(tcx, parameter_type, true, output, visited);
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_codegen_ssa/src/mir/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment thread
teor2345 marked this conversation as resolved.
let (first_args, untuple) = if sig.abi() == ExternAbi::RustCall
&& let Some((tup, args)) = args.split_last()
{
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_codegen_ssa/src/mir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_const_eval/src/const_eval/type_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,10 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> {
sym::variadic => {
self.write_scalar(Scalar::from_bool(fn_sig_kind.c_variadic()), &field_place)?;
}
sym::splat => {
// 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}"),
}
}
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_const_eval/src/interpret/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Isn't this meant to be desugared already during MIR building? That seems preferable. There's only one place where we do MIR building, but there's 3-4 MIR consumers in-tree depending on how you count, and a bunch more out-of-tree.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Now that I re-read the discussion, that makes the most sense:
#t-lang > On overloading @ 💬

I think I can de-tuple in MIR. It's a bit more complicated because it involves de-tupling both the caller and callee. But the code might end up nicer, because we'd just be splatting whatever's inside the tuple. So there's no messing around with different argument counts.

Did you want that change in this PR?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Note that MIR is generic, so this will only work if splatting doesn't support "generic forwarding". Is that meant to be possible?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure, the kind of overloading we're aiming for is in this UI test:
https://github.com/rust-lang/rust/pull/153697/changes#diff-c3e596dfdd17cabcfcaca5dd69bd8b1e1f5a43bc19fd0ab26d23d010dae596b2R12

I don't know if we'd want generic forwarding for more complex overloading situations.

Would we need it to support custom C++ pointer types? Or any other interop features?

Crubit wants custom auto traits eventually, they're currently using a combination of generated impls and blanket impls.

(Maybe this is a broader lang + interop question.)

Copy link
Copy Markdown
Member

@programmerjake programmerjake Mar 27, 2026

Choose a reason for hiding this comment

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

assuming we want splatting to replace extern "rust-call", we'll need generic forwarding for things like: impl Fn<Args> for Box<F> https://doc.rust-lang.org/1.93.1/src/alloc/boxed.rs.html#2219

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would we need it to support custom C++ pointer types? Or any other interop features?

I don't know. I haven't been following in detail.

I think I also misunderstood what splat does. That ui test looks more like it's... unsplatting? Like, the caller passes a bunch of separate arguments, but actually those become a tuple and then the callee just sees the tuple. Is the idea that on the ABI level these are a single big tuple or separate arguments or does it not matter?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

imo it's up to the backend, but an optimizing backend should untuple the arguments

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

hmm @RalfJung @workingjubilee since this is a thing that solely exists for the Rust ABI, should this just be a field on ExternAbi::Rust ?

Copy link
Copy Markdown
Member

@RalfJung RalfJung Mar 28, 2026

Choose a reason for hiding this comment

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

I wonder if this can't just reuse the same logic as what RustCall already uses? It seems to have exactly the behavior we want -- in fact it's already more powerful than we need: with RustCall, caller and callee can disagree on whether splatting is happening. RustCall is really two features:

  • A caller-side transformation where, for ABI purposes, the last argument is a tuple that has its fields passed as separate arguments. So we may have a function with signature fn(i32, i64, f64, i32) and then we can call it via fn(i32, (i64, f64, i32)) but also via fn(i32, i64, (f64, if32)). Both generate the same actual call. This happens post-mono, i.e. the tuple can even be generic.
  • A callee-side transformation where one MIR local can be designated as a "spread arg". That local must have tuple type, and it will then gather N arguments that are separate on the ABI into the fields of that local. (The interpreter allows more non-spread args after this, but I don't know if codegen also supports this.) This is independent of whether the caller used "untupling" since the ABI actually has separate arguments here. The type of the local can be generic (but it must then only ever be instantiated as a tuple type); all the actual handling happens post-mono.

let args: Cow<'_, [FnArg<'tcx, M::Provenance>]> =
if caller_abi == ExternAbi::RustCall && !args.is_empty() {
// Untuple
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
Loading
Loading