Skip to content

Commit 83fe45c

Browse files
committed
Ban splat on closures and rust-call
1 parent 2babfd5 commit 83fe45c

8 files changed

Lines changed: 205 additions & 18 deletions

compiler/rustc_ast_passes/src/ast_validation.rs

Lines changed: 73 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,39 @@ enum SelfSemantic {
4545
No,
4646
}
4747

48+
/// Is `#[splat]` allowed semantically in a function or closure?
49+
/// Only applies to the function kind and header, the parameters are checked elsewhere.
50+
enum SplatSemantic {
51+
Yes,
52+
NoClosures(Span),
53+
NoRustCall(Span),
54+
}
55+
56+
impl SplatSemantic {
57+
/// Returns if splatting is semantically allowed for the given `FnKind`,
58+
/// Only checks the function kind and header, not the parameters.
59+
fn from_fn_kind(fk: &FnKind<'_>) -> Self {
60+
match fk {
61+
FnKind::Fn(_, _, f) => Self::from_extern(f.sig.header.ext),
62+
// Splatting closures is banned, because closure arguments are already de-tupled.
63+
FnKind::Closure(_, _, _, expr) => SplatSemantic::NoClosures(expr.span),
64+
}
65+
}
66+
67+
fn from_extern(ext: Extern) -> Self {
68+
match ext {
69+
Extern::None => SplatSemantic::Yes,
70+
// FIXME(splat): should splatting extern "C" or other ABIs be allowed?
71+
Extern::Implicit(_) => SplatSemantic::Yes,
72+
// For now, splatting rust-call is banned, because it already de-tuples args.
73+
Extern::Explicit(abi_str, span) if abi_str.symbol_unescaped.as_str() == "rust-call" => {
74+
SplatSemantic::NoRustCall(span)
75+
}
76+
Extern::Explicit(_abi_str, _span) => SplatSemantic::Yes,
77+
}
78+
}
79+
}
80+
4881
enum TraitOrImpl {
4982
Trait { vis: Span, constness: Const },
5083
TraitImpl { constness: Const, polarity: ImplPolarity, trait_ref_span: Span },
@@ -350,10 +383,15 @@ impl<'a> AstValidator<'a> {
350383
});
351384
}
352385

353-
fn check_fn_decl(&self, fn_decl: &FnDecl, self_semantic: SelfSemantic) {
386+
fn check_fn_decl(
387+
&self,
388+
fn_decl: &FnDecl,
389+
self_semantic: SelfSemantic,
390+
splat_semantic: SplatSemantic,
391+
) {
354392
self.check_decl_num_args(fn_decl);
355393
let c_variadic_span = self.check_decl_cvariadic_pos(fn_decl);
356-
self.check_decl_splatting(fn_decl, c_variadic_span);
394+
self.check_decl_splatting(fn_decl, c_variadic_span, splat_semantic);
357395
self.check_decl_attrs(fn_decl);
358396
self.check_decl_self_param(fn_decl, self_semantic);
359397
}
@@ -399,8 +437,13 @@ impl<'a> AstValidator<'a> {
399437
/// Emits an error if a function declaration has more than one splatted argument, with a
400438
/// C-variadic parameter, or a splat at an unsupported index (for performance).
401439
/// Example: `fn foo(#[splat] x: (), #[splat] y: ())` will emit an error.
402-
fn check_decl_splatting(&self, fn_decl: &FnDecl, c_variadic_span: Option<Span>) {
403-
let (splatted_arg_indexes, mut splatted_spans): (Vec<u16>, Vec<Span>) = fn_decl
440+
fn check_decl_splatting(
441+
&self,
442+
fn_decl: &FnDecl,
443+
c_variadic_span: Option<Span>,
444+
splat_semantic: SplatSemantic,
445+
) {
446+
let (splatted_arg_indexes, splatted_spans): (Vec<u16>, Vec<Span>) = fn_decl
404447
.inputs
405448
.iter()
406449
.enumerate()
@@ -433,9 +476,28 @@ impl<'a> AstValidator<'a> {
433476
if let Some(c_variadic_span) = c_variadic_span
434477
&& !splatted_spans.is_empty()
435478
{
479+
let mut splatted_spans = splatted_spans.clone();
436480
splatted_spans.push(c_variadic_span);
437481
self.dcx().emit_err(diagnostics::CVarArgsAndSplat { spans: splatted_spans });
438482
}
483+
484+
if !splatted_arg_indexes.is_empty() {
485+
match splat_semantic {
486+
SplatSemantic::NoClosures(closure_span) => {
487+
let mut splatted_spans = splatted_spans.clone();
488+
splatted_spans.push(closure_span);
489+
self.dcx()
490+
.emit_err(diagnostics::SplatNotAllowedOnClosures { spans: splatted_spans });
491+
}
492+
SplatSemantic::NoRustCall(abi_span) => {
493+
let mut splatted_spans = splatted_spans;
494+
splatted_spans.push(abi_span);
495+
self.dcx()
496+
.emit_err(diagnostics::SplatNotAllowedOnRustCall { spans: splatted_spans });
497+
}
498+
SplatSemantic::Yes => {}
499+
}
500+
}
439501
}
440502

441503
fn check_decl_attrs(&self, fn_decl: &FnDecl) {
@@ -1055,7 +1117,11 @@ impl<'a> AstValidator<'a> {
10551117
match &ty.kind {
10561118
TyKind::FnPtr(bfty) => {
10571119
self.check_fn_ptr_safety(bfty.decl_span, bfty.safety);
1058-
self.check_fn_decl(&bfty.decl, SelfSemantic::No);
1120+
self.check_fn_decl(
1121+
&bfty.decl,
1122+
SelfSemantic::No,
1123+
SplatSemantic::from_extern(bfty.ext),
1124+
);
10591125
Self::check_decl_no_pat(&bfty.decl, |span, _, _| {
10601126
self.dcx().emit_err(diagnostics::PatternFnPointer { span });
10611127
});
@@ -1746,7 +1812,8 @@ impl Visitor<'_> for AstValidator<'_> {
17461812
Some(FnCtxt::Assoc(_)) => SelfSemantic::Yes,
17471813
_ => SelfSemantic::No,
17481814
};
1749-
self.check_fn_decl(fk.decl(), self_semantic);
1815+
let splat_semantic = SplatSemantic::from_fn_kind(&fk);
1816+
self.check_fn_decl(fk.decl(), self_semantic, splat_semantic);
17501817

17511818
if let Some(&FnHeader { safety, .. }) = fk.header() {
17521819
self.check_item_safety(span, safety);

compiler/rustc_ast_passes/src/diagnostics.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,22 @@ pub(crate) struct CVarArgsAndSplat {
150150
pub spans: Vec<Span>,
151151
}
152152

153+
#[derive(Diagnostic)]
154+
#[diag("`#[splat]` is not allowed on closures")]
155+
#[help("remove `#[splat]` or turn the closure into a function")]
156+
pub(crate) struct SplatNotAllowedOnClosures {
157+
#[primary_span]
158+
pub spans: Vec<Span>,
159+
}
160+
161+
#[derive(Diagnostic)]
162+
#[diag("`#[splat]` is not allowed on functions with the `rust-call` ABI")]
163+
#[help("remove `#[splat]` or change the ABI")]
164+
pub(crate) struct SplatNotAllowedOnRustCall {
165+
#[primary_span]
166+
pub spans: Vec<Span>,
167+
}
168+
153169
#[derive(Diagnostic)]
154170
#[diag("documentation comments cannot be applied to function parameters")]
155171
pub(crate) struct FnParamDocComment {
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//! Checks that closures and rust-call functions can't be splatted.
2+
//! This should be rejected until we decide on sensible semantics.
3+
4+
#![feature(splat, unboxed_closures, tuple_trait)]
5+
#![expect(incomplete_features)]
6+
7+
use std::marker::Tuple;
8+
9+
trait Trait: Tuple + Sized {
10+
extern "rust-call" fn method(#[splat] self: Self);
11+
//~^ ERROR: `#[splat]` is not allowed on functions with the `rust-call` ABI
12+
}
13+
14+
impl Trait for (i32, i64) {
15+
extern "rust-call" fn method(#[splat] self: Self) {
16+
//~^ ERROR: `#[splat]` is not allowed on functions with the `rust-call` ABI
17+
println!("{self:?}");
18+
}
19+
}
20+
21+
fn main() {
22+
(|#[splat] x: i32| {
23+
//~^ ERROR `#[splat]` is not allowed on closures
24+
println!("{x}");
25+
})(1);
26+
27+
(1_i32, 2_i64).method();
28+
Trait::method(3_i32, 4_i64);
29+
//~^ ERROR functions with the "rust-call" ABI must take a single non-self tuple argument
30+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
error: `#[splat]` is not allowed on functions with the `rust-call` ABI
2+
--> $DIR/reject-closure-issue-158605.rs:10:5
3+
|
4+
LL | extern "rust-call" fn method(#[splat] self: Self);
5+
| ^^^^^^^^^^^^^^^^^^ ^^^^
6+
|
7+
= help: remove `#[splat]` or change the ABI
8+
9+
error: `#[splat]` is not allowed on functions with the `rust-call` ABI
10+
--> $DIR/reject-closure-issue-158605.rs:15:5
11+
|
12+
LL | extern "rust-call" fn method(#[splat] self: Self) {
13+
| ^^^^^^^^^^^^^^^^^^ ^^^^
14+
|
15+
= help: remove `#[splat]` or change the ABI
16+
17+
error: `#[splat]` is not allowed on closures
18+
--> $DIR/reject-closure-issue-158605.rs:22:7
19+
|
20+
LL | (|#[splat] x: i32| {
21+
| _______^^^^^^^^^^^^^^^__^
22+
LL | |
23+
LL | | println!("{x}");
24+
LL | | })(1);
25+
| |_____^
26+
|
27+
= help: remove `#[splat]` or turn the closure into a function
28+
29+
error: functions with the "rust-call" ABI must take a single non-self tuple argument
30+
--> $DIR/reject-closure-issue-158605.rs:28:26
31+
|
32+
LL | Trait::method(3_i32, 4_i64);
33+
| ^^^^^
34+
35+
error: aborting due to 4 previous errors
36+

tests/ui/splat/splat-fn-ptr-cast.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
fn main() {
99
// Bug #158603 regression test variants
1010
#[rustfmt::skip]
11-
let _x: fn(#[splat] (i32,)) = None.unwrap();
11+
let _x: fn(#[splat] (f32,)) = None.unwrap();
1212
// FIXME(splat): causes an ICE until #158603 is fixed
13-
//x(1);
13+
//x(1.0);
1414

1515
let x: fn((i32,)) = None.unwrap();
1616
x((1,));

tests/ui/splat/splat-fn-ptr-cast.stderr

Lines changed: 0 additions & 10 deletions
This file was deleted.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//! Test using `#[splat]` on tuple arguments of pointers to "rust-call" functions.
2+
//! Currently ICEs at a later stage, but AST validation should catch it earlier.
3+
4+
#![allow(incomplete_features)]
5+
#![feature(splat)]
6+
#![feature(unboxed_closures)]
7+
8+
extern "rust-call" fn f(#[splat] _: ()) {} //~ ERROR `#[splat]` is not allowed on functions with the `rust-call` ABI
9+
10+
fn main() {
11+
// FIXME(rustfmt): the attribute gets deleted by rustfmt
12+
#[rustfmt::skip]
13+
let f2: extern "rust-call" fn(#[splat] ()) = f; //~ ERROR `#[splat]` is not allowed on functions with the `rust-call` ABI
14+
// These errors could be confusing, but they're useful if the user meant to use "rust-call"
15+
// instead of #[splat]
16+
f(); //~ ERROR functions with the "rust-call" ABI must take a single non-self tuple argument
17+
f2(); //~ ERROR functions with the "rust-call" ABI must take a single non-self tuple argument
18+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
error: `#[splat]` is not allowed on functions with the `rust-call` ABI
2+
--> $DIR/splat-fn-ptr-rust-call.rs:8:1
3+
|
4+
LL | extern "rust-call" fn f(#[splat] _: ()) {}
5+
| ^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^
6+
|
7+
= help: remove `#[splat]` or change the ABI
8+
9+
error: `#[splat]` is not allowed on functions with the `rust-call` ABI
10+
--> $DIR/splat-fn-ptr-rust-call.rs:13:13
11+
|
12+
LL | let f2: extern "rust-call" fn(#[splat] ()) = f;
13+
| ^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^
14+
|
15+
= help: remove `#[splat]` or change the ABI
16+
17+
error: functions with the "rust-call" ABI must take a single non-self tuple argument
18+
--> $DIR/splat-fn-ptr-rust-call.rs:16:5
19+
|
20+
LL | f();
21+
| ^^^
22+
23+
error: functions with the "rust-call" ABI must take a single non-self tuple argument
24+
--> $DIR/splat-fn-ptr-rust-call.rs:17:5
25+
|
26+
LL | f2();
27+
| ^^^^
28+
29+
error: aborting due to 4 previous errors
30+

0 commit comments

Comments
 (0)