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 compiler/rustc_ast/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3057,6 +3057,9 @@ impl FnDecl {
pub fn c_variadic(&self) -> bool {
self.inputs.last().is_some_and(|arg| matches!(arg.ty.kind, TyKind::CVarArgs))
}
pub fn splatted(&self) -> bool {
self.inputs.last().is_some_and(|arg| arg.attrs.iter().any(|attr| attr.has_name(sym::splat)))
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.

since it's a per-arg attribute, are there checks ensuring it's only used on the last arg?

}
}

/// Is the trait definition an auto trait?
Expand Down
21 changes: 15 additions & 6 deletions compiler/rustc_ast_lowering/src/delegation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ impl<'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'_, 'hir, R> {

let is_method = self.is_method(sig_id, span);

let (param_count, c_variadic) = self.param_count(sig_id);
let (param_count, c_variadic, splatted) = self.param_count(sig_id);

let mut generics = self.uplift_delegation_generics(delegation, sig_id, item_id);

Expand All @@ -156,8 +156,14 @@ impl<'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'_, 'hir, R> {
span,
);

let decl =
self.lower_delegation_decl(sig_id, param_count, c_variadic, span, &generics);
let decl = self.lower_delegation_decl(
sig_id,
param_count,
c_variadic,
splatted,
span,
&generics,
);

let sig = self.lower_delegation_sig(sig_id, decl, span);
let ident = self.lower_ident(delegation.ident);
Expand Down Expand Up @@ -271,17 +277,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*/, bool /*splatted*/) {
let sig = self.tcx.fn_sig(def_id).skip_binder().skip_binder();
(sig.inputs().len() + usize::from(sig.c_variadic), sig.c_variadic)
(sig.inputs().len() + usize::from(sig.c_variadic), sig.c_variadic, sig.splatted)
}

fn lower_delegation_decl(
&mut self,
sig_id: DefId,
param_count: usize,
c_variadic: bool,
splatted: bool,
span: Span,
generics: &GenericsGenerationResults<'hir>,
) -> &'hir hir::FnDecl<'hir> {
Expand Down Expand Up @@ -313,6 +320,7 @@ impl<'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'_, 'hir, R> {
inputs,
output: hir::FnRetTy::Return(output),
c_variadic,
splatted,
lifetime_elision_allowed: true,
implicit_self: hir::ImplicitSelfKind::None,
})
Expand Down Expand Up @@ -612,6 +620,7 @@ impl<'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'_, 'hir, R> {
inputs: &[],
output: hir::FnRetTy::DefaultReturn(span),
c_variadic: false,
splatted: false,
lifetime_elision_allowed: true,
implicit_self: hir::ImplicitSelfKind::None,
});
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_ast_lowering/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,7 @@ impl<'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'_, 'hir, R> {
inputs,
output,
c_variadic: false,
splatted: false,
implicit_self: hir::ImplicitSelfKind::None,
lifetime_elision_allowed: false,
});
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_ast_lowering/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1826,6 +1826,7 @@ impl<'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'_, 'hir, R> {
coro: Option<CoroutineKind>,
) -> &'hir hir::FnDecl<'hir> {
let c_variadic = decl.c_variadic();
let splatted = decl.splatted();

// Skip the `...` (`CVarArgs`) trailing arguments from the AST,
// as they are not explicit in HIR/Ty function signatures.
Expand Down Expand Up @@ -1898,6 +1899,7 @@ impl<'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'_, 'hir, R> {
inputs,
output,
c_variadic,
splatted,
lifetime_elision_allowed: self.resolver.lifetime_elision_allowed(fn_node_id),
implicit_self: decl.inputs.get(0).map_or(hir::ImplicitSelfKind::None, |arg| {
let is_mutable_pat = matches!(
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_ast_passes/src/ast_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,7 @@ impl<'a> AstValidator<'a> {
sym::deny,
sym::expect,
sym::forbid,
sym::splat,
sym::warn,
];
!attr.has_any_name(&arr) && rustc_attr_parsing::is_builtin_attr(*attr)
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_ast_passes/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
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 @@ -597,6 +597,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) {
gate_all!(const_block_items, "const block items are experimental");
gate_all!(final_associated_functions, "`final` on trait functions is experimental");
gate_all!(impl_restriction, "`impl` restrictions are experimental");
gate_all!(splat, "`#[splat] fn` is incomplete", "call as func((a, ...)) instead");

if !visitor.features.never_patterns() {
if let Some(spans) = spans.get(&sym::never_patterns) {
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 @@ -63,6 +63,7 @@ pub(crate) mod rustc_allocator;
pub(crate) mod rustc_dump;
pub(crate) mod rustc_internal;
pub(crate) mod semantics;
pub(crate) mod splat;
pub(crate) mod stability;
pub(crate) mod test_attrs;
pub(crate) mod traits;
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 @@ -53,6 +53,7 @@ use crate::attributes::rustc_allocator::*;
use crate::attributes::rustc_dump::*;
use crate::attributes::rustc_internal::*;
use crate::attributes::semantics::*;
use crate::attributes::splat::*;
use crate::attributes::stability::*;
use crate::attributes::test_attrs::*;
use crate::attributes::traits::*;
Expand Down Expand Up @@ -327,6 +328,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
1 change: 1 addition & 0 deletions compiler/rustc_borrowck/src/diagnostics/region_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1087,6 +1087,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
liberated_sig.inputs().iter().copied(),
peeled_ty,
liberated_sig.c_variadic,
liberated_sig.splatted,
hir::Safety::Safe,
rustc_abi::ExternAbi::Rust,
Comment on lines 1089 to 1092
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.

preexisting, but imo this PR shows that we should do this:

on main we could merge c_variadic, splatted, safety and ExternAbi into a separate struct, with some nice methods on it for creating default versions, making it easy to use structured update syntax in the cases where it's needed, and generally simplifying all the mk_fn_sig calls

)),
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_borrowck/src/type_check/input_output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
user_provided_sig.inputs().iter().copied(),
output_ty,
user_provided_sig.c_variadic,
user_provided_sig.splatted,
user_provided_sig.safety,
user_provided_sig.abi,
);
Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_codegen_cranelift/src/value_and_place.rs
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,7 @@ pub(crate) fn assert_assignable<'tcx>(
let FnSig {
inputs_and_output: types_from,
c_variadic: c_variadic_from,
splatted: splatted_from,
safety: unsafety_from,
abi: abi_from,
} = from_sig;
Expand All @@ -881,6 +882,7 @@ pub(crate) fn assert_assignable<'tcx>(
let FnSig {
inputs_and_output: types_to,
c_variadic: c_variadic_to,
splatted: splatted_to,
safety: unsafety_to,
abi: abi_to,
} = to_sig;
Expand All @@ -898,6 +900,11 @@ pub(crate) fn assert_assignable<'tcx>(
"Can't write fn ptr with incompatible sig {:?} to place with sig {:?}\n\n{:#?}",
from_sig, to_sig, fx,
);
assert_eq!(
splatted_from, splatted_to,
"Can't write fn ptr with incompatible sig {:?} to place with sig {:?}\n\n{:#?}",
from_sig, to_sig, fx,
);
assert_eq!(
unsafety_from, unsafety_to,
"Can't write fn ptr with incompatible sig {:?} to place with sig {:?}\n\n{:#?}",
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_codegen_gcc/src/intrinsic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1472,6 +1472,7 @@ fn get_rust_try_fn<'a, 'gcc, 'tcx>(
iter::once(i8p),
tcx.types.unit,
false,
false,
rustc_hir::Safety::Unsafe,
ExternAbi::Rust,
)),
Expand All @@ -1483,6 +1484,7 @@ fn get_rust_try_fn<'a, 'gcc, 'tcx>(
[i8p, i8p].iter().cloned(),
tcx.types.unit,
false,
false,
rustc_hir::Safety::Unsafe,
ExternAbi::Rust,
)),
Expand All @@ -1492,6 +1494,7 @@ fn get_rust_try_fn<'a, 'gcc, 'tcx>(
[try_fn_ty, i8p, catch_fn_ty],
tcx.types.i32,
false,
false,
rustc_hir::Safety::Unsafe,
ExternAbi::Rust,
));
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_codegen_llvm/src/intrinsic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1279,6 +1279,7 @@ fn get_rust_try_fn<'a, 'll, 'tcx>(
[i8p],
tcx.types.unit,
false,
false,
hir::Safety::Unsafe,
ExternAbi::Rust,
)),
Expand All @@ -1290,6 +1291,7 @@ fn get_rust_try_fn<'a, 'll, 'tcx>(
[i8p, i8p],
tcx.types.unit,
false,
false,
hir::Safety::Unsafe,
ExternAbi::Rust,
)),
Expand All @@ -1299,6 +1301,7 @@ fn get_rust_try_fn<'a, 'll, 'tcx>(
[try_fn_ty, i8p, catch_fn_ty],
tcx.types.i32,
false,
false,
hir::Safety::Unsafe,
ExternAbi::Rust,
));
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 @@ -1136,6 +1136,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
};

// Split the rust-call tupled arguments off.
// FIXME(splat): un-tuple splatted arguments in codegen, for performance
let (first_args, untuple) = if sig.abi() == ExternAbi::RustCall
&& let Some((tup, args)) = args.split_last()
{
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 @@ -405,6 +405,7 @@ fn arg_local_refs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
let arg_decl = &mir.local_decls[local];
let arg_ty = fx.monomorphize(arg_decl.ty);

// FIXME(splat): re-tuple splatted arguments that were un-tupled in the ABI
if Some(local) == mir.spread_arg {
// This argument (e.g., the last argument in the "rust-call" ABI)
// is a tuple that was spread at the ABI level and now we have
Expand Down
5 changes: 4 additions & 1 deletion compiler/rustc_const_eval/src/const_eval/type_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> {
sig: &FnSigTys<TyCtxt<'tcx>>,
fn_header: &FnHeader<TyCtxt<'tcx>>,
) -> InterpResult<'tcx> {
let FnHeader { safety, c_variadic, abi } = fn_header;
let FnHeader { safety, c_variadic, splatted, abi } = fn_header;

for (field_idx, field) in
place.layout().ty.ty_adt_def().unwrap().non_enum_variant().fields.iter_enumerated()
Expand Down Expand Up @@ -465,6 +465,9 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> {
sym::variadic => {
self.write_scalar(Scalar::from_bool(*c_variadic), &field_place)?;
}
sym::splat => {
self.write_scalar(Scalar::from_bool(*splatted), &field_place)?;
}
other => span_bug!(self.tcx.def_span(field.did), "unimplemented field {other}"),
}
}
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 @@ -651,6 +651,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
};

// Special handling for the closure ABI: untuple the last argument.
// FIXME(splat): un-tuple splatted arguments that were tupled in typecheck
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
9 changes: 9 additions & 0 deletions compiler/rustc_feature/src/builtin_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -905,6 +905,15 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
EncodeCrossCrate::Yes, pin_ergonomics, experimental!(pin_v2),
),

// The `#[splat]` attribute is part of the `splat` experiment
// that improves the ergonomics of function overloading, tracked in:
//
// - https://github.com/rust-lang/rust/issues/153629
gated!(
splat, Normal, template!(Word), WarnFollowing,
EncodeCrossCrate::Yes, experimental!(splat)
),

// ==========================================================================
// Internal attributes: Stability, deprecation, and unsafe:
// ==========================================================================
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 @@ -670,6 +670,9 @@ declare_features! (
(unstable, sparc_target_feature, "1.84.0", Some(132783)),
/// Allows specialization of implementations (RFC 1210).
(incomplete, specialization, "1.7.0", Some(31844)),
/// Experimental "splatting" of function call arguments at the call site.
/// e.g. `foo(a, b, c)` calls `#[splat] fn foo((a: A, b: B, c: C))`.
(incomplete, splat, "CURRENT_RUSTC_VERSION", Some(153629)),
/// Allows using `#[rustc_align_static(...)]` on static items.
(unstable, static_align, "1.91.0", Some(146177)),
/// Allows attributes on expressions and non-item statements.
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 @@ -1603,6 +1603,9 @@ pub enum AttributeKind {
span: Span,
},

/// Represents `#[splat]`
Splat(Span),

/// Represents `#[stable]`, `#[unstable]` and `#[rustc_allowed_through_unstable_modules]`.
Stability {
stability: Stability,
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 @@ -190,6 +190,7 @@ impl AttributeKind {
RustcUnsafeSpecializationMarker(..) => No,
Sanitize { .. } => No,
ShouldPanic { .. } => No,
Splat(..) => Yes,
Stability { .. } => Yes,
TargetFeature { .. } => No,
TestRunner(..) => Yes,
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_hir/src/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3926,6 +3926,8 @@ pub struct FnDecl<'hir> {
pub inputs: &'hir [Ty<'hir>],
pub output: FnRetTy<'hir>,
pub c_variadic: bool,
/// Is the last argument of the function splatted into multiple arguments in callers?
pub splatted: bool,
/// Does the function have an implicit self?
pub implicit_self: ImplicitSelfKind,
/// Is lifetime elision allowed.
Expand Down
10 changes: 8 additions & 2 deletions compiler/rustc_hir/src/intravisit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1212,8 +1212,14 @@ pub fn walk_fn_decl<'v, V: Visitor<'v>>(
visitor: &mut V,
function_declaration: &'v FnDecl<'v>,
) -> V::Result {
let FnDecl { inputs, output, c_variadic: _, implicit_self: _, lifetime_elision_allowed: _ } =
function_declaration;
let FnDecl {
inputs,
output,
c_variadic: _,
splatted: _,
implicit_self: _,
lifetime_elision_allowed: _,
} = function_declaration;
walk_list!(visitor, visit_ty_unambig, *inputs);
visitor.visit_fn_ret_ty(output)
}
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_hir_analysis/src/check/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ fn check_main_fn_ty(tcx: TyCtxt<'_>, main_def_id: DefId) {
error = true;
}

// FIXME(splat): also reject `#[splat]` on main function arguments
if let Some(attr_span) = find_attr!(tcx, main_def_id, TrackCaller(span) => *span) {
tcx.dcx().emit_err(errors::TrackCallerOnMain { span: attr_span, annotated: main_span });
error = true;
Expand Down Expand Up @@ -155,6 +156,7 @@ fn check_main_fn_ty(tcx: TyCtxt<'_>, main_def_id: DefId) {
[],
expected_return_type,
false,
false,
hir::Safety::Safe,
ExternAbi::Rust,
));
Expand Down
Loading
Loading