Skip to content

Commit 193c83e

Browse files
committed
Add AST validation for #[splat]
1 parent 902d075 commit 193c83e

6 files changed

Lines changed: 238 additions & 2 deletions

File tree

compiler/rustc_ast_passes/src/ast_validation.rs

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,8 @@ impl<'a> AstValidator<'a> {
352352

353353
fn check_fn_decl(&self, fn_decl: &FnDecl, self_semantic: SelfSemantic) {
354354
self.check_decl_num_args(fn_decl);
355-
self.check_decl_cvariadic_pos(fn_decl);
355+
let c_variadic_span = self.check_decl_cvariadic_pos(fn_decl);
356+
self.check_decl_splatting(fn_decl, c_variadic_span);
356357
self.check_decl_attrs(fn_decl);
357358
self.check_decl_self_param(fn_decl, self_semantic);
358359
}
@@ -370,17 +371,59 @@ impl<'a> AstValidator<'a> {
370371
/// Emits an error if a function declaration has a variadic parameter in the
371372
/// beginning or middle of parameter list.
372373
/// Example: `fn foo(..., x: i32)` will emit an error.
373-
fn check_decl_cvariadic_pos(&self, fn_decl: &FnDecl) {
374+
/// If a C-variadic parameter is found, returns its span.
375+
fn check_decl_cvariadic_pos(&self, fn_decl: &FnDecl) -> Option<Span> {
376+
let mut c_variadic_span = None;
377+
374378
match &*fn_decl.inputs {
375379
[ps @ .., _] => {
376380
for Param { ty, span, .. } in ps {
377381
if let TyKind::CVarArgs = ty.kind {
382+
c_variadic_span = Some(*span);
378383
self.dcx().emit_err(diagnostics::FnParamCVarArgsNotLast { span: *span });
379384
}
380385
}
381386
}
382387
_ => {}
383388
}
389+
390+
if let Some(Param { ty, span, .. }) = &fn_decl.inputs.last() {
391+
if let TyKind::CVarArgs = ty.kind {
392+
c_variadic_span = Some(*span);
393+
}
394+
}
395+
396+
c_variadic_span
397+
}
398+
399+
/// Emits an error if a function declaration has more than one splatted argument, with a
400+
/// C-variadic parameter, or a splat at an unsupported index (for performance).
401+
/// 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
404+
.inputs
405+
.iter()
406+
.enumerate()
407+
.filter_map(|(index, arg)| {
408+
arg.attrs
409+
.iter()
410+
.any(|attr| attr.has_name(sym::splat))
411+
.then_some((u16::try_from(index).unwrap(), arg.span))
412+
})
413+
.unzip();
414+
415+
// Multiple splatted arguments are invalid: we can't know which arguments go in each splat.
416+
if splatted_arg_indexes.len() > 1 {
417+
self.dcx()
418+
.emit_err(diagnostics::DuplicateSplattedArgs { spans: splatted_spans.clone() });
419+
}
420+
421+
if let Some(c_variadic_span) = c_variadic_span
422+
&& !splatted_spans.is_empty()
423+
{
424+
splatted_spans.push(c_variadic_span);
425+
self.dcx().emit_err(diagnostics::CVarArgsAndSplat { spans: splatted_spans });
426+
}
384427
}
385428

386429
fn check_decl_attrs(&self, fn_decl: &FnDecl) {
@@ -396,6 +439,7 @@ impl<'a> AstValidator<'a> {
396439
sym::deny,
397440
sym::expect,
398441
sym::forbid,
442+
sym::splat,
399443
sym::warn,
400444
];
401445
!attr.has_any_name(&arr) && rustc_attr_parsing::is_builtin_attr(*attr)

compiler/rustc_ast_passes/src/diagnostics.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,22 @@ pub(crate) struct FnParamCVarArgsNotLast {
123123
pub span: Span,
124124
}
125125

126+
#[derive(Diagnostic)]
127+
#[diag("multiple `#[splat]`s are not allowed in the same function")]
128+
#[help("remove `#[splat]` from all but one argument")]
129+
pub(crate) struct DuplicateSplattedArgs {
130+
#[primary_span]
131+
pub spans: Vec<Span>,
132+
}
133+
134+
#[derive(Diagnostic)]
135+
#[diag("`...` and `#[splat]` are not allowed in the same function")]
136+
#[help("remove `#[splat]` or remove `...`")]
137+
pub(crate) struct CVarArgsAndSplat {
138+
#[primary_span]
139+
pub spans: Vec<Span>,
140+
}
141+
126142
#[derive(Diagnostic)]
127143
#[diag("documentation comments cannot be applied to function parameters")]
128144
pub(crate) struct FnParamDocComment {
@@ -131,6 +147,7 @@ pub(crate) struct FnParamDocComment {
131147
pub span: Span,
132148
}
133149

150+
// FIXME(splat): add splat to the allowed built-in attributes when it is complete/stabilized
134151
#[derive(Diagnostic)]
135152
#[diag(
136153
"allow, cfg, cfg_attr, deny, expect, forbid, and warn are the only allowed built-in attributes in function parameters"

tests/ui/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1287,6 +1287,12 @@ An assorted collection of tests that involves specific diagnostic spans.
12871287

12881288
See [Tracking issue for specialization (RFC 1210) #31844](https://github.com/rust-lang/rust/issues/31844).
12891289

1290+
## `tests/ui/splat`
1291+
1292+
Tests for the `#![feature(splat)]` attribute.
1293+
1294+
See [Tracking Issue for argument splatting #153629](https://github.com/rust-lang/rust/issues/153629).
1295+
12901296
## `tests/ui/stability-attribute/`
12911297

12921298
Stability attributes used internally by the standard library: `#[stable()]` and `#[unstable()]`.

tests/ui/splat/splat-invalid.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//! Test using `#[splat]` incorrectly, in ways not covered by other tests.
2+
3+
#![allow(incomplete_features)]
4+
#![feature(splat)]
5+
#![feature(c_variadic)]
6+
7+
fn multisplat_bad(#[splat] (_a, _b): (u32, i8), #[splat] (_c, _d): (u32, i8)) {}
8+
//~^ ERROR multiple `#[splat]`s are not allowed in the same function
9+
10+
unsafe extern "C" fn splat_variadic(#[splat] (_a, _b): (u32, i8), varargs: ...) {}
11+
//~^ ERROR `...` and `#[splat]` are not allowed in the same function
12+
13+
unsafe extern "C" fn splat_variadic2(varargs: ..., #[splat] (_a, _b): (u32, i8)) {}
14+
//~^ ERROR `...` and `#[splat]` are not allowed in the same function
15+
//~| ERROR `...` must be the last argument of a C-variadic function
16+
17+
extern "C" {
18+
fn splat_variadic3(#[splat] (_a, _b): (u32, i8), ...) {}
19+
//~^ ERROR incorrect function inside `extern` block
20+
//~| ERROR `...` and `#[splat]` are not allowed in the same function
21+
22+
fn splat_variadic4(..., #[splat] (_a, _b): (u32, i8)) {}
23+
//~^ ERROR incorrect function inside `extern` block
24+
//~| ERROR `...` and `#[splat]` are not allowed in the same function
25+
//~| ERROR `...` must be the last argument of a C-variadic function
26+
27+
// FIXME(splat): tuple layouts are unspecified. Should this error in addition to
28+
// the existing `improper_ctypes` lint?
29+
#[expect(improper_ctypes)]
30+
fn bar_2(#[splat] _: (u32, i8));
31+
}
32+
33+
fn main() {}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
error: multiple `#[splat]`s are not allowed in the same function
2+
--> $DIR/splat-invalid.rs:7:19
3+
|
4+
LL | fn multisplat_bad(#[splat] (_a, _b): (u32, i8), #[splat] (_c, _d): (u32, i8)) {}
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= help: remove `#[splat]` from all but one argument
8+
9+
error: `...` and `#[splat]` are not allowed in the same function
10+
--> $DIR/splat-invalid.rs:10:37
11+
|
12+
LL | unsafe extern "C" fn splat_variadic(#[splat] (_a, _b): (u32, i8), varargs: ...) {}
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^
14+
|
15+
= help: remove `#[splat]` or remove `...`
16+
17+
error: `...` must be the last argument of a C-variadic function
18+
--> $DIR/splat-invalid.rs:13:38
19+
|
20+
LL | unsafe extern "C" fn splat_variadic2(varargs: ..., #[splat] (_a, _b): (u32, i8)) {}
21+
| ^^^^^^^^^^^^
22+
23+
error: `...` and `#[splat]` are not allowed in the same function
24+
--> $DIR/splat-invalid.rs:13:38
25+
|
26+
LL | unsafe extern "C" fn splat_variadic2(varargs: ..., #[splat] (_a, _b): (u32, i8)) {}
27+
| ^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
28+
|
29+
= help: remove `#[splat]` or remove `...`
30+
31+
error: incorrect function inside `extern` block
32+
--> $DIR/splat-invalid.rs:18:8
33+
|
34+
LL | extern "C" {
35+
| ---------- `extern` blocks define existing foreign functions and functions inside of them cannot have a body
36+
LL | fn splat_variadic3(#[splat] (_a, _b): (u32, i8), ...) {}
37+
| ^^^^^^^^^^^^^^^ cannot have a body -- help: remove the invalid body: `;`
38+
|
39+
= help: you might have meant to write a function accessible through FFI, which can be done by writing `extern fn` outside of the `extern` block
40+
= note: for more information, visit https://doc.rust-lang.org/std/keyword.extern.html
41+
42+
error: `...` and `#[splat]` are not allowed in the same function
43+
--> $DIR/splat-invalid.rs:18:24
44+
|
45+
LL | fn splat_variadic3(#[splat] (_a, _b): (u32, i8), ...) {}
46+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^
47+
|
48+
= help: remove `#[splat]` or remove `...`
49+
50+
error: incorrect function inside `extern` block
51+
--> $DIR/splat-invalid.rs:22:8
52+
|
53+
LL | extern "C" {
54+
| ---------- `extern` blocks define existing foreign functions and functions inside of them cannot have a body
55+
...
56+
LL | fn splat_variadic4(..., #[splat] (_a, _b): (u32, i8)) {}
57+
| ^^^^^^^^^^^^^^^ cannot have a body -- help: remove the invalid body: `;`
58+
|
59+
= help: you might have meant to write a function accessible through FFI, which can be done by writing `extern fn` outside of the `extern` block
60+
= note: for more information, visit https://doc.rust-lang.org/std/keyword.extern.html
61+
62+
error: `...` must be the last argument of a C-variadic function
63+
--> $DIR/splat-invalid.rs:22:24
64+
|
65+
LL | fn splat_variadic4(..., #[splat] (_a, _b): (u32, i8)) {}
66+
| ^^^
67+
68+
error: `...` and `#[splat]` are not allowed in the same function
69+
--> $DIR/splat-invalid.rs:22:24
70+
|
71+
LL | fn splat_variadic4(..., #[splat] (_a, _b): (u32, i8)) {}
72+
| ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
73+
|
74+
= help: remove `#[splat]` or remove `...`
75+
76+
error: aborting due to 9 previous errors
77+

tests/ui/splat/splat-non-fn-arg.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//! Test that using `#[splat]` on non-function-arguments is an error.
2+
3+
#![allow(incomplete_features)]
4+
#![feature(splat)]
5+
6+
#[splat] //~ ERROR `#[splat]` attribute cannot be used on functions
7+
fn tuple_args_bad((a, b): (u32, i8)) {}
8+
9+
#[splat] //~ ERROR `#[splat]` attribute cannot be used on traits
10+
trait FooTraitBad {
11+
fn tuple_1(_: (u32,));
12+
13+
fn tuple_4(self, _: (u32, i8, (), f32));
14+
}
15+
16+
struct Foo;
17+
18+
#[splat] //~ ERROR `#[splat]` attribute cannot be used on inherent impl blocks
19+
impl Foo {
20+
fn tuple_1_bad((a,): (u32,)) {}
21+
}
22+
23+
impl Foo {
24+
#[splat] //~ ERROR `#[splat]` attribute cannot be used on inherent methods
25+
fn tuple_3_bad((a, b, c): (u32, i32, i8)) {}
26+
27+
#[splat] //~ ERROR `#[splat]` attribute cannot be used on inherent methods
28+
fn tuple_2_bad(self, (a, b): (u32, i8)) -> u32 {
29+
a
30+
}
31+
}
32+
33+
#[splat] //~ ERROR `#[splat]` attribute cannot be used on trait impl blocks
34+
impl FooTraitBad for Foo {
35+
fn tuple_1(_: (u32,)) {}
36+
37+
fn tuple_4(self, _: (u32, i8, (), f32)) {}
38+
}
39+
40+
#[splat] //~ ERROR `#[splat]` attribute cannot be used on foreign modules
41+
extern "C" {
42+
fn foo_2(_: (u32, i8));
43+
}
44+
45+
extern "C" {
46+
#[splat] //~ ERROR `#[splat]` attribute cannot be used on foreign functions
47+
fn bar_2_bad(_: (u32, i8));
48+
}
49+
50+
#[splat] //~ ERROR `#[splat]` attribute cannot be used on modules
51+
mod foo_mod {}
52+
53+
#[splat] //~ ERROR `#[splat]` attribute cannot be used on use statements
54+
use std::mem;
55+
56+
#[splat] //~ ERROR `#[splat]` attribute cannot be used on structs
57+
struct FooStruct;
58+
59+
fn main() {}

0 commit comments

Comments
 (0)