Skip to content

Add arg splat experiment initial tuple impl#153697

Open
teor2345 wants to merge 5 commits intorust-lang:mainfrom
teor2345:fn-arg-splat-experiment
Open

Add arg splat experiment initial tuple impl#153697
teor2345 wants to merge 5 commits intorust-lang:mainfrom
teor2345:fn-arg-splat-experiment

Conversation

@teor2345
Copy link
Copy Markdown
Contributor

@teor2345 teor2345 commented Mar 11, 2026

View all comments

This PR is part of the argument splatting lang experiment, and FFI overloading / C++ interop project goals:

Example code using existing unstable features:

Discussion of implementation strategy:

The PR is the initial implementation of the feature:

  • splat incomplete feature gate
  • #[splat] attribute on function arguments
    • only the final argument is supported for now (this is not fully checked)
  • #[splat] function parameter check at THIR level
  • splatted MIR lowering and tupling
  • feature gate and UI tests for item type filtering, non-splattable arguments, splattable tuples, generics, and the "overloading at home" example

Once this PR merges, we can add further functionality, then test it out in interop tools.

TODOs

  • Make splatted argument type inference work when the tuple is a generic
  • Implement MIR lowering of splatted arguments

Out of Scope for this PR

  • Change codegen to de-tuple caller and callee

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Mar 11, 2026
@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented Mar 11, 2026

r? @JohnTitor

rustbot has assigned @JohnTitor.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: compiler
  • compiler expanded to 69 candidates
  • Random selection from 16 candidates

@rust-log-analyzer

This comment has been minimized.

@JohnTitor
Copy link
Copy Markdown
Member

It should be better for someone on https://rust-lang.zulipchat.com/#narrow/channel/213817-t-lang/topic/On.20overloading/with/573924937 to review this, @oli-obk could you take over?

@oli-obk oli-obk assigned oli-obk and unassigned JohnTitor Mar 11, 2026
@oli-obk oli-obk added the S-blocked Status: Blocked on something else such as an RFC or other implementation work. label Mar 12, 2026
@oli-obk
Copy link
Copy Markdown
Contributor

oli-obk commented Mar 12, 2026

Let's wait for the ongoing discussion on Zulip to figure out whether we need to have a proc macro, an AST manipulating attribute (like define_opaque), or just a normal attribute

@teor2345 teor2345 marked this pull request as draft March 13, 2026 06:49
@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Mar 13, 2026
@teor2345 teor2345 force-pushed the fn-arg-splat-experiment branch from 89102bf to c784a57 Compare March 16, 2026 07:35
@rustbot rustbot added the A-attributes Area: Attributes (`#[…]`, `#![…]`) label Mar 16, 2026
@teor2345 teor2345 force-pushed the fn-arg-splat-experiment branch from c784a57 to 2d9e563 Compare March 20, 2026 01:37
@rustbot rustbot added A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. A-query-system Area: The rustc query system (https://rustc-dev-guide.rust-lang.org/query.html) labels Mar 20, 2026
@teor2345

This comment was marked as outdated.

@rust-log-analyzer

This comment has been minimized.

Copy link
Copy Markdown
Contributor Author

@teor2345 teor2345 left a comment

Choose a reason for hiding this comment

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

Here's the specific code I need help with.

(I rebased to remove unrelated typo fixes, I'll open a separate PR for those.)

View changes since this review

@rust-log-analyzer

This comment has been minimized.

@rustbot rustbot added the T-clippy Relevant to the Clippy team. label Mar 20, 2026
@rustbot rustbot added the T-rust-analyzer Relevant to the rust-analyzer team, which will review and decide on the PR/issue. label Mar 20, 2026
@rust-log-analyzer

This comment has been minimized.

JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request Mar 20, 2026
…lmann

Fix typos and markdown errors

This PR fixes some typos and markdown errors I found while writing rust-lang#153697.

I've split it out to reduce the size of that PR.
JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request Mar 20, 2026
…lmann

Fix typos and markdown errors

This PR fixes some typos and markdown errors I found while writing rust-lang#153697.

I've split it out to reduce the size of that PR.
JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request Mar 20, 2026
…lmann

Fix typos and markdown errors

This PR fixes some typos and markdown errors I found while writing rust-lang#153697.

I've split it out to reduce the size of that PR.
@rust-bors

This comment has been minimized.

@teor2345 teor2345 force-pushed the fn-arg-splat-experiment branch from 4072dbb to 886f7cf Compare March 24, 2026 04:12
@rust-log-analyzer

This comment has been minimized.

@rust-bors

This comment has been minimized.

makai410 pushed a commit to makai410/rustc_public that referenced this pull request Mar 27, 2026
Fix typos and markdown errors

This PR fixes some typos and markdown errors I found while writing rust-lang/rust#153697.

I've split it out to reduce the size of that PR.
@teor2345 teor2345 changed the title Add arg splat experiment feature gate and stub Add arg splat experiment feature gate and tuple impl Mar 27, 2026
@teor2345 teor2345 changed the title Add arg splat experiment feature gate and tuple impl Add arg splat experiment initial tuple impl Mar 27, 2026
@teor2345 teor2345 force-pushed the fn-arg-splat-experiment branch from 886f7cf to 8a0a4c6 Compare March 27, 2026 07:20
@teor2345 teor2345 marked this pull request as ready for review March 27, 2026 07:21
@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Mar 27, 2026
@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented Mar 27, 2026

Some changes occurred in compiler/rustc_attr_parsing

cc @jdonszelmann, @JonathanBrouwer

Some changes occurred to the CTFE / Miri interpreter

cc @rust-lang/miri

Some changes occurred in compiler/rustc_passes/src/check_attr.rs

cc @jdonszelmann, @JonathanBrouwer

Some changes occurred in compiler/rustc_codegen_gcc

cc @antoyo, @GuillaumeGomez

Some changes occurred in compiler/rustc_codegen_cranelift

cc @bjorn3

Some changes occurred to the CTFE machinery

cc @RalfJung, @oli-obk, @lcnr

The Miri subtree was changed

cc @rust-lang/miri

This PR changes rustc_public

cc @oli-obk, @celinval, @ouz-a, @makai410

Some changes occurred to MIR optimizations

cc @rust-lang/wg-mir-opt

Some changes occurred in compiler/rustc_hir/src/attrs

cc @jdonszelmann, @JonathanBrouwer

HIR ty lowering was modified

cc @fmease

Some changes occurred in src/tools/clippy

cc @rust-lang/clippy

rust-analyzer is developed in its own repository. If possible, consider making this change to rust-lang/rust-analyzer instead.

cc @rust-lang/rust-analyzer

@rustbot rustbot removed the S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. label Mar 27, 2026
Comment on lines +754 to +756
/// Is the final argument of this function splatted?
/// FIXME(splat): combine this with c_variadic in an enum, they are mutually exclusive.
pub splatted: bool,
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.

Some changes occurred in ...

Wow, that's a lot. Sorry for all the CCs.

I don't think adding a new field to FnSig is the way to implement this feature correctly. It ends up propagating to FnDef and a bunch of other places, so it's very intrusive.

Does anyone have any suggestions for how to implement it more locally?

(This PR passes a bunch of new #[splat] UI tests locally, so making changes to the implementation is unlikely to break anything.)

};

// 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.

@rust-log-analyzer
Copy link
Copy Markdown
Collaborator

The job aarch64-gnu-llvm-21-1 failed! Check out the build log: (web) (plain enhanced) (plain)

Click to see the possible cause of the failure (guessed by this bot)
Executing "/scripts/stage_2_test_set1.sh"
+ /scripts/stage_2_test_set1.sh
PR_CI_JOB set; skipping tidy
+ '[' 1 == 1 ']'
+ echo 'PR_CI_JOB set; skipping tidy'
+ SKIP_TIDY='--skip tidy'
+ ../x.py --stage 2 test --skip tidy --skip compiler --skip src
##[group]Building bootstrap
    Finished `dev` profile [unoptimized] target(s) in 0.04s
##[endgroup]
downloading https://static.rust-lang.org/dist/2026-03-05/rustfmt-nightly-aarch64-unknown-linux-gnu.tar.xz
---
[RUSTC-TIMING] schemars test:false 7.285
   Compiling rustc_type_ir_macros v0.0.0 (/checkout/compiler/rustc_type_ir_macros)
[RUSTC-TIMING] rustc_type_ir_macros test:false 0.909
   Compiling rustc_apfloat v0.2.3+llvm-462a31f5a5ab
error: internal compiler error: compiler/rustc_hir_analysis/src/collect.rs:1046:13: unexpected sort of node in fn_sig(): Item(Item { owner_id: DefId(0:988 ~ rustc_errors[a69d]::TRACK_DIAGNOSTIC), kind: Static(Not, TRACK_DIAGNOSTIC#0, Ty { hir_id: HirId(DefId(0:988 ~ rustc_errors[a69d]::TRACK_DIAGNOSTIC).1), span: compiler/rustc_errors/src/lib.rs:398:30: 400:2 (#0), kind: Path(Resolved(None, Path { span: compiler/rustc_errors/src/lib.rs:398:30: 400:2 (#0), res: Def(Struct, DefId(27:3056 ~ rustc_data_structures[3968]::atomic_ref::AtomicRef)), segments: [PathSegment { ident: AtomicRef#0, hir_id: HirId(DefId(0:988 ~ rustc_errors[a69d]::TRACK_DIAGNOSTIC).24), res: Def(Struct, DefId(27:3056 ~ rustc_data_structures[3968]::atomic_ref::AtomicRef)), args: Some(GenericArgs { args: [Type(Ty { hir_id: HirId(DefId(0:988 ~ rustc_errors[a69d]::TRACK_DIAGNOSTIC).23), span: compiler/rustc_errors/src/lib.rs:399:5: 399:99 (#0), kind: FnPtr(FnPtrTy { safety: Safe, abi: Rust, generic_params: [GenericParam { hir_id: HirId(DefId(0:988 ~ rustc_errors[a69d]::TRACK_DIAGNOSTIC).2), def_id: DefId(0:3185 ~ rustc_errors[a69d]::TRACK_DIAGNOSTIC::'_), name: Fresh, span: compiler/rustc_errors/src/lib.rs:399:19: 399:20 (#0), pure_wrt_drop: false, kind: Lifetime { kind: Elided(Ampersand) }, colon_span: None, source: Binder }], decl: FnDecl { inputs: [Ty { hir_id: HirId(DefId(0:988 ~ rustc_errors[a69d]::TRACK_DIAGNOSTIC).3), span: compiler/rustc_errors/src/lib.rs:399:8: 399:17 (#0), kind: Path(Resolved(None, Path { span: compiler/rustc_errors/src/lib.rs:399:8: 399:17 (#0), res: Def(Struct, DefId(0:1878 ~ rustc_errors[a69d]::diagnostic::DiagInner)), segments: [PathSegment { ident: DiagInner#0, hir_id: HirId(DefId(0:988 ~ rustc_errors[a69d]::TRACK_DIAGNOSTIC).4), res: Def(Struct, DefId(0:1878 ~ rustc_errors[a69d]::diagnostic::DiagInner)), args: None, infer_args: false }] })) }, Ty { hir_id: HirId(DefId(0:988 ~ rustc_errors[a69d]::TRACK_DIAGNOSTIC).18), span: compiler/rustc_errors/src/lib.rs:399:19: 399:71 (#0), kind: Ref(Lifetime { hir_id: HirId(DefId(0:988 ~ rustc_errors[a69d]::TRACK_DIAGNOSTIC).5), ident: '_#0, kind: Param(DefId(0:3185 ~ rustc_errors[a69d]::TRACK_DIAGNOSTIC::'_)), source: Reference, syntax: Implicit }, MutTy { ty: Ty { hir_id: HirId(DefId(0:988 ~ rustc_errors[a69d]::TRACK_DIAGNOSTIC).17), span: compiler/rustc_errors/src/lib.rs:399:24: 399:71 (#0), kind: TraitObject([PolyTraitRef { bound_generic_params: [], modifiers: TraitBoundModifiers { constness: Never, polarity: Positive }, trait_ref: TraitRef { path: Path { span: compiler/rustc_errors/src/lib.rs:399:28: 399:71 (#0), res: Def(Trait, DefId(2:4353 ~ core[2234]::ops::function::FnMut)), segments: [PathSegment { ident: FnMut#0, hir_id: HirId(DefId(0:988 ~ rustc_errors[a69d]::TRACK_DIAGNOSTIC).14), res: Def(Trait, DefId(2:4353 ~ core[2234]::ops::function::FnMut)), args: Some(GenericArgs { args: [Type(Ty { hir_id: HirId(DefId(0:988 ~ rustc_errors[a69d]::TRACK_DIAGNOSTIC).12), span: compiler/rustc_errors/src/lib.rs:399:33: 399:44 (#0), kind: Tup([Ty { hir_id: HirId(DefId(0:988 ~ rustc_errors[a69d]::TRACK_DIAGNOSTIC).6), span: compiler/rustc_errors/src/lib.rs:399:34: 399:43 (#0), kind: Path(Resolved(None, Path { span: compiler/rustc_errors/src/lib.rs:399:34: 399:43 (#0), res: Def(Struct, DefId(0:1878 ~ rustc_errors[a69d]::diagnostic::DiagInner)), segments: [PathSegment { ident: DiagInner#0, hir_id: HirId(DefId(0:988 ~ rustc_errors[a69d]::TRACK_DIAGNOSTIC).7), res: Def(Struct, DefId(0:1878 ~ rustc_errors[a69d]::diagnostic::DiagInner)), args: None, infer_args: false }] })) }]) })], constraints: [AssocItemConstraint { hir_id: HirId(DefId(0:988 ~ rustc_errors[a69d]::TRACK_DIAGNOSTIC).13), ident: Output#0, gen_args: GenericArgs { args: [], constraints: [], parenthesized: No, span_ext: no-location (#0) }, kind: Equality { term: Ty(Ty { hir_id: HirId(DefId(0:988 ~ rustc_errors[a69d]::TRACK_DIAGNOSTIC).8), span: compiler/rustc_errors/src/lib.rs:399:48: 399:71 (#0), kind: Path(Resolved(None, Path { span: compiler/rustc_errors/src/lib.rs:399:48: 399:71 (#0), res: Def(Enum, DefId(2:39971 ~ core[2234]::option::Option)), segments: [PathSegment { ident: Option#0, hir_id: HirId(DefId(0:988 ~ rustc_errors[a69d]::TRACK_DIAGNOSTIC).11), res: Def(Enum, DefId(2:39971 ~ core[2234]::option::Option)), args: Some(GenericArgs { args: [Type(Ty { hir_id: HirId(DefId(0:988 ~ rustc_errors[a69d]::TRACK_DIAGNOSTIC).9), span: compiler/rustc_errors/src/lib.rs:399:55: 399:70 (#0), kind: Path(Resolved(None, Path { span: compiler/rustc_errors/src/lib.rs:399:55: 399:70 (#0), res: Def(Struct, DefId(99:5753 ~ rustc_span[dfc4]::ErrorGuaranteed)), segments: [PathSegment { ident: ErrorGuaranteed#0, hir_id: HirId(DefId(0:988 ~ rustc_errors[a69d]::TRACK_DIAGNOSTIC).10), res: Def(Struct, DefId(99:5753 ~ rustc_span[dfc4]::ErrorGuaranteed)), args: None, infer_args: false }] })) })], constraints: [], parenthesized: No, span_ext: compiler/rustc_errors/src/lib.rs:399:54: 399:71 (#0) }), infer_args: false }] })) }) }, span: compiler/rustc_errors/src/lib.rs:399:48: 399:71 (#0) }], parenthesized: ParenSugar, span_ext: compiler/rustc_errors/src/lib.rs:399:33: 399:44 (#0) }), infer_args: false }] }, hir_ref_id: HirId(DefId(0:988 ~ rustc_errors[a69d]::TRACK_DIAGNOSTIC).15) }, span: compiler/rustc_errors/src/lib.rs:399:28: 399:71 (#0) }], TaggedRef { pointer: Lifetime { hir_id: HirId(DefId(0:988 ~ rustc_errors[a69d]::TRACK_DIAGNOSTIC).16), ident: '_#0, kind: ImplicitObjectLifetimeDefault, source: Other, syntax: Implicit }, tag: Dyn }) }, mutbl: Mut }) }], output: Return(Ty { hir_id: HirId(DefId(0:988 ~ rustc_errors[a69d]::TRACK_DIAGNOSTIC).19), span: compiler/rustc_errors/src/lib.rs:399:76: 399:99 (#0), kind: Path(Resolved(None, Path { span: compiler/rustc_errors/src/lib.rs:399:76: 399:99 (#0), res: Def(Enum, DefId(2:39971 ~ core[2234]::option::Option)), segments: [PathSegment { ident: Option#0, hir_id: HirId(DefId(0:988 ~ rustc_errors[a69d]::TRACK_DIAGNOSTIC).22), res: Def(Enum, DefId(2:39971 ~ core[2234]::option::Option)), args: Some(GenericArgs { args: [Type(Ty { hir_id: HirId(DefId(0:988 ~ rustc_errors[a69d]::TRACK_DIAGNOSTIC).20), span: compiler/rustc_errors/src/lib.rs:399:83: 399:98 (#0), kind: Path(Resolved(None, Path { span: compiler/rustc_errors/src/lib.rs:399:83: 399:98 (#0), res: Def(Struct, DefId(99:5753 ~ rustc_span[dfc4]::ErrorGuaranteed)), segments: [PathSegment { ident: ErrorGuaranteed#0, hir_id: HirId(DefId(0:988 ~ rustc_errors[a69d]::TRACK_DIAGNOSTIC).21), res: Def(Struct, DefId(99:5753 ~ rustc_span[dfc4]::ErrorGuaranteed)), args: None, infer_args: false }] })) })], constraints: [], parenthesized: No, span_ext: compiler/rustc_errors/src/lib.rs:399:82: 399:99 (#0) }), infer_args: false }] })) }), c_variadic: false, splatted: false, implicit_self: None, lifetime_elision_allowed: true }, param_idents: [None, None] }) })], constraints: [], parenthesized: No, span_ext: compiler/rustc_errors/src/lib.rs:398:39: 400:2 (#0) }), infer_args: false }] })) }, BodyId { hir_id: HirId(DefId(0:988 ~ rustc_errors[a69d]::TRACK_DIAGNOSTIC).25) }), span: compiler/rustc_errors/src/lib.rs:398:1: 400:54 (#0), vis_span: compiler/rustc_errors/src/lib.rs:398:1: 398:4 (#0), has_delayed_lints: false, eii: false })


thread 'rustc' (18439) panicked at compiler/rustc_hir_analysis/src/collect.rs:1046:13:
Box<dyn Any>
stack backtrace:
   0: std::panicking::begin_panic::<rustc_errors::ExplicitBug>
   1: <rustc_errors::diagnostic::BugAbort as rustc_errors::diagnostic::EmissionGuarantee>::emit_producing_guarantee
   2: rustc_middle::util::bug::opt_span_bug_fmt::<rustc_span::span_encoding::Span>::{closure#0}
   3: rustc_middle::ty::context::tls::with_opt::<rustc_middle::util::bug::opt_span_bug_fmt<rustc_span::span_encoding::Span>::{closure#0}, !>::{closure#0}
   4: rustc_middle::ty::context::tls::with_context_opt::<rustc_middle::ty::context::tls::with_opt<rustc_middle::util::bug::opt_span_bug_fmt<rustc_span::span_encoding::Span>::{closure#0}, !>::{closure#0}, !>
   5: rustc_middle::util::bug::bug_fmt
   6: rustc_hir_analysis::collect::fn_sig
      [... omitted 2 frames ...]
   7: <rustc_hir_typeck::fn_ctxt::FnCtxt>::check_expr_kind
   8: <rustc_hir_typeck::fn_ctxt::FnCtxt>::check_expr_with_expectation_and_args
   9: <rustc_hir_typeck::fn_ctxt::FnCtxt>::check_expr_with_expectation
  10: <rustc_hir_typeck::fn_ctxt::FnCtxt>::check_expr_block
  11: <rustc_hir_typeck::fn_ctxt::FnCtxt>::check_expr_kind
  12: <rustc_hir_typeck::fn_ctxt::FnCtxt>::check_expr_with_expectation_and_args
  13: <rustc_hir_typeck::fn_ctxt::FnCtxt>::check_expr_with_expectation
  14: <rustc_hir_typeck::fn_ctxt::FnCtxt>::check_expr_kind
  15: <rustc_hir_typeck::fn_ctxt::FnCtxt>::check_expr_with_expectation_and_args
  16: <rustc_hir_typeck::fn_ctxt::FnCtxt>::check_expr_with_expectation
  17: <rustc_hir_typeck::fn_ctxt::FnCtxt>::check_expr_block
  18: <rustc_hir_typeck::fn_ctxt::FnCtxt>::check_expr_kind
  19: <rustc_hir_typeck::fn_ctxt::FnCtxt>::check_expr_with_expectation_and_args
  20: <rustc_hir_typeck::fn_ctxt::FnCtxt>::check_expr_with_expectation
  21: <rustc_hir_typeck::fn_ctxt::FnCtxt>::check_expr_kind
  22: <rustc_hir_typeck::fn_ctxt::FnCtxt>::check_expr_with_expectation_and_args
  23: <rustc_hir_typeck::fn_ctxt::FnCtxt>::check_expr_with_expectation
  24: <rustc_hir_typeck::fn_ctxt::FnCtxt>::check_expr_block
  25: <rustc_hir_typeck::fn_ctxt::FnCtxt>::check_expr_kind
  26: <rustc_hir_typeck::fn_ctxt::FnCtxt>::check_expr_with_expectation_and_args
  27: <rustc_hir_typeck::fn_ctxt::FnCtxt>::check_expr_with_expectation
  28: <rustc_hir_typeck::fn_ctxt::FnCtxt>::check_expr_kind
  29: <rustc_hir_typeck::fn_ctxt::FnCtxt>::check_expr_with_expectation_and_args
  30: <rustc_hir_typeck::fn_ctxt::FnCtxt>::check_expr_with_expectation
  31: <rustc_hir_typeck::fn_ctxt::FnCtxt>::check_expr_block
  32: <rustc_hir_typeck::fn_ctxt::FnCtxt>::check_expr_kind
  33: <rustc_hir_typeck::fn_ctxt::FnCtxt>::check_expr_with_expectation_and_args
  34: <rustc_hir_typeck::fn_ctxt::FnCtxt>::check_expr_with_expectation
  35: <rustc_hir_typeck::fn_ctxt::FnCtxt>::check_return_or_body_tail
  36: rustc_hir_typeck::check::check_fn
  37: rustc_hir_typeck::typeck_with_inspect
      [... omitted 2 frames ...]
  38: <rustc_middle::ty::context::TyCtxt>::par_hir_body_owners::<rustc_hir_analysis::check_crate::{closure#2}>::{closure#0}
  39: rustc_data_structures::sync::parallel::par_for_each_in::<&rustc_span::def_id::LocalDefId, &[rustc_span::def_id::LocalDefId], <rustc_middle::ty::context::TyCtxt>::par_hir_body_owners<rustc_hir_analysis::check_crate::{closure#2}>::{closure#0}>
  40: rustc_hir_analysis::check_crate
  41: rustc_interface::passes::analysis
      [... omitted 2 frames ...]
  42: <std::thread::local::LocalKey<core::cell::Cell<*const ()>>>::with::<rustc_middle::ty::context::tls::enter_context<<rustc_middle::ty::context::GlobalCtxt>::enter<rustc_interface::passes::create_and_enter_global_ctxt<core::option::Option<rustc_interface::queries::Linker>, rustc_driver_impl::run_compiler::{closure#0}::{closure#2}>::{closure#2}, core::option::Option<rustc_interface::queries::Linker>>::{closure#1}, core::option::Option<rustc_interface::queries::Linker>>::{closure#0}, core::option::Option<rustc_interface::queries::Linker>>
  43: <rustc_middle::ty::context::TyCtxt>::create_global_ctxt::<core::option::Option<rustc_interface::queries::Linker>, rustc_interface::passes::create_and_enter_global_ctxt<core::option::Option<rustc_interface::queries::Linker>, rustc_driver_impl::run_compiler::{closure#0}::{closure#2}>::{closure#2}>
  44: rustc_interface::passes::create_and_enter_global_ctxt::<core::option::Option<rustc_interface::queries::Linker>, rustc_driver_impl::run_compiler::{closure#0}::{closure#2}>
  45: rustc_span::create_session_globals_then::<(), rustc_interface::util::run_in_thread_with_globals<rustc_interface::util::run_in_thread_pool_with_globals<rustc_interface::interface::run_compiler<(), rustc_driver_impl::run_compiler::{closure#0}>::{closure#1}, ()>::{closure#0}, ()>::{closure#0}::{closure#0}::{closure#0}>
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

note: using internal features is not supported and expected to cause internal compiler errors when used incorrectly

warning: the ICE couldn't be written to `/checkout/rustc-ice-2026-03-27T07_35_42-18436.txt`: Read-only file system (os error 30)

note: rustc 1.96.0-nightly (9b7c44bdf 2026-03-27) running on aarch64-unknown-linux-gnu

note: compiler flags: --crate-type lib -C opt-level=3 -C embed-bitcode=no -C debug-assertions=on -C symbol-mangling-version=v0 -Z annotate-moves -Z randomize-layout -Z unstable-options -Z macro-backtrace -C split-debuginfo=off -C llvm-args=-import-instr-limit=10 -C link-args=-Wl,-z,origin -C link-args=-Wl,-rpath,$ORIGIN/../lib -Z on-broken-pipe=kill -Z binary-dep-depinfo -Z tls-model=initial-exec -Z force-unstable-if-unmarked

note: some of the compiler flags provided by cargo are hidden

query stack during panic:
#0 [fn_sig] computing function signature of `TRACK_DIAGNOSTIC`
#1 [typeck] type-checking `<impl at compiler/rustc_errors/src/lib.rs:1152:1: 1152:19>::emit_diagnostic`
#2 [analysis] running analysis passes on crate `rustc_errors`
end of query stack
[RUSTC-TIMING] rustc_errors test:false 1.545
error: could not compile `rustc_errors` (lib)

Caused by:
  process didn't exit successfully: `/checkout/obj/build/bootstrap/debug/rustc /checkout/obj/build/bootstrap/debug/rustc --crate-name rustc_errors --edition=2024 compiler/rustc_errors/src/lib.rs --error-format=json --json=diagnostic-rendered-ansi,artifacts,future-incompat --crate-type lib --emit=dep-info,metadata,link -C opt-level=3 -C embed-bitcode=no -C debug-assertions=on --check-cfg 'cfg(docsrs,test)' --check-cfg 'cfg(feature, values())' -C metadata=1892970bc16317ce -C extra-filename=-648353350842c13d --out-dir /checkout/obj/build/aarch64-unknown-linux-gnu/stage2-rustc/aarch64-unknown-linux-gnu/release/deps --target aarch64-unknown-linux-gnu -L dependency=/checkout/obj/build/aarch64-unknown-linux-gnu/stage2-rustc/aarch64-unknown-linux-gnu/release/deps -L dependency=/checkout/obj/build/aarch64-unknown-linux-gnu/stage2-rustc/release/deps --extern annotate_snippets=/checkout/obj/build/aarch64-unknown-linux-gnu/stage2-rustc/aarch64-unknown-linux-gnu/release/deps/libannotate_snippets-03f5634917950bf8.rmeta --extern anstream=/checkout/obj/build/aarch64-unknown-linux-gnu/stage2-rustc/aarch64-unknown-linux-gnu/release/deps/libanstream-e49c737635dbc057.rmeta --extern anstyle=/checkout/obj/build/aarch64-unknown-linux-gnu/stage2-rustc/aarch64-unknown-linux-gnu/release/deps/libanstyle-e7f9df2ff2d761f6.rmeta --extern derive_setters=/checkout/obj/build/aarch64-unknown-linux-gnu/stage2-rustc/release/deps/libderive_setters-1ac52bb0f3e03331.so --extern rustc_abi=/checkout/obj/build/aarch64-unknown-linux-gnu/stage2-rustc/aarch64-unknown-linux-gnu/release/deps/librustc_abi-72d632a92aebc2eb.rmeta --extern rustc_ast=/checkout/obj/build/aarch64-unknown-linux-gnu/stage2-rustc/aarch64-unknown-linux-gnu/release/deps/librustc_ast-a919e74245c5771e.rmeta --extern rustc_data_structures=/checkout/obj/build/aarch64-unknown-linux-gnu/stage2-rustc/aarch64-unknown-linux-gnu/release/deps/librustc_data_structures-e5b49d51bf312e39.rmeta --extern rustc_error_codes=/checkout/obj/build/aarch64-unknown-linux-gnu/stage2-rustc/aarch64-unknown-linux-gnu/release/deps/librustc_error_codes-3dc99520261bd395.rmeta --extern rustc_error_messages=/checkout/obj/build/aarch64-unknown-linux-gnu/stage2-rustc/aarch64-unknown-linux-gnu/release/deps/librustc_error_messages-b8afb78171fb7a70.rmeta --extern rustc_hashes=/checkout/obj/build/aarch64-unknown-linux-gnu/stage2-rustc/aarch64-unknown-linux-gnu/release/deps/librustc_hashes-c4af0383450e4c22.rmeta --extern rustc_index=/checkout/obj/build/aarch64-unknown-linux-gnu/stage2-rustc/aarch64-unknown-linux-gnu/release/deps/librustc_index-06e0d5d322233dfe.rmeta --extern rustc_lint_defs=/checkout/obj/build/aarch64-unknown-linux-gnu/stage2-rustc/aarch64-unknown-linux-gnu/release/deps/librustc_lint_defs-46ebc1616619b30c.rmeta --extern rustc_macros=/checkout/obj/build/aarch64-unknown-linux-gnu/stage2-rustc/release/deps/librustc_macros-c8fc0724bde9c918.so --extern rustc_serialize=/checkout/obj/build/aarch64-unknown-linux-gnu/stage2-rustc/aarch64-unknown-linux-gnu/release/deps/librustc_serialize-ad3671fbe46006aa.rmeta --extern rustc_span=/checkout/obj/build/aarch64-unknown-linux-gnu/stage2-rustc/aarch64-unknown-linux-gnu/release/deps/librustc_span-13ea748e364576a4.rmeta --extern serde=/checkout/obj/build/aarch64-unknown-linux-gnu/stage2-rustc/aarch64-unknown-linux-gnu/release/deps/libserde-9bfa0c796cbb1f71.rmeta --extern serde_json=/checkout/obj/build/aarch64-unknown-linux-gnu/stage2-rustc/aarch64-unknown-linux-gnu/release/deps/libserde_json-8a208799ec42c5b1.rmeta --extern termize=/checkout/obj/build/aarch64-unknown-linux-gnu/stage2-rustc/aarch64-unknown-linux-gnu/release/deps/libtermize-1bd908db64d2ec7c.rmeta --extern tracing=/checkout/obj/build/aarch64-unknown-linux-gnu/stage2-rustc/aarch64-unknown-linux-gnu/release/deps/libtracing-43aa014eee29a2e1.rmeta --cfg=windows_raw_dylib -Csymbol-mangling-version=v0 -Zannotate-moves -Zrandomize-layout -Zunstable-options '--check-cfg=cfg(bootstrap)' -Zmacro-backtrace -Csplit-debuginfo=off -Cllvm-args=-import-instr-limit=10 -Clink-args=-Wl,-z,origin '-Clink-args=-Wl,-rpath,$ORIGIN/../lib' -Zon-broken-pipe=kill -Z binary-dep-depinfo -L native=/checkout/obj/build/aarch64-unknown-linux-gnu/stage2-rustc/aarch64-unknown-linux-gnu/release/build/psm-f914de601d602ad7/out -L native=/checkout/obj/build/aarch64-unknown-linux-gnu/stage2-rustc/aarch64-unknown-linux-gnu/release/build/blake3-0f4c8d2d3a28490b/out` (exit status: 101)
warning: build failed, waiting for other jobs to finish...
[RUSTC-TIMING] build_script_build test:false 0.238
[RUSTC-TIMING] annotate_snippets test:false 4.966
[RUSTC-TIMING] object test:false 11.890
Bootstrap failed while executing `--stage 2 test --skip tidy --skip compiler --skip src`

Copy link
Copy Markdown
Contributor

@oli-obk oli-obk left a comment

Choose a reason for hiding this comment

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

I think this PR is showing somewhat what the limitations of tuple splatting are, compared to having multiple different Fn impls for the same type.

Having a generic T: Tuple as the last parameter will always require MIR to treat the last argument as a tuple, and backends to handle the untupling, along with any ABI concerns around all that.

A non-generic last argument is almost never useful, as you want to allow for different logic depending on the size of the tuple that is being splatted.

I'm continuing this train of thought on zulip, maybe we can figure sth out

https://rust-lang.zulipchat.com/#narrow/channel/213817-t-lang/topic/On.20overloading/near/582319866

View changes since this review

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?

};

// 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
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 ?

Comment on lines 1089 to 1092
liberated_sig.c_variadic,
liberated_sig.splatted,
hir::Safety::Safe,
rustc_abi::ExternAbi::Rust,
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

Comment on lines +232 to +236
// If the callee has `#[splat]` on an argument
if let hir::ExprKind::Path(ref qpath) = callee_expr.kind
&& let Res::Def(_def_kind, def_id) =
self.typeck_results.borrow().qpath_res(qpath, callee_expr.hir_id)
&& self.tcx.fn_sig(def_id).skip_binder().skip_binder().splatted
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.

you can get the fn sig directly from the adjusted_ty with the fn_sig method

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.

I think otherwise your logic won't work for function pointers and also not for let x = f; x()

}

Some(CallStep::Splatted(callee_ty)) => {
self.confirm_splatted_call(call_expr, callee_ty, arg_exprs, expected)
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.

why is this needed? it's basically the same function, what's the actual difference to confirm_builtin_call?

type_dependent_defs: ItemLocalMap<Result<(DefKind, DefId), ErrorGuaranteed>>,

/// Resolved definitions for splatted function calls, including the splatted argument index.
splatted_defs:
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.

this tuple deserves a struct 😅

pub fn is_splatted_method_call(&self, expr: &hir::Expr<'_>) -> bool {
matches!(
self.splatted_defs().get(expr.hir_id),
Some(Ok((true /* is_method_call */, DefKind::AssocFn, _, _)))
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.

is the "is a method call" part and the DefKind useful? the only caller to is_spatted_method_call already knows it's a method

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Mar 28, 2026
@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented Mar 28, 2026

Reminder, once the PR becomes ready for a review, use @rustbot ready.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-attributes Area: Attributes (`#[…]`, `#![…]`) A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. A-query-system Area: The rustc query system (https://rustc-dev-guide.rust-lang.org/query.html) S-blocked Status: Blocked on something else such as an RFC or other implementation work. S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-clippy Relevant to the Clippy team. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue. T-rust-analyzer Relevant to the rust-analyzer team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants